Skip to content
This repository has been archived by the owner on May 12, 2023. It is now read-only.

feat: image transform support #46

Merged
merged 34 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bbde7fc
add basic transform test
dshukertjr Dec 1, 2022
98c4a71
implemented tests
dshukertjr Dec 2, 2022
cd58fca
remove usage of deprecated download
dshukertjr Dec 3, 2022
b2ae92f
adds storage docker file
dshukertjr Dec 3, 2022
f2c1bf1
adds comments
dshukertjr Dec 3, 2022
e4c2ad5
Implements publicDownload and authenticatedDownload
dshukertjr Dec 5, 2022
c3b6b81
adds public URL support
dshukertjr Dec 5, 2022
f958b16
update path to sadcat.jpg
dshukertjr Dec 5, 2022
22b3e69
fix path of sadcat
dshukertjr Dec 5, 2022
f5b2458
minor fix
dshukertjr Dec 5, 2022
e25a0f1
fixed stub of public download
dshukertjr Dec 5, 2022
6e78063
get absolute path of file
dshukertjr Dec 5, 2022
6395017
using Platform.script to get file directory
dshukertjr Dec 5, 2022
4e0ff45
use dirname to create direcotry
dshukertjr Dec 5, 2022
fe5302b
remove test from file path
dshukertjr Dec 5, 2022
7a75e15
removed typo ( from path
dshukertjr Dec 5, 2022
3480e43
readd test
dshukertjr Dec 5, 2022
2498819
update file path
dshukertjr Dec 5, 2022
d8dc24d
fix path to the sadcat file
dshukertjr Dec 6, 2022
ee320e1
create bucket before uploading
dshukertjr Dec 6, 2022
563809f
fix doanload tests to handle futures properly
dshukertjr Dec 6, 2022
f32ae2f
write downloaded files locally in tests
dshukertjr Dec 6, 2022
c91b46f
created toQueryParams
dshukertjr Dec 6, 2022
48f0250
made public url code leaner
dshukertjr Dec 6, 2022
bb96173
adds more comment docs
dshukertjr Dec 6, 2022
85e7b75
Update lib/src/types.dart
dshukertjr Dec 7, 2022
5eb5a4d
update tests to use download()
dshukertjr Dec 12, 2022
5bcce48
update download()
dshukertjr Dec 12, 2022
7ebb7b9
Merge branch 'feat/transform' of https://github.com/supabase-communit…
dshukertjr Dec 12, 2022
a80200f
update example
dshukertjr Dec 12, 2022
4e5cf28
update test
dshukertjr Dec 12, 2022
873bbdb
fix download fix
dshukertjr Dec 12, 2022
84171a5
Update lib/src/storage_file_api.dart
dshukertjr Dec 12, 2022
b9887dd
chore: publish v1.2.0
dshukertjr Dec 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## [1.2.0]

- feat: add transform option to `createSignedUrl()`, `getPublicUrl()`, and `.download()` to transform images on the fly [#46](https://github.com/supabase/storage-dart/pull/46)
```dart
final signedUrl = await storage.from(newBucketName).createSignedUrl(uploadPath, 2000,
transform: TransformOptions(
width: 100,
height: 100,
));

final publicUrl = storage.from(bucket).getPublicUrl(path,
transform: TransformOptions(width: 200, height: 300));

final file = await storage.from(newBucketName).download(uploadPath,
transform: TransformOptions(
width: 200,
height: 200,
));
```

## [1.1.0]

- feat: add retry on file upload failure when offline ([#44](https://github.com/supabase/storage-dart/pull/44))
Expand Down
35 changes: 30 additions & 5 deletions infra/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@ services:
ports:
- '3000:3000'
depends_on:
- db
storage:
condition: service_healthy
restart: always
environment:
PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres
PGRST_DB_SCHEMA: public, storage
PGRST_DB_ANON_ROLE: postgres
PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
storage:
image: supabase/storage-api:v0.13.0
build:
context: ./storage
ports:
- '5000:5000'
depends_on:
- db
- rest
db:
condition: service_healthy
restart: always
environment:
ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.ReNhHIoXIOa-8tL1DO3e26mJmOTnYuvdgobwIYGzrLQ
Expand All @@ -47,6 +49,12 @@ services:
FILE_SIZE_LIMIT: 52428800
STORAGE_BACKEND: file
FILE_STORAGE_BACKEND_PATH: /tmp/storage
ENABLE_IMAGE_TRANSFORMATION: "true"
IMGPROXY_URL: http://imgproxy:8080
volumes:
- assets-volume:/tmp/storage
healthcheck:
test: ['CMD-SHELL', 'curl -f -LI http://localhost:5000/status']
db:
build:
context: ./postgres
Expand All @@ -60,4 +68,21 @@ services:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
POSTGRES_PORT: 5432
healthcheck:
test: [ "CMD-SHELL", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5

imgproxy:
image: darthsim/imgproxy
ports:
- 50020:8080
volumes:
- assets-volume:/tmp/storage
environment:
- IMGPROXY_LOCAL_FILESYSTEM_ROOT=/
- IMGPROXY_USE_ETAG=true
volumes:
assets-volume:
3 changes: 3 additions & 0 deletions infra/storage/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM supabase/storage-api:v0.25.1

RUN apk add curl --no-cache
8 changes: 4 additions & 4 deletions lib/src/fetch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Fetch {
Future<dynamic> _handleRequest(
String method,
String url,
dynamic body,
Map<String, dynamic>? body,
FetchOptions? options,
) async {
final headers = options?.headers ?? {};
Expand Down Expand Up @@ -181,23 +181,23 @@ class Fetch {

Future<dynamic> post(
String url,
dynamic body, {
Map<String, dynamic>? body, {
FetchOptions? options,
}) async {
return _handleRequest('POST', url, body, options);
}

Future<dynamic> put(
String url,
dynamic body, {
Map<String, dynamic>? body, {
FetchOptions? options,
}) async {
return _handleRequest('PUT', url, body, options);
}

Future<dynamic> delete(
String url,
dynamic body, {
Map<String, dynamic>? body, {
FetchOptions? options,
}) async {
return _handleRequest('DELETE', url, body, options);
Expand Down
45 changes: 36 additions & 9 deletions lib/src/storage_file_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,16 +211,20 @@ class StorageFileApi {
/// [expiresIn] is the number of seconds until the signed URL expire. For
/// example, `60` for a URL which are valid for one minute.
///
/// The signed url is returned.
/// [transform] adds image transformations parameters to the generated url.
Future<String> createSignedUrl(
String path,
int expiresIn,
) async {
int expiresIn, {
TransformOptions? transform,
}) async {
final finalPath = _getFinalPath(path);
final options = FetchOptions(headers: headers);
final response = await storageFetch.post(
'$url/object/sign/$finalPath',
{'expiresIn': expiresIn},
{
'expiresIn': expiresIn,
if (transform != null) 'transform': transform.toQueryParams,
},
options: options,
);
final signedUrlPath = (response as Map<String, dynamic>)['signedURL'];
Expand Down Expand Up @@ -264,22 +268,45 @@ class StorageFileApi {
///
/// [path] is the file path to be downloaded, including the path and file
/// name. For example `download('folder/image.png')`.
Future<Uint8List> download(String path) async {
///
/// [transform] download a transformed variant of the image with the provided options
Future<Uint8List> download(String path, {TransformOptions? transform}) async {
final wantsTransformations = transform != null;
final finalPath = _getFinalPath(path);
final renderPath =
wantsTransformations ? 'render/image/authenticated' : 'object';
final queryParams = transform?.toQueryParams;
final options = FetchOptions(headers: headers, noResolveJson: true);

var fetchUrl = Uri.parse('$url/$renderPath/$finalPath');
fetchUrl = fetchUrl.replace(queryParameters: queryParams);

final response =
await storageFetch.get('$url/object/$finalPath', options: options);
await storageFetch.get(fetchUrl.toString(), options: options);
return response as Uint8List;
}

/// Retrieve URLs for assets in public buckets
///
/// [path] is the file path to be downloaded, including the current file name.
/// For example `getPublicUrl('folder/image.png')`.
String getPublicUrl(String path) {
///
/// [transform] adds image transformations parameters to the generated url.
String getPublicUrl(
String path, {
TransformOptions? transform,
}) {
final finalPath = _getFinalPath(path);
final publicUrl = '$url/object/public/$finalPath';
return publicUrl;

final wantsTransformation = transform != null;
final renderPath = wantsTransformation ? 'render/image' : 'object';
final transformationQuery = transform?.toQueryParams;

var publicUrl = Uri.parse('$url/$renderPath/public/$finalPath');

publicUrl = publicUrl.replace(queryParameters: transformationQuery);

return publicUrl.toString();
}

/// Deletes files within the same bucket
Expand Down
66 changes: 66 additions & 0 deletions lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,69 @@ class StorageRetryController {
_cancelled = true;
}
}

/// {@template resize_mode}
/// Specifies how image cropping should be handled when performing image transformations.
/// {@endtemplate}
enum ResizeMode {
/// Resizes the image while keeping the aspect ratio to fill a given size and crops projecting parts.
cover,

/// Resizes the image while keeping the aspect ratio to fit a given size.
contain,

/// Resizes the image without keeping the aspect ratio to fill a given size.
fill,
}

/// {@template transform_options}
/// Specifies the dimensions and the resize mode of the requesting image.
/// {@endtemplate}
class TransformOptions {
/// Width of the requesting image to be.
final int? width;

/// Height of requesting image to be.
final int? height;

/// {@macro resize_mode}
///
/// [ResizeMode.cover] will be used if no value is specified.
final ResizeMode? resize;

/// {@macro transform_options}
const TransformOptions({
this.width,
this.height,
this.resize,
});
}

extension ToQueryParams on TransformOptions {
Vinzent03 marked this conversation as resolved.
Show resolved Hide resolved
Map<String, String> get toQueryParams {
return {
if (width != null) 'width': '$width',
if (height != null) 'height': '$height',
if (resize != null) 'resize': resize!.snakeCase,
};
}
}

extension ToSnakeCase on Enum {
String get snakeCase {
final a = 'a'.codeUnitAt(0), z = 'z'.codeUnitAt(0);
final A = 'A'.codeUnitAt(0), Z = 'Z'.codeUnitAt(0);
final result = StringBuffer()..write(name[0].toLowerCase());
for (var i = 1; i < name.length; i++) {
final char = name.codeUnitAt(i);
if (A <= char && char <= Z) {
final pChar = name.codeUnitAt(i - 1);
if (a <= pChar && pChar <= z) {
result.write('_');
}
}
result.write(name[i].toLowerCase());
}
return result.toString();
}
}
2 changes: 1 addition & 1 deletion lib/src/version.dart
Original file line number Diff line number Diff line change
@@ -1 +1 @@
const version = '1.1.0';
const version = '1.2.0';
2 changes: 1 addition & 1 deletion lib/storage_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ library storage_client;

export 'src/storage_client.dart';
export 'src/storage_file_api.dart';
export 'src/types.dart' hide FetchOptions;
export 'src/types.dart' hide FetchOptions, ToSnakeCase, ToQueryParams;
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: storage_client
description: Dart client library to interact with Supabase Storage.
version: 1.1.0
version: 1.2.0
homepage: "https://supabase.io"
repository: "https://github.com/supabase/storage-dart"

Expand All @@ -18,3 +18,4 @@ dev_dependencies:
mocktail: ^0.3.0
test: ^1.21.4
lints: ^1.0.1
path: ^1.8.2
6 changes: 3 additions & 3 deletions test/basic_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,13 @@ void main() {
expect(response.length, 2);
});

test('should download file', () async {
test('should download public file', () async {
final file = File('a.txt');
file.writeAsStringSync('Updated content');

when(
() => storageFetch.get(
'$objectUrl/public/b.txt',
'$objectUrl/public_bucket/b.txt',
options: mockFetchOptions,
),
).thenAnswer(
Expand All @@ -268,7 +268,7 @@ void main() {
),
);

final response = await client.from('public').download('b.txt');
final response = await client.from('public_bucket').download('b.txt');
expect(response, isA<Uint8List>());
expect(String.fromCharCodes(response), 'Updated content');
});
Expand Down
Loading