supabase / supabase-flutter

Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products.
https://supabase.com/
MIT License
703 stars 167 forks source link

No destinationBucket support in storage copy? #1034

Open RockStone opened 4 days ago

RockStone commented 4 days ago

Is your feature request related to a problem? Please describe.

Yes, the current storage_client package (version 2.0.3) does not support copying files between different buckets. While the supabase-js library includes an option to specify a destinationBucket when copying a file, the Flutter version only allows copying files within the same bucket. This inconsistency between the platforms leads to frustration when working with Supabase storage in Flutter, especially when there is a need to move files across buckets. It becomes inconvenient to download the file from one bucket and re-upload it to another, which introduces unnecessary complexity and performance issues.

For example, the Flutter SDK code in storage_client-2.0.3/lib/src/storage_file_api.dart currently looks like this:

Future<String> copy(String fromPath, String toPath) async {
  final options = FetchOptions(headers: headers);
  final response = await _storageFetch.post(
    '$url/object/copy',
    {
      'bucketId': bucketId,
      'sourceKey': fromPath,
      'destinationKey': toPath,
    },
    options: options,
  );
  return (response as Map<String, dynamic>)['Key'] as String;
}

Whereas supabase-js supports copying across buckets, as seen here:

async copy(
  fromPath: string,
  toPath: string,
  options?: DestinationOptions
): Promise<
  | {
      data: { path: string }
      error: null
    }
  | {
      data: null
      error: StorageError
    }
> {
  const data = await post(
    this.fetch,
    `${this.url}/object/copy`,
    {
      bucketId: this.bucketId,
      sourceKey: fromPath,
      destinationKey: toPath,
      destinationBucket: options?.destinationBucket,
    },
    { headers: this.headers }
  );
  return { data: { path: data.Key }, error: null };
}

This inconsistency severely limits the functionality of the Flutter SDK when compared to other platforms, and it results in a fragmented experience across different languages (such as TypeScript and Swift) for developers working with Supabase storage.

Describe the solution you'd like

I would like the supabase-flutter package to support the destinationBucket parameter in the copy method, just as it is supported in supabase-js and supabase-swift. This would enable developers to copy files across different buckets within Supabase storage without the need for workarounds like downloading and re-uploading files.

The new copy method signature in the Flutter package should look like this:

Future<String> copy(String fromPath, String toPath, {String? destinationBucket}) async {
  final options = FetchOptions(headers: headers);
  final response = await _storageFetch.post(
    '$url/object/copy',
    {
      'bucketId': bucketId,
      'sourceKey': fromPath,
      'destinationKey': toPath,
      'destinationBucket': destinationBucket,
    },
    options: options,
  );
  return (response as Map<String, dynamic>)['Key'] as String;
}

This will provide a consistent and unified experience across different SDKs, and make file management more efficient and straightforward in Flutter.

Describe alternatives you've considered

The alternative I have considered involves manually downloading the file from the source bucket and then uploading it to the destination bucket, followed by potentially deleting the original file. While this workaround is technically possible, it is cumbersome and inefficient. It increases bandwidth usage, introduces additional code complexity, and results in poor performance for large files or when performing many file operations.

Here’s an example of the workaround:

Future<void> copyFileBetweenBuckets(String sourceBucket, String sourcePath, String destinationBucket, String destinationPath) async {
  try {
    // Download the file from the source bucket
    final response = await Supabase.instance.client.storage.from(sourceBucket).download(sourcePath);
    if (response == null) {
      throw Exception('Failed to download file from $sourceBucket/$sourcePath');
    }

    // Upload the file to the destination bucket
    await Supabase.instance.client.storage.from(destinationBucket).upload(destinationPath, response);

    debugPrint('File successfully copied to $destinationBucket/$destinationPath');
  } catch (e) {
    debugPrint('Error during file copy: $e');
  }
}

This alternative is not ideal as it is error-prone and involves additional steps that should be handled internally by the SDK.

Additional context

public func copy(
  from source: String,
  to destination: String,
  options: DestinationOptions? = nil
) async throws -> String {
  struct UploadResponse: Decodable {
    let Key: String
  }

  return try await execute(
    HTTPRequest(
      url: configuration.url.appendingPathComponent("object/copy"),
      method: .post,
      body: configuration.encoder.encode(
        [
          "bucketId": bucketId,
          "sourceKey": source,
          "destinationKey": destination,
          "destinationBucket": options?.destinationBucket,
        ]
      )
    )
  )
  .decoded(as: UploadResponse.self, decoder: configuration.decoder)
  .Key
}

It would greatly benefit the Flutter community to have this feature available as soon as possible to align with the functionality provided by the other SDKs.

RockStone commented 2 days ago

@Vinzent03 thanks for taking care of this. will we have this on the next release? any ETA?