diff --git a/packages/amplify_core/lib/src/types/storage/remove_many_options.dart b/packages/amplify_core/lib/src/types/storage/remove_many_options.dart index d9e32131fe..05db2da027 100644 --- a/packages/amplify_core/lib/src/types/storage/remove_many_options.dart +++ b/packages/amplify_core/lib/src/types/storage/remove_many_options.dart @@ -1,7 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:aws_common/aws_common.dart'; +import 'package:amplify_core/amplify_core.dart'; /// {@template amplify_core.storage.remove_many_options} /// Configurable options for `Amplify.Storage.removeMany`. @@ -14,13 +14,20 @@ class StorageRemoveManyOptions /// {@macro amplify_core.storage.remove_many_options} const StorageRemoveManyOptions({ this.pluginOptions, + this.bucket, }); /// {@macro amplify_core.storage.remove_many_plugin_options} final StorageRemoveManyPluginOptions? pluginOptions; + /// Optionally specify which bucket to target + final StorageBucket? bucket; + @override - List get props => [pluginOptions]; + List get props => [ + pluginOptions, + bucket, + ]; @override String get runtimeTypeName => 'StorageRemoveManyOptions'; @@ -28,6 +35,7 @@ class StorageRemoveManyOptions @override Map toJson() => { 'pluginOptions': pluginOptions?.toJson(), + 'bucket': bucket, }; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/remove_many_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/remove_many_test.dart index 87c5ff2e3b..435ed1d986 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/remove_many_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/remove_many_test.dart @@ -90,6 +90,127 @@ void main() { }); }); + group('Multi-bucket', () { + final mainBucket = StorageBucket.fromOutputs( + 'Storage Integ Test main bucket', + ); + final secondaryBucket = StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket', + ); + final path1 = 'public/multi-bucket-remove-many-${uuid()}'; + final path2 = 'public/multi-bucket-remove-many-${uuid()}'; + final storagePath1 = StoragePath.fromString(path1); + final storagePath2 = StoragePath.fromString(path2); + setUp(() async { + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes('data'.codeUnits), + path: storagePath1, + options: StorageUploadDataOptions( + bucket: mainBucket, + ), + ).result; + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes('data'.codeUnits), + path: storagePath2, + options: StorageUploadDataOptions( + bucket: mainBucket, + ), + ).result; + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes('data'.codeUnits), + path: storagePath1, + options: StorageUploadDataOptions( + bucket: secondaryBucket, + ), + ).result; + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes('data'.codeUnits), + path: storagePath2, + options: StorageUploadDataOptions( + bucket: secondaryBucket, + ), + ).result; + }); + + testWidgets('removes objects from main bucket', (_) async { + expect( + await objectExists( + storagePath1, + bucket: mainBucket, + ), + true, + ); + expect( + await objectExists( + storagePath2, + bucket: mainBucket, + ), + true, + ); + final result = await Amplify.Storage.removeMany( + paths: [storagePath1, storagePath2], + options: StorageRemoveManyOptions( + bucket: mainBucket, + ), + ).result; + expect( + await objectExists( + storagePath1, + bucket: mainBucket, + ), + false, + ); + expect( + await objectExists( + storagePath2, + bucket: mainBucket, + ), + false, + ); + final removedPaths = result.removedItems.map((i) => i.path).toList(); + expect(removedPaths, unorderedEquals([path1, path2])); + }); + + testWidgets('removes objects from secondary bucket', (_) async { + expect( + await objectExists( + storagePath1, + bucket: secondaryBucket, + ), + true, + ); + expect( + await objectExists( + storagePath2, + bucket: secondaryBucket, + ), + true, + ); + final result = await Amplify.Storage.removeMany( + paths: [storagePath1, storagePath2], + options: StorageRemoveManyOptions( + bucket: secondaryBucket, + ), + ).result; + expect( + await objectExists( + storagePath1, + bucket: secondaryBucket, + ), + false, + ); + expect( + await objectExists( + storagePath2, + bucket: secondaryBucket, + ), + false, + ); + final removedPaths = result.removedItems.map((i) => i.path).toList(); + expect(removedPaths, unorderedEquals([path1, path2])); + }); + }); + testWidgets('unauthorized path', (_) async { final result = await Amplify.Storage.removeMany( paths: [const StoragePath.fromString('unauthorized/path')], diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart b/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart index 5512a934e2..a1f40fb4b2 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart @@ -416,6 +416,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface final s3Options = StorageRemoveManyOptions( pluginOptions: s3PluginOptions, + bucket: options?.bucket, ); return S3RemoveManyOperation( diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart index 9140551535..db5ca5069a 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart @@ -490,6 +490,8 @@ class StorageS3Service { final objectIdentifiersToRemove = resolvedPaths.map((path) => s3.ObjectIdentifier(key: path)).toList(); + final s3ClientInfo = getS3ClientInfo(storageBucket: options.bucket); + final removedItems = []; final removedErrors = []; @@ -502,7 +504,7 @@ class StorageS3Service { ); final request = s3.DeleteObjectsRequest.build((builder) { builder - ..bucket = _storageOutputs.bucketName + ..bucket = s3ClientInfo.bucketName // force to use sha256 instead of md5 ..checksumAlgorithm = s3.ChecksumAlgorithm.sha256 ..delete = s3.Delete.build((builder) { @@ -510,7 +512,7 @@ class StorageS3Service { }).toBuilder(); }); try { - final output = await _defaultS3Client.deleteObjects(request).result; + final output = await s3ClientInfo.client.deleteObjects(request).result; removedItems.addAll( output.deleted?.toList().map( (removedObject) => S3Item.fromS3Object( diff --git a/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart b/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart index 5d01e5736c..92a2e5a0d0 100644 --- a/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart @@ -1115,6 +1115,9 @@ void main() { () async { const testOptions = StorageRemoveManyOptions( pluginOptions: S3RemoveManyPluginOptions(), + bucket: StorageBucket.fromBucketInfo( + BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2'), + ), ); when(