From 13d67230bef1eb0f4820a12f0333ce6f36981725 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Tue, 17 Sep 2024 10:35:48 -0700 Subject: [PATCH 01/42] updating storage_outputs class for multi-bucket support --- .../storage/storage_outputs.dart | 5 +++- .../storage/storage_outputs.g.dart | 25 +++++++++++++++---- .../config/amplify_outputs/test_data.dart | 23 ++++++++++++++--- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart index 9db9356aa6..416a658cda 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart @@ -12,7 +12,7 @@ part 'storage_outputs.g.dart'; class StorageOutputs with AWSEquatable, AWSSerializable, AWSDebuggable { /// {@macro amplify_core.amplify_outputs.storage_outputs} - const StorageOutputs({required this.awsRegion, required this.bucketName}); + const StorageOutputs({required this.awsRegion, required this.bucketName, this.buckets}); factory StorageOutputs.fromJson(Map json) => _$StorageOutputsFromJson(json); @@ -23,6 +23,9 @@ class StorageOutputs /// The Amazon S3 bucket name. final String bucketName; + /// The list of buckets if there are multiple buckets for the project + final List>? buckets; + @override List get props => [awsRegion, bucketName]; diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart index 7b90421189..8b77e74549 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart @@ -16,6 +16,11 @@ StorageOutputs _$StorageOutputsFromJson(Map json) => final val = StorageOutputs( awsRegion: $checkedConvert('aws_region', (v) => v as String), bucketName: $checkedConvert('bucket_name', (v) => v as String), + buckets: $checkedConvert( + 'buckets', + (v) => (v as List?) + ?.map((e) => Map.from(e as Map)) + .toList()), ); return val; }, @@ -25,8 +30,18 @@ StorageOutputs _$StorageOutputsFromJson(Map json) => }, ); -Map _$StorageOutputsToJson(StorageOutputs instance) => - { - 'aws_region': instance.awsRegion, - 'bucket_name': instance.bucketName, - }; +Map _$StorageOutputsToJson(StorageOutputs instance) { + final val = { + 'aws_region': instance.awsRegion, + 'bucket_name': instance.bucketName, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('buckets', instance.buckets); + return val; +} diff --git a/packages/amplify_core/test/config/amplify_outputs/test_data.dart b/packages/amplify_core/test/config/amplify_outputs/test_data.dart index 317e7a78b4..4a1f5e7b87 100644 --- a/packages/amplify_core/test/config/amplify_outputs/test_data.dart +++ b/packages/amplify_core/test/config/amplify_outputs/test_data.dart @@ -99,9 +99,26 @@ const amplifyoutputs = '''{ ] }, "storage": { - "aws_region": "oem dks", - "bucket_name": "dolor et esse" - }, + "aws_region": "us-east-2", + "bucket_name": "amplifyTeamDrive-one-stora-testbucketgen2bucket0b8c-9ggcfqfunkjr", + "buckets": [ + { + "name": "amplifyTeamDrive-one", + "bucket_name": "amplifyTeamDrive-one-stora-testbucketgen2bucket0b8c-9ggcfqfunkjr", + "aws_region": "us-east-2" + }, + { + "name": "amplifyTeamDrive-two", + "bucket_name": "amplifyTeamDrive-two-stora-testbucketgen2bucket0b8c-2", + "aws_region": "us-east-2" + }, + { + "name": "amplifyTeamDrive-three", + "bucket_name": "amplifyTeamDrive-three-stora-testbucketgen2bucket0b8c-3", + "aws_region": "us-east-2" + } + ] +}, "version": "1" } '''; From c5c0438ad5957f82704642982357b1f3f13d70a4 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Tue, 17 Sep 2024 14:50:21 -0700 Subject: [PATCH 02/42] seperated bucket out into its own class instead of using a map --- .../storage/storage_output_bucket.dart | 16 ++++++++++++++++ .../amplify_outputs/storage/storage_outputs.dart | 8 +++++--- .../storage/storage_outputs.g.dart | 5 +++-- 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart new file mode 100644 index 0000000000..66cc099dbc --- /dev/null +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + + + +/// {@template amplify_core.amplify_outputs.amazon_pinpoint_channel} +/// Supported channels for Amazon Pinpoint. +/// {@endtemplate} +class StorageOutputBucket { + StorageOutputBucket(this.name, this.bucketName, this.awsRegion); + factory StorageOutputBucket.fromJson(Map json) => StorageOutputBucket(json['name'].toString(), json['bucket_name'].toString(), json['aws_region'].toString()); + String name; + String bucketName; + String awsRegion; + Map toJson() => {'name':name, 'bucket_name':bucketName, 'aws_region':awsRegion}; +} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart index 416a658cda..f6744287ac 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/config/amplify_outputs/storage/storage_output_bucket.dart'; part 'storage_outputs.g.dart'; @@ -14,6 +15,7 @@ class StorageOutputs /// {@macro amplify_core.amplify_outputs.storage_outputs} const StorageOutputs({required this.awsRegion, required this.bucketName, this.buckets}); + factory StorageOutputs.fromJson(Map json) => _$StorageOutputsFromJson(json); @@ -24,10 +26,10 @@ class StorageOutputs final String bucketName; /// The list of buckets if there are multiple buckets for the project - final List>? buckets; - + final List? buckets; + @override - List get props => [awsRegion, bucketName]; + List get props => [awsRegion, bucketName, buckets]; @override String get runtimeTypeName => 'StorageOutputs'; diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart index 8b77e74549..2fb3499954 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart @@ -19,7 +19,8 @@ StorageOutputs _$StorageOutputsFromJson(Map json) => buckets: $checkedConvert( 'buckets', (v) => (v as List?) - ?.map((e) => Map.from(e as Map)) + ?.map((e) => + StorageOutputBucket.fromJson(e as Map)) .toList()), ); return val; @@ -42,6 +43,6 @@ Map _$StorageOutputsToJson(StorageOutputs instance) { } } - writeNotNull('buckets', instance.buckets); + writeNotNull('buckets', instance.buckets?.map((e) => e.toJson()).toList()); return val; } From f22dd48de75dd06877fa2c6df79567e2de83909b Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 18 Sep 2024 11:31:25 -0700 Subject: [PATCH 03/42] made bucket_output class a proper output class like the others in amplify-flutter --- packages/amplify_core/doc/lib/auth.dart | 6 +-- .../storage/bucket_output.dart | 37 +++++++++++++++++++ .../storage/bucket_output.g.dart | 34 +++++++++++++++++ .../storage/storage_output_bucket.dart | 16 -------- .../storage/storage_outputs.dart | 4 +- .../storage/storage_outputs.g.dart | 3 +- 6 files changed, 77 insertions(+), 23 deletions(-) create mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart create mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart delete mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart diff --git a/packages/amplify_core/doc/lib/auth.dart b/packages/amplify_core/doc/lib/auth.dart index 6e48014a9b..7572dc0855 100644 --- a/packages/amplify_core/doc/lib/auth.dart +++ b/packages/amplify_core/doc/lib/auth.dart @@ -110,13 +110,13 @@ Future _handleSignInResult(SignInResult result) async { // #enddocregion handle-signin, handle-confirm-signin-sms, handle-confirm-signin-new-password, handle-confirm-signin-custom-challenge, handle-confirm-signin-reset-password, handle-confirm-signin-confirm-signup, handle-confirm-signin-done, handle-confirm-signin-mfa-selection, handle-confirm-signin-totp-setup, handle-confirm-signin-totp-code // #docregion handle-confirm-signin-mfa-selection case AuthSignInStep.continueSignInWithMfaSelection: - final allowedMfaTypes = result.nextStep.allowedMfaTypes!; + final allowedMfaTypes = result.nextStep.allowedMfaTypes; final selection = await _promptUserPreference(allowedMfaTypes); return _handleMfaSelection(selection); // #enddocregion handle-confirm-signin-mfa-selection // #docregion handle-confirm-signin-totp-setup case AuthSignInStep.continueSignInWithTotpSetup: - final totpSetupDetails = result.nextStep.totpSetupDetails!; + final totpSetupDetails = result.nextStep.totpSetupDetails; final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); safePrint('Open URI to complete setup: $setupUri'); // #enddocregion handle-confirm-signin-totp-setup @@ -255,7 +255,7 @@ Future signOutGlobally() async { if (result is CognitoCompleteSignOut) { safePrint('Sign out completed successfully'); } else if (result is CognitoPartialSignOut) { - final globalSignOutException = result.globalSignOutException!; + final globalSignOutException = result.globalSignOutException; final accessToken = globalSignOutException.accessToken; // Retry the global sign out using the access token, if desired // ... diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart new file mode 100644 index 0000000000..9c09884ee3 --- /dev/null +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart @@ -0,0 +1,37 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; + +part 'bucket_output.g.dart'; + +@zAmplifyOutputsSerializable +class BucketOutput + with AWSEquatable, AWSSerializable, AWSDebuggable{ + + const BucketOutput({required this.name, required this.bucketName, required this.awsRegion}); + + factory BucketOutput.fromJson(Map json) => + _$BucketOutputFromJson(json); + + final String name; + + final String bucketName; + + final String awsRegion; + + @override + List get props => [ + name, + bucketName, + awsRegion, + ]; + + @override + String get runtimeTypeName => 'BucketOutput'; + + @override + Object? toJson() { + return _$BucketOutputToJson(this); + } +} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart new file mode 100644 index 0000000000..331517e83e --- /dev/null +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: deprecated_member_use_from_same_package + +part of 'bucket_output.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +BucketOutput _$BucketOutputFromJson(Map json) => + $checkedCreate( + 'BucketOutput', + json, + ($checkedConvert) { + final val = BucketOutput( + name: $checkedConvert('name', (v) => v as String), + bucketName: $checkedConvert('bucket_name', (v) => v as String), + awsRegion: $checkedConvert('aws_region', (v) => v as String), + ); + return val; + }, + fieldKeyMap: const { + 'bucketName': 'bucket_name', + 'awsRegion': 'aws_region' + }, + ); + +Map _$BucketOutputToJson(BucketOutput instance) => + { + 'name': instance.name, + 'bucket_name': instance.bucketName, + 'aws_region': instance.awsRegion, + }; diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart deleted file mode 100644 index 66cc099dbc..0000000000 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - - - -/// {@template amplify_core.amplify_outputs.amazon_pinpoint_channel} -/// Supported channels for Amazon Pinpoint. -/// {@endtemplate} -class StorageOutputBucket { - StorageOutputBucket(this.name, this.bucketName, this.awsRegion); - factory StorageOutputBucket.fromJson(Map json) => StorageOutputBucket(json['name'].toString(), json['bucket_name'].toString(), json['aws_region'].toString()); - String name; - String bucketName; - String awsRegion; - Map toJson() => {'name':name, 'bucket_name':bucketName, 'aws_region':awsRegion}; -} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart index f6744287ac..9a0fc949de 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_core/amplify_core.dart'; -import 'package:amplify_core/src/config/amplify_outputs/storage/storage_output_bucket.dart'; +import 'package:amplify_core/src/config/amplify_outputs/storage/bucket_output.dart'; part 'storage_outputs.g.dart'; @@ -26,7 +26,7 @@ class StorageOutputs final String bucketName; /// The list of buckets if there are multiple buckets for the project - final List? buckets; + final List? buckets; @override List get props => [awsRegion, bucketName, buckets]; diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart index 2fb3499954..9dac1f7c12 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart @@ -19,8 +19,7 @@ StorageOutputs _$StorageOutputsFromJson(Map json) => buckets: $checkedConvert( 'buckets', (v) => (v as List?) - ?.map((e) => - StorageOutputBucket.fromJson(e as Map)) + ?.map((e) => BucketOutput.fromJson(e as Map)) .toList()), ); return val; From cc6c55bb3d694e6ed42d9482f7ece27df19e87d3 Mon Sep 17 00:00:00 2001 From: ekjotmultani <43255916+ekjotmultani@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:42:38 -0700 Subject: [PATCH 04/42] Update auth.dart --- packages/amplify_core/doc/lib/auth.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/amplify_core/doc/lib/auth.dart b/packages/amplify_core/doc/lib/auth.dart index 7572dc0855..6e48014a9b 100644 --- a/packages/amplify_core/doc/lib/auth.dart +++ b/packages/amplify_core/doc/lib/auth.dart @@ -110,13 +110,13 @@ Future _handleSignInResult(SignInResult result) async { // #enddocregion handle-signin, handle-confirm-signin-sms, handle-confirm-signin-new-password, handle-confirm-signin-custom-challenge, handle-confirm-signin-reset-password, handle-confirm-signin-confirm-signup, handle-confirm-signin-done, handle-confirm-signin-mfa-selection, handle-confirm-signin-totp-setup, handle-confirm-signin-totp-code // #docregion handle-confirm-signin-mfa-selection case AuthSignInStep.continueSignInWithMfaSelection: - final allowedMfaTypes = result.nextStep.allowedMfaTypes; + final allowedMfaTypes = result.nextStep.allowedMfaTypes!; final selection = await _promptUserPreference(allowedMfaTypes); return _handleMfaSelection(selection); // #enddocregion handle-confirm-signin-mfa-selection // #docregion handle-confirm-signin-totp-setup case AuthSignInStep.continueSignInWithTotpSetup: - final totpSetupDetails = result.nextStep.totpSetupDetails; + final totpSetupDetails = result.nextStep.totpSetupDetails!; final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); safePrint('Open URI to complete setup: $setupUri'); // #enddocregion handle-confirm-signin-totp-setup @@ -255,7 +255,7 @@ Future signOutGlobally() async { if (result is CognitoCompleteSignOut) { safePrint('Sign out completed successfully'); } else if (result is CognitoPartialSignOut) { - final globalSignOutException = result.globalSignOutException; + final globalSignOutException = result.globalSignOutException!; final accessToken = globalSignOutException.accessToken; // Retry the global sign out using the access token, if desired // ... From b6f8719c8fc430232ab925e85d8b5a4056ece519 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 18 Sep 2024 12:19:27 -0700 Subject: [PATCH 05/42] added doc comments and changed name of bucket class --- devtools_options.yaml | 3 ++ .../storage/bucket_output.dart | 37 ----------------- .../storage/bucket_outputs.dart | 41 +++++++++++++++++++ ...et_output.g.dart => bucket_outputs.g.dart} | 10 ++--- .../storage/storage_outputs.dart | 4 +- .../storage/storage_outputs.g.dart | 3 +- 6 files changed, 53 insertions(+), 45 deletions(-) create mode 100644 devtools_options.yaml delete mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart create mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.dart rename packages/amplify_core/lib/src/config/amplify_outputs/storage/{bucket_output.g.dart => bucket_outputs.g.dart} (79%) diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart deleted file mode 100644 index 9c09884ee3..0000000000 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; - -part 'bucket_output.g.dart'; - -@zAmplifyOutputsSerializable -class BucketOutput - with AWSEquatable, AWSSerializable, AWSDebuggable{ - - const BucketOutput({required this.name, required this.bucketName, required this.awsRegion}); - - factory BucketOutput.fromJson(Map json) => - _$BucketOutputFromJson(json); - - final String name; - - final String bucketName; - - final String awsRegion; - - @override - List get props => [ - name, - bucketName, - awsRegion, - ]; - - @override - String get runtimeTypeName => 'BucketOutput'; - - @override - Object? toJson() { - return _$BucketOutputToJson(this); - } -} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.dart new file mode 100644 index 0000000000..c3e05b87ec --- /dev/null +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.dart @@ -0,0 +1,41 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; + +part 'bucket_outputs.g.dart'; + +/// {@template amplify_core.amplify_outputs.bucket_outputs} +/// The Amplify Gen 2 outputs for Buckets in the Storage category. +/// {@endtemplate} +@zAmplifyOutputsSerializable +class BucketOutputs + with AWSEquatable, AWSSerializable, AWSDebuggable{ + /// {@macro amplify_core.amplify_outputs.bucket_outputs} + const BucketOutputs({required this.name, required this.bucketName, required this.awsRegion,}); + + factory BucketOutputs.fromJson(Map json) => + _$BucketOutputsFromJson(json); + + /// The user friendly name of the bucket + final String name; + /// The Amazon S3 bucket name. + final String bucketName; + /// The AWS region of Amazon S3 resources. + final String awsRegion; + + @override + List get props => [ + name, + bucketName, + awsRegion, + ]; + + @override + String get runtimeTypeName => 'BucketOutputs'; + + @override + Object? toJson() { + return _$BucketOutputsToJson(this); + } +} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.g.dart similarity index 79% rename from packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart rename to packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.g.dart index 331517e83e..0d60c85fcc 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.g.dart @@ -2,18 +2,18 @@ // ignore_for_file: deprecated_member_use_from_same_package -part of 'bucket_output.dart'; +part of 'bucket_outputs.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -BucketOutput _$BucketOutputFromJson(Map json) => +BucketOutputs _$BucketOutputsFromJson(Map json) => $checkedCreate( - 'BucketOutput', + 'BucketOutputs', json, ($checkedConvert) { - final val = BucketOutput( + final val = BucketOutputs( name: $checkedConvert('name', (v) => v as String), bucketName: $checkedConvert('bucket_name', (v) => v as String), awsRegion: $checkedConvert('aws_region', (v) => v as String), @@ -26,7 +26,7 @@ BucketOutput _$BucketOutputFromJson(Map json) => }, ); -Map _$BucketOutputToJson(BucketOutput instance) => +Map _$BucketOutputsToJson(BucketOutputs instance) => { 'name': instance.name, 'bucket_name': instance.bucketName, diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart index 9a0fc949de..87f34ad570 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_core/amplify_core.dart'; -import 'package:amplify_core/src/config/amplify_outputs/storage/bucket_output.dart'; +import 'package:amplify_core/src/config/amplify_outputs/storage/bucket_outputs.dart'; part 'storage_outputs.g.dart'; @@ -26,7 +26,7 @@ class StorageOutputs final String bucketName; /// The list of buckets if there are multiple buckets for the project - final List? buckets; + final List? buckets; @override List get props => [awsRegion, bucketName, buckets]; diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart index 9dac1f7c12..40d147f387 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.g.dart @@ -19,7 +19,8 @@ StorageOutputs _$StorageOutputsFromJson(Map json) => buckets: $checkedConvert( 'buckets', (v) => (v as List?) - ?.map((e) => BucketOutput.fromJson(e as Map)) + ?.map( + (e) => BucketOutputs.fromJson(e as Map)) .toList()), ); return val; From 3fc0ce9580911ce58601087599e899ff89dc617c Mon Sep 17 00:00:00 2001 From: ekjotmultani <43255916+ekjotmultani@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:30:21 -0700 Subject: [PATCH 06/42] Delete devtools_options.yaml --- devtools_options.yaml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml deleted file mode 100644 index fa0b357c4f..0000000000 --- a/devtools_options.yaml +++ /dev/null @@ -1,3 +0,0 @@ -description: This file stores settings for Dart & Flutter DevTools. -documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states -extensions: From 9114206e5c7f153e4fc2f477db553aa06fa9b0d8 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 18 Sep 2024 13:40:33 -0700 Subject: [PATCH 07/42] added trailing commas to pass ci test --- .../lib/src/config/amplify_outputs/storage/storage_outputs.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart index 87f34ad570..10eac4b055 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart @@ -13,7 +13,7 @@ part 'storage_outputs.g.dart'; class StorageOutputs with AWSEquatable, AWSSerializable, AWSDebuggable { /// {@macro amplify_core.amplify_outputs.storage_outputs} - const StorageOutputs({required this.awsRegion, required this.bucketName, this.buckets}); + const StorageOutputs({required this.awsRegion, required this.bucketName, this.buckets,}); factory StorageOutputs.fromJson(Map json) => From e7906c727c58d07b1b7f6ac0ddabae39dd9dd0a5 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 18 Sep 2024 14:25:35 -0700 Subject: [PATCH 08/42] ran dart format on two failing files --- .../amplify_outputs/storage/bucket_outputs.dart | 14 ++++++++++---- .../amplify_outputs/storage/storage_outputs.dart | 7 +++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.dart index c3e05b87ec..e156f08567 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_outputs.dart @@ -9,18 +9,24 @@ part 'bucket_outputs.g.dart'; /// The Amplify Gen 2 outputs for Buckets in the Storage category. /// {@endtemplate} @zAmplifyOutputsSerializable -class BucketOutputs - with AWSEquatable, AWSSerializable, AWSDebuggable{ +class BucketOutputs + with AWSEquatable, AWSSerializable, AWSDebuggable { /// {@macro amplify_core.amplify_outputs.bucket_outputs} - const BucketOutputs({required this.name, required this.bucketName, required this.awsRegion,}); + const BucketOutputs({ + required this.name, + required this.bucketName, + required this.awsRegion, + }); factory BucketOutputs.fromJson(Map json) => _$BucketOutputsFromJson(json); /// The user friendly name of the bucket final String name; + /// The Amazon S3 bucket name. final String bucketName; + /// The AWS region of Amazon S3 resources. final String awsRegion; @@ -33,7 +39,7 @@ class BucketOutputs @override String get runtimeTypeName => 'BucketOutputs'; - + @override Object? toJson() { return _$BucketOutputsToJson(this); diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart index 10eac4b055..fec0ec0662 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart @@ -13,8 +13,11 @@ part 'storage_outputs.g.dart'; class StorageOutputs with AWSEquatable, AWSSerializable, AWSDebuggable { /// {@macro amplify_core.amplify_outputs.storage_outputs} - const StorageOutputs({required this.awsRegion, required this.bucketName, this.buckets,}); - + const StorageOutputs({ + required this.awsRegion, + required this.bucketName, + this.buckets, + }); factory StorageOutputs.fromJson(Map json) => _$StorageOutputsFromJson(json); From 6055a8024c7b50a4a74e7096da9f606627a5835e Mon Sep 17 00:00:00 2001 From: NikaHsn Date: Fri, 20 Sep 2024 12:57:15 -0700 Subject: [PATCH 09/42] feat(core): add storage bucket type (#5478) --- .../types/exception/amplify_exception.dart | 1 + .../invalid_storage_bucket_exception.dart | 15 +++++++++ .../lib/src/types/storage/bucket_info.dart | 9 ++++++ .../lib/src/types/storage/storage_bucket.dart | 19 ++++++++++++ .../storage/storage_bucket_from_outputs.dart | 31 +++++++++++++++++++ .../lib/src/types/storage/storage_types.dart | 2 ++ 6 files changed, 77 insertions(+) create mode 100644 packages/amplify_core/lib/src/types/exception/storage/invalid_storage_bucket_exception.dart create mode 100644 packages/amplify_core/lib/src/types/storage/bucket_info.dart create mode 100644 packages/amplify_core/lib/src/types/storage/storage_bucket.dart create mode 100644 packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart diff --git a/packages/amplify_core/lib/src/types/exception/amplify_exception.dart b/packages/amplify_core/lib/src/types/exception/amplify_exception.dart index aedc1c1e19..a60f435bd6 100644 --- a/packages/amplify_core/lib/src/types/exception/amplify_exception.dart +++ b/packages/amplify_core/lib/src/types/exception/amplify_exception.dart @@ -21,6 +21,7 @@ part 'network_exception.dart'; part 'push/push_notification_exception.dart'; part 'storage/access_denied_exception.dart'; part 'storage/http_status_exception.dart'; +part 'storage/invalid_storage_bucket_exception.dart'; part 'storage/local_file_not_found_exception.dart'; part 'storage/not_found_exception.dart'; part 'storage/operation_canceled_exception.dart'; diff --git a/packages/amplify_core/lib/src/types/exception/storage/invalid_storage_bucket_exception.dart b/packages/amplify_core/lib/src/types/exception/storage/invalid_storage_bucket_exception.dart new file mode 100644 index 0000000000..5e5c56fe0e --- /dev/null +++ b/packages/amplify_core/lib/src/types/exception/storage/invalid_storage_bucket_exception.dart @@ -0,0 +1,15 @@ +part of '../amplify_exception.dart'; + +/// {@template amplify_core.storage.invalid_storage_bucket_exception} +/// Exception thrown when the [StorageBucket] is invalid. +/// {@endtemplate} +class InvalidStorageBucketException extends StorageException { + const InvalidStorageBucketException( + super.message, { + super.recoverySuggestion, + super.underlyingException, + }); + + @override + String get runtimeTypeName => 'InvalidStorageBucketException'; +} diff --git a/packages/amplify_core/lib/src/types/storage/bucket_info.dart b/packages/amplify_core/lib/src/types/storage/bucket_info.dart new file mode 100644 index 0000000000..a3c065ef23 --- /dev/null +++ b/packages/amplify_core/lib/src/types/storage/bucket_info.dart @@ -0,0 +1,9 @@ +/// {@template amplify_core.storage.bucket_info} +/// Presents a storage bucket information. +/// {@endtemplate} +class BucketInfo { + /// {@macro amplify_core.storage.bucket_info} + const BucketInfo({required this.bucketName, required this.region}); + final String bucketName; + final String region; +} diff --git a/packages/amplify_core/lib/src/types/storage/storage_bucket.dart b/packages/amplify_core/lib/src/types/storage/storage_bucket.dart new file mode 100644 index 0000000000..711e30d18b --- /dev/null +++ b/packages/amplify_core/lib/src/types/storage/storage_bucket.dart @@ -0,0 +1,19 @@ +import 'package:amplify_core/src/config/amplify_outputs/storage/storage_outputs.dart'; +import 'package:amplify_core/src/types/storage/bucket_info.dart'; +import 'package:amplify_core/src/types/storage/storage_bucket_from_outputs.dart'; +import 'package:meta/meta.dart'; + +/// Presents a storage bucket. +class StorageBucket { + /// Creates a [StorageBucket] from [BucketInfo]. + const StorageBucket.fromBucketInfo(this._info); + + /// Creates a [StorageBucket] defined by the [name] in AmplifyOutputs file. + factory StorageBucket.fromOutputs(String name) => + StorageBucketFromOutputs(name); + + final BucketInfo _info; + + @internal + BucketInfo resolveBucketInfo(StorageOutputs? storageOutputs) => _info; +} diff --git a/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart b/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart new file mode 100644 index 0000000000..4ee6cedb83 --- /dev/null +++ b/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart @@ -0,0 +1,31 @@ +import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/config/amplify_outputs/storage/storage_outputs.dart'; +import 'package:meta/meta.dart'; + +/// {@template amplify_core.storage.storage_bucket_from_outputs} +/// Creates a [StorageBucket] defined by the name in AmplifyOutputs file. +/// {@endtemplate} +@internal +class StorageBucketFromOutputs implements StorageBucket { + /// {@macro amplify_core.storage.storage_bucket_from_outputs} + const StorageBucketFromOutputs(this._name); + + final String _name; + + @override + BucketInfo resolveBucketInfo(StorageOutputs? storageOutputs) { + assert( + storageOutputs != null, + const InvalidStorageBucketException( + 'Amplify Storage is not configured.', + recoverySuggestion: + 'Make sure storage exists in the Amplify Outputs file.', + ), + ); + // TODO(nikahsn): fix after adding buckets to StorageOutputs. + return BucketInfo( + bucketName: _name, + region: storageOutputs!.awsRegion, + ); + } +} diff --git a/packages/amplify_core/lib/src/types/storage/storage_types.dart b/packages/amplify_core/lib/src/types/storage/storage_types.dart index 9324a72683..1e4bfbfb02 100644 --- a/packages/amplify_core/lib/src/types/storage/storage_types.dart +++ b/packages/amplify_core/lib/src/types/storage/storage_types.dart @@ -11,6 +11,7 @@ export '../exception/amplify_exception.dart' StorageOperationCanceledException, NetworkException, UnknownException; +export 'bucket_info.dart'; export 'copy_operation.dart'; export 'copy_options.dart'; export 'copy_request.dart'; @@ -44,6 +45,7 @@ export 'remove_operation.dart'; export 'remove_options.dart'; export 'remove_request.dart'; export 'remove_result.dart'; +export 'storage_bucket.dart'; export 'storage_item.dart'; export 'storage_path.dart'; export 'transfer_progress.dart'; From d01d0dfa5b990666fbf5a434ddf93a7c3e038b45 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Tue, 24 Sep 2024 11:01:33 -0700 Subject: [PATCH 10/42] updated resource.ts and backend.ts for multiple buckets in our infra-gen2 stack --- .../storage/main/amplify/storage/resource.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/infra-gen2/backends/storage/main/amplify/storage/resource.ts b/infra-gen2/backends/storage/main/amplify/storage/resource.ts index 3fb921c12b..4bb947fa19 100644 --- a/infra-gen2/backends/storage/main/amplify/storage/resource.ts +++ b/infra-gen2/backends/storage/main/amplify/storage/resource.ts @@ -1,7 +1,25 @@ import { defineStorage } from "@aws-amplify/backend"; -export const storage = defineStorage({ - name: "Storage Integ Test main", +export const firstBucket = defineStorage({ + name: "Storage Integ Test main bucket", + isDefault: true, + access: (allow) => ({ + "public/*": [ + allow.guest.to(["read", "write", "delete"]), + allow.authenticated.to(["read", "delete", "write"]), + ], + "protected/{entity_id}/*": [ + allow.authenticated.to(["read"]), + allow.entity("identity").to(["read", "write", "delete"]), + ], + "private/{entity_id}/*": [ + allow.entity("identity").to(["read", "write", "delete"]), + ], + }), +}); + +export const secondBucket = defineStorage({ + name: "Storage Integ Test secondary bucket", access: (allow) => ({ "public/*": [ allow.guest.to(["read", "write", "delete"]), From 47c1371547cb8c8f0e3898f7c663bb19a244a3bd Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Tue, 24 Sep 2024 11:02:33 -0700 Subject: [PATCH 11/42] updated resource.ts and backend.ts for multiple buckets in our infra-gen2 stack --- .../backends/storage/main/amplify/backend.ts | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/infra-gen2/backends/storage/main/amplify/backend.ts b/infra-gen2/backends/storage/main/amplify/backend.ts index e9d462a0b3..c75ada8bba 100644 --- a/infra-gen2/backends/storage/main/amplify/backend.ts +++ b/infra-gen2/backends/storage/main/amplify/backend.ts @@ -1,26 +1,52 @@ import { defineBackend } from "@aws-amplify/backend"; import * as s3 from "aws-cdk-lib/aws-s3"; import { auth } from "./auth/resource"; -import { storage } from "./storage/resource"; +import { firstBucket, secondBucket } from "./storage/resource"; /** * @see https://docs.amplify.aws/react/build-a-backend/ to add storage, functions, and more */ const backend = defineBackend({ auth, - storage, + firstBucket, + secondBucket, }); // custom storage configurations -const s3Bucket = backend.storage.resources.bucket; +const s3Bucket = backend.firstBucket.resources.bucket; const cfnBucket = s3Bucket.node.defaultChild as s3.CfnBucket; +const s3SecondaryBucket = backend.secondBucket.resources.bucket; +const cfnSecondaryBucket = s3SecondaryBucket.node.defaultChild as s3.CfnBucket; cfnBucket.accelerateConfiguration = { accelerationStatus: "Enabled", }; +cfnSecondaryBucket.accelerateConfiguration = { + accelerationStatus: "Enabled", +}; + +// required to add the metadata header, which amplify-backend does not support +backend.firstBucket.resources.cfnResources.cfnBucket.corsConfiguration = { + corsRules: [ + { + allowedHeaders: ["*"], + allowedMethods: ["GET", "HEAD", "PUT", "POST", "DELETE"], + allowedOrigins: ["*"], + exposedHeaders: [ + "x-amz-server-side-encryption", + "x-amz-request-id", + "x-amz-id-2", + "ETag", + "x-amz-meta-description", + ], + maxAge: 3000, + }, + ], +}; + // required to add the metadata header, which amplify-backend does not support -backend.storage.resources.cfnResources.cfnBucket.corsConfiguration = { +backend.secondBucket.resources.cfnResources.cfnBucket.corsConfiguration = { corsRules: [ { allowedHeaders: ["*"], From d68bb031f0503f27ee57bff36b53b92bc91f673a Mon Sep 17 00:00:00 2001 From: NikaHsn Date: Wed, 25 Sep 2024 11:57:31 -0700 Subject: [PATCH 12/42] feat(storage): update s3 storage service to support multiple s3 client for multi-bucket support (#5493) --- .../storage/storage_bucket_from_outputs.dart | 27 +++++-- .../service/s3_client_info.dart | 12 +++ .../service/storage_s3_service_impl.dart | 79 +++++++++++++++---- 3 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/s3_client_info.dart diff --git a/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart b/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart index 4ee6cedb83..21ab788904 100644 --- a/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart +++ b/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart @@ -17,15 +17,32 @@ class StorageBucketFromOutputs implements StorageBucket { assert( storageOutputs != null, const InvalidStorageBucketException( - 'Amplify Storage is not configured.', + 'Amplify Outputs file does not have storage configuration.', recoverySuggestion: - 'Make sure storage exists in the Amplify Outputs file.', + 'Make sure Amplify Storage is configured and the Amplify Outputs ' + 'file has storage configuration.', + ), + ); + final buckets = storageOutputs!.buckets; + if (buckets == null) { + throw const InvalidStorageBucketException( + 'Amplify Outputs storage configuration does not have buckets specified.', + recoverySuggestion: + 'Make sure Amplify Outputs file has storage configuration with ' + 'buckets specified.', + ); + } + final bucket = buckets.singleWhere( + (e) => e.name == _name, + orElse: () => throw const InvalidStorageBucketException( + 'Unable to lookup bucket from provided name in Amplify Outputs file.', + recoverySuggestion: 'Make sure Amplify Outputs file has the specified ' + 'bucket configuration.', ), ); - // TODO(nikahsn): fix after adding buckets to StorageOutputs. return BucketInfo( - bucketName: _name, - region: storageOutputs!.awsRegion, + bucketName: bucket.bucketName, + region: bucket.awsRegion, ); } } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/s3_client_info.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/s3_client_info.dart new file mode 100644 index 0000000000..162c140dcd --- /dev/null +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/s3_client_info.dart @@ -0,0 +1,12 @@ +import 'package:amplify_storage_s3_dart/src/sdk/src/s3/s3_client.dart'; +import 'package:meta/meta.dart'; +import 'package:smithy_aws/smithy_aws.dart'; + +/// It holds Amazon S3 client information. +@internal +class S3ClientInfo { + const S3ClientInfo({required this.client, required this.config}); + + final S3Client client; + final S3ClientConfig config; +} 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 f749de67f3..33f32dba3b 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 @@ -14,6 +14,7 @@ import 'package:amplify_storage_s3_dart/src/path_resolver/s3_path_resolver.dart' import 'package:amplify_storage_s3_dart/src/sdk/s3.dart' as s3; import 'package:amplify_storage_s3_dart/src/sdk/src/s3/common/endpoint_resolver.dart' as endpoint_resolver; +import 'package:amplify_storage_s3_dart/src/storage_s3_service/service/s3_client_info.dart'; import 'package:amplify_storage_s3_dart/src/storage_s3_service/storage_s3_service.dart'; import 'package:amplify_storage_s3_dart/src/storage_s3_service/transfer/transfer.dart' as transfer; @@ -84,10 +85,8 @@ class StorageS3Service { ..supportedProtocols = SupportedProtocols.http1, ), _pathResolver = pathResolver, + _credentialsProvider = credentialsProvider, _logger = logger, - // dependencyManager.get() => sigv4.AWSSigV4Signer is used for unit tests - _awsSigV4Signer = dependencyManager.get() ?? - sigv4.AWSSigV4Signer(credentialsProvider: credentialsProvider), _dependencyManager = dependencyManager, _serviceStartingTime = DateTime.now(); @@ -101,14 +100,10 @@ class StorageS3Service { final s3.S3Client _defaultS3Client; final S3PathResolver _pathResolver; final AWSLogger _logger; - final sigv4.AWSSigV4Signer _awsSigV4Signer; final DependencyManager _dependencyManager; final DateTime _serviceStartingTime; - - sigv4.AWSCredentialScope get _signerScope => sigv4.AWSCredentialScope( - region: _storageOutputs.awsRegion, - service: AWSService.s3, - ); + final AWSIamAmplifyAuthProvider _credentialsProvider; + final Map _s3ClientsInfo = {}; transfer.TransferDatabase get _transferDatabase => _dependencyManager.getOrCreate(); @@ -261,10 +256,20 @@ class StorageS3Service { path: '/$resolvedPath', ); + // dependencyManager.get() is used for unit tests + final awsSigV4Signer = _dependencyManager.get() ?? + sigv4.AWSSigV4Signer( + credentialsProvider: _credentialsProvider, + ); + final signerScope = sigv4.AWSCredentialScope( + region: _storageOutputs.awsRegion, + service: AWSService.s3, + ); + return S3GetUrlResult( - url: await _awsSigV4Signer.presign( + url: await awsSigV4Signer.presign( urlRequest, - credentialScope: _signerScope, + credentialScope: signerScope, expiresIn: s3PluginOptions.expiresIn, serviceConfiguration: _defaultS3SignerConfiguration, ), @@ -323,12 +328,17 @@ class StorageS3Service { void Function(S3TransferProgress)? onProgress, FutureOr Function()? onDone, FutureOr Function()? onError, + StorageBucket? bucket, }) { + // ignore: invalid_use_of_internal_member + final bucketName = bucket?.resolveBucketInfo(_storageOutputs).bucketName ?? + _storageOutputs.bucketName; + final s3ClientInfo = _getS3ClientInfo(bucket); final uploadDataTask = S3UploadTask.fromDataPayload( dataPayload, - s3Client: _defaultS3Client, - defaultS3ClientConfig: _defaultS3ClientConfig, - bucket: _storageOutputs.bucketName, + s3Client: s3ClientInfo.client, + defaultS3ClientConfig: s3ClientInfo.config, + bucket: bucketName, path: path, options: options, pathResolver: _pathResolver, @@ -611,4 +621,45 @@ class StorageS3Service { } } } + + S3ClientInfo _getS3ClientInfo(StorageBucket? storageBucket) { + if (storageBucket == null) { + return S3ClientInfo( + client: _defaultS3Client, + config: _defaultS3ClientConfig, + ); + } + // ignore: invalid_use_of_internal_member + final bucketInfo = storageBucket.resolveBucketInfo(_storageOutputs); + if (_s3ClientsInfo[bucketInfo.bucketName] != null) { + return _s3ClientsInfo[bucketInfo.bucketName]!; + } + + final usePathStyle = bucketInfo.bucketName.contains('.'); + if (usePathStyle) { + _logger.warn( + 'Since your bucket name contains dots (`"."`), the StorageS3 plugin' + ' will use path style URLs to communicate with the S3 service. S3' + ' Transfer acceleration is not supported for path style URLs. For more' + ' information, refer to:' + ' https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html'); + } + final s3ClientConfig = smithy_aws.S3ClientConfig( + signerConfiguration: _defaultS3SignerConfiguration, + usePathStyle: usePathStyle, + ); + final s3Client = s3.S3Client( + region: bucketInfo.region, + credentialsProvider: _credentialsProvider, + s3ClientConfig: s3ClientConfig, + client: AmplifyHttpClient(_dependencyManager) + ..supportedProtocols = SupportedProtocols.http1, + ); + final s3ClientInfo = S3ClientInfo( + client: s3Client, + config: s3ClientConfig, + ); + _s3ClientsInfo[bucketInfo.bucketName] = s3ClientInfo; + return s3ClientInfo; + } } From 3eafd519f5964790b81996a8519448fd6fdaf40c Mon Sep 17 00:00:00 2001 From: NikaHsn Date: Mon, 14 Oct 2024 12:07:34 -0700 Subject: [PATCH 13/42] feat(storage): update uploadData API to accept optional storage bucket param (#5540) --- .../category/amplify_storage_category.dart | 1 + .../amplify_storage_plugin_interface.dart | 1 + .../lib/src/types/storage/bucket_info.dart | 10 +- .../storage/storage_bucket_from_outputs.dart | 7 +- .../types/storage/storage_bucket_test.dart | 87 ++++++++++++ .../lib/src/amplify_storage_s3_dart_impl.dart | 2 + .../service/s3_client_info.dart | 9 +- .../service/storage_s3_service_impl.dart | 36 +++-- .../service/task/s3_upload_task.dart | 29 ++-- .../transfer/database/database_io.dart | 24 +++- .../transfer/database/tables.dart | 6 + .../transfer/database/tables.drift.dart | 133 ++++++++++++++++-- .../transfer/database/transfer_record.dart | 8 ++ .../transfer/database/transfer_record.g.dart | 4 + .../test/amplify_storage_s3_dart_test.dart | 50 +++++++ .../storage_s3_service_test.dart | 35 ++++- .../task/s3_upload_task_test.dart | 86 +++++++---- .../transfer/database_html_test.dart | 4 + .../test/test_utils/mocks.dart | 4 + 19 files changed, 466 insertions(+), 70 deletions(-) create mode 100644 packages/amplify_core/test/types/storage/storage_bucket_test.dart diff --git a/packages/amplify_core/lib/src/category/amplify_storage_category.dart b/packages/amplify_core/lib/src/category/amplify_storage_category.dart index 416446d12e..c3f4cfbe2f 100644 --- a/packages/amplify_core/lib/src/category/amplify_storage_category.dart +++ b/packages/amplify_core/lib/src/category/amplify_storage_category.dart @@ -144,6 +144,7 @@ class StorageCategory extends AmplifyCategory { required StoragePath path, void Function(StorageTransferProgress)? onProgress, StorageUploadDataOptions? options, + StorageBucket? bucket, }) { return identifyCall( StorageCategoryMethod.uploadData, diff --git a/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart b/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart index c4d874d8a0..efb891fbef 100644 --- a/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart +++ b/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart @@ -63,6 +63,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { required StorageDataPayload data, void Function(StorageTransferProgress)? onProgress, StorageUploadDataOptions? options, + StorageBucket? bucket, }) { throw UnimplementedError('uploadData() has not been implemented.'); } diff --git a/packages/amplify_core/lib/src/types/storage/bucket_info.dart b/packages/amplify_core/lib/src/types/storage/bucket_info.dart index a3c065ef23..a30811b333 100644 --- a/packages/amplify_core/lib/src/types/storage/bucket_info.dart +++ b/packages/amplify_core/lib/src/types/storage/bucket_info.dart @@ -1,9 +1,17 @@ +import 'package:amplify_core/amplify_core.dart'; + /// {@template amplify_core.storage.bucket_info} /// Presents a storage bucket information. /// {@endtemplate} -class BucketInfo { +class BucketInfo with AWSEquatable { /// {@macro amplify_core.storage.bucket_info} const BucketInfo({required this.bucketName, required this.region}); final String bucketName; final String region; + + @override + List get props => [ + bucketName, + region, + ]; } diff --git a/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart b/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart index 21ab788904..ed22c24aa2 100644 --- a/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart +++ b/packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart @@ -16,12 +16,7 @@ class StorageBucketFromOutputs implements StorageBucket { BucketInfo resolveBucketInfo(StorageOutputs? storageOutputs) { assert( storageOutputs != null, - const InvalidStorageBucketException( - 'Amplify Outputs file does not have storage configuration.', - recoverySuggestion: - 'Make sure Amplify Storage is configured and the Amplify Outputs ' - 'file has storage configuration.', - ), + 'storageOutputs can not be null', ); final buckets = storageOutputs!.buckets; if (buckets == null) { diff --git a/packages/amplify_core/test/types/storage/storage_bucket_test.dart b/packages/amplify_core/test/types/storage/storage_bucket_test.dart new file mode 100644 index 0000000000..edae2ab4af --- /dev/null +++ b/packages/amplify_core/test/types/storage/storage_bucket_test.dart @@ -0,0 +1,87 @@ +import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/config/amplify_outputs/storage/bucket_outputs.dart'; +import 'package:amplify_core/src/config/amplify_outputs/storage/storage_outputs.dart'; +import 'package:test/test.dart'; + +void main() { + group('Storage bucket resolve BucketInfo', () { + const defaultBucketOutputs = BucketOutputs( + name: 'default-bucket-friendly-name', + bucketName: 'default-bucket-unique-name', + awsRegion: 'default-bucket-aws-region', + ); + const secondBucketOutputs = BucketOutputs( + name: 'second-bucket-friendly-name', + bucketName: 'second-bucket-unique-name', + awsRegion: 'second-bucket-aws-region', + ); + final defaultBucketInfo = BucketInfo( + bucketName: defaultBucketOutputs.bucketName, + region: defaultBucketOutputs.awsRegion, + ); + final secondBucketInfo = BucketInfo( + bucketName: secondBucketOutputs.bucketName, + region: secondBucketOutputs.awsRegion, + ); + final testStorageOutputsMultiBucket = StorageOutputs( + awsRegion: defaultBucketOutputs.awsRegion, + bucketName: defaultBucketOutputs.bucketName, + buckets: [ + defaultBucketOutputs, + secondBucketOutputs, + ], + ); + final testStorageOutputsSingleBucket = StorageOutputs( + awsRegion: defaultBucketOutputs.awsRegion, + bucketName: defaultBucketOutputs.bucketName, + ); + + test( + 'should return same bucket info when storage bucket is created from' + ' a bucket info', () { + final storageBucket = StorageBucket.fromBucketInfo( + defaultBucketInfo, + ); + final bucketInfo = storageBucket.resolveBucketInfo(null); + expect(bucketInfo, defaultBucketInfo); + }); + + test( + 'should return bucket info when storage bucket is created from' + ' buckets in storage outputs', () { + final storageBucket = StorageBucket.fromOutputs(secondBucketOutputs.name); + final bucketInfo = + storageBucket.resolveBucketInfo(testStorageOutputsMultiBucket); + expect(bucketInfo, secondBucketInfo); + }); + + test( + 'should throw assertion error when storage bucket is created from' + ' outputs and storage outputs is null', () { + final storageBucket = + StorageBucket.fromOutputs(defaultBucketOutputs.name); + expect( + () => storageBucket.resolveBucketInfo(null), + throwsA(isA()), + ); + }); + test( + 'should throw exception when storage bucket is created from outputs and' + ' storage outputs does not have buckets', () { + final storageBucket = StorageBucket.fromOutputs('bucket-name'); + expect( + () => storageBucket.resolveBucketInfo(testStorageOutputsSingleBucket), + throwsA(isA()), + ); + }); + test( + 'should throw exception when storage bucket is created from outputs and' + ' bucket name does not match any bucket in storage outputs', () { + final storageBucket = StorageBucket.fromOutputs('invalid-bucket-name'); + expect( + () => storageBucket.resolveBucketInfo(testStorageOutputsMultiBucket), + throwsA(isA()), + ); + }); + }); +} 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 cc709c5802..f4ec4035c4 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 @@ -274,6 +274,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface required StoragePath path, void Function(S3TransferProgress)? onProgress, StorageUploadDataOptions? options, + StorageBucket? bucket, }) { final s3PluginOptions = reifyPluginOptions( pluginOptions: options?.pluginOptions, @@ -290,6 +291,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface dataPayload: data, options: s3Options, onProgress: onProgress, + bucket: bucket, ); return S3UploadDataOperation( diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/s3_client_info.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/s3_client_info.dart index 162c140dcd..d70adc524d 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/s3_client_info.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/s3_client_info.dart @@ -5,8 +5,15 @@ import 'package:smithy_aws/smithy_aws.dart'; /// It holds Amazon S3 client information. @internal class S3ClientInfo { - const S3ClientInfo({required this.client, required this.config}); + const S3ClientInfo({ + required this.client, + required this.config, + required this.bucketName, + required this.awsRegion, + }); final S3Client client; final S3ClientConfig config; + final String bucketName; + final String awsRegion; } 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 33f32dba3b..86a355eac9 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 @@ -330,15 +330,13 @@ class StorageS3Service { FutureOr Function()? onError, StorageBucket? bucket, }) { - // ignore: invalid_use_of_internal_member - final bucketName = bucket?.resolveBucketInfo(_storageOutputs).bucketName ?? - _storageOutputs.bucketName; - final s3ClientInfo = _getS3ClientInfo(bucket); + final s3ClientInfo = getS3ClientInfo(storageBucket: bucket); final uploadDataTask = S3UploadTask.fromDataPayload( dataPayload, s3Client: s3ClientInfo.client, - defaultS3ClientConfig: s3ClientInfo.config, - bucket: bucketName, + s3ClientConfig: s3ClientInfo.config, + bucket: s3ClientInfo.bucketName, + awsRegion: s3ClientInfo.awsRegion, path: path, options: options, pathResolver: _pathResolver, @@ -376,8 +374,9 @@ class StorageS3Service { final uploadDataTask = S3UploadTask.fromAWSFile( localFile, s3Client: _defaultS3Client, - defaultS3ClientConfig: _defaultS3ClientConfig, + s3ClientConfig: _defaultS3ClientConfig, bucket: _storageOutputs.bucketName, + awsRegion: _storageOutputs.awsRegion, path: path, options: uploadDataOptions, pathResolver: _pathResolver, @@ -604,17 +603,23 @@ class StorageS3Service { Future abortIncompleteMultipartUploads() async { final records = await _transferDatabase .getMultipartUploadRecordsCreatedBefore(_serviceStartingTime); - for (final record in records) { + final bucketInfo = BucketInfo( + bucketName: record.bucketName ?? _storageOutputs.bucketName, + region: record.awsRegion ?? _storageOutputs.awsRegion, + ); final request = s3.AbortMultipartUploadRequest.build((builder) { builder - ..bucket = _storageOutputs.bucketName + ..bucket = bucketInfo.bucketName ..key = record.objectKey ..uploadId = record.uploadId; }); + final s3Client = getS3ClientInfo( + storageBucket: StorageBucket.fromBucketInfo(bucketInfo), + ).client; try { - await _defaultS3Client.abortMultipartUpload(request).result; + await s3Client.abortMultipartUpload(request).result; await _transferDatabase.deleteTransferRecords(record.uploadId); } on Exception catch (error) { _logger.error('Failed to abort multipart upload due to: $error'); @@ -622,11 +627,18 @@ class StorageS3Service { } } - S3ClientInfo _getS3ClientInfo(StorageBucket? storageBucket) { + /// Creates and caches [S3ClientInfo] given the optional [storageBucket] + /// parameter. If the optional parameter is not provided it uses + /// StorageOutputs default bucket to create the [S3ClientInfo]. + @internal + @visibleForTesting + S3ClientInfo getS3ClientInfo({StorageBucket? storageBucket}) { if (storageBucket == null) { return S3ClientInfo( client: _defaultS3Client, config: _defaultS3ClientConfig, + bucketName: _storageOutputs.bucketName, + awsRegion: _storageOutputs.awsRegion, ); } // ignore: invalid_use_of_internal_member @@ -658,6 +670,8 @@ class StorageS3Service { final s3ClientInfo = S3ClientInfo( client: s3Client, config: s3ClientConfig, + bucketName: bucketInfo.bucketName, + awsRegion: bucketInfo.region, ); _s3ClientsInfo[bucketInfo.bucketName] = s3ClientInfo; return s3ClientInfo; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_upload_task.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_upload_task.dart index 55f5091165..bbb49cf100 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_upload_task.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_upload_task.dart @@ -48,9 +48,10 @@ const fallbackContentType = 'application/octet-stream'; class S3UploadTask { S3UploadTask._({ required s3.S3Client s3Client, - required smithy_aws.S3ClientConfig defaultS3ClientConfig, + required smithy_aws.S3ClientConfig s3ClientConfig, required S3PathResolver pathResolver, required String bucket, + required String awsRegion, required StoragePath path, required StorageUploadDataOptions options, S3DataPayload? dataPayload, @@ -59,9 +60,10 @@ class S3UploadTask { required AWSLogger logger, required transfer.TransferDatabase transferDatabase, }) : _s3Client = s3Client, - _defaultS3ClientConfig = defaultS3ClientConfig, + _s3ClientConfig = s3ClientConfig, _pathResolver = pathResolver, _bucket = bucket, + _awsRegion = awsRegion, _path = path, _options = options, _dataPayload = dataPayload, @@ -81,9 +83,10 @@ class S3UploadTask { S3UploadTask.fromDataPayload( S3DataPayload dataPayload, { required s3.S3Client s3Client, - required smithy_aws.S3ClientConfig defaultS3ClientConfig, + required smithy_aws.S3ClientConfig s3ClientConfig, required S3PathResolver pathResolver, required String bucket, + required String awsRegion, required StoragePath path, required StorageUploadDataOptions options, void Function(S3TransferProgress)? onProgress, @@ -91,9 +94,10 @@ class S3UploadTask { required transfer.TransferDatabase transferDatabase, }) : this._( s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: s3ClientConfig, pathResolver: pathResolver, bucket: bucket, + awsRegion: awsRegion, path: path, dataPayload: dataPayload, options: options, @@ -108,9 +112,10 @@ class S3UploadTask { S3UploadTask.fromAWSFile( AWSFile localFile, { required s3.S3Client s3Client, - required smithy_aws.S3ClientConfig defaultS3ClientConfig, + required smithy_aws.S3ClientConfig s3ClientConfig, required S3PathResolver pathResolver, required String bucket, + required String awsRegion, required StoragePath path, required StorageUploadDataOptions options, void Function(S3TransferProgress)? onProgress, @@ -118,9 +123,10 @@ class S3UploadTask { required transfer.TransferDatabase transferDatabase, }) : this._( s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: s3ClientConfig, pathResolver: pathResolver, bucket: bucket, + awsRegion: awsRegion, path: path, localFile: localFile, options: options, @@ -135,9 +141,10 @@ class S3UploadTask { final Completer _uploadCompleter = Completer(); final s3.S3Client _s3Client; - final smithy_aws.S3ClientConfig _defaultS3ClientConfig; + final smithy_aws.S3ClientConfig _s3ClientConfig; final S3PathResolver _pathResolver; final String _bucket; + final String _awsRegion; final StoragePath _path; final StorageUploadDataOptions _options; final void Function(S3TransferProgress)? _onProgress; @@ -191,7 +198,7 @@ class S3UploadTask { /// Should be used only internally. Future start() async { if (_s3PluginOptions.useAccelerateEndpoint && - _defaultS3ClientConfig.usePathStyle) { + _s3ClientConfig.usePathStyle) { _completeUploadWithError(s3_exception.accelerateEndpointUnusable); return; } @@ -328,7 +335,7 @@ class S3UploadTask { try { _putObjectOperation = _s3Client.putObject( putObjectRequest, - s3ClientConfig: _defaultS3ClientConfig.copyWith( + s3ClientConfig: _s3ClientConfig.copyWith( useAcceleration: _s3PluginOptions.useAccelerateEndpoint, ), ); @@ -497,6 +504,8 @@ class S3UploadTask { TransferRecord( uploadId: uploadId, objectKey: _resolvedPath, + bucketName: _bucket, + awsRegion: _awsRegion, createdAt: DateTime.now(), ), ); @@ -651,7 +660,7 @@ class S3UploadTask { try { final operation = _s3Client.uploadPart( request, - s3ClientConfig: _defaultS3ClientConfig.copyWith( + s3ClientConfig: _s3ClientConfig.copyWith( useAcceleration: _s3PluginOptions.useAccelerateEndpoint, ), ); diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/database_io.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/database_io.dart index a16a66bcf3..1c105d8b32 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/database_io.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/database_io.dart @@ -35,7 +35,25 @@ class TransferDatabase extends $TransferDatabase // Bump the version number when any alteration is made into tables.dart @override - int get schemaVersion => 1; + int get schemaVersion => 2; + + @override + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (Migrator m) async { + await m.createAll(); + }, + onUpgrade: (Migrator m, int from, int to) async { + // Note: From schemaVersion 1->2 we added bucketName and awsRegion. + // they are nullable columns so that on upgrade we need to update + // the transferRecords table to add these two columns + if (from < 2) { + await m.addColumn(transferRecords, transferRecords.bucketName); + await m.addColumn(transferRecords, transferRecords.awsRegion); + } + }, + ); + } @override Future> getMultipartUploadRecordsCreatedBefore( @@ -52,6 +70,8 @@ class TransferDatabase extends $TransferDatabase objectKey: e.objectKey, uploadId: e.uploadId, createdAt: DateTime.parse(e.createdAt), + bucketName: e.bucketName, + awsRegion: e.awsRegion, ), ) .get(); @@ -63,6 +83,8 @@ class TransferDatabase extends $TransferDatabase uploadId: record.uploadId, objectKey: record.objectKey, createdAt: record.createdAt.toIso8601String(), + bucketName: Value(record.bucketName), + awsRegion: Value(record.awsRegion), ); final value = await into(transferRecords).insert(entry); return value.toString(); diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/tables.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/tables.dart index 00d5b5b22d..4d13dcea2a 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/tables.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/tables.dart @@ -20,4 +20,10 @@ class TransferRecords extends Table { /// Timestamp of [uploadId] creation. TextColumn get createdAt => text()(); + + /// Amazon S3 bucket name. + TextColumn get bucketName => text().nullable()(); + + /// AWS region of Amazon S3 bucket. + TextColumn get awsRegion => text().nullable()(); } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/tables.drift.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/tables.drift.dart index 30ebd91612..f50e5c57ce 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/tables.drift.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/tables.drift.dart @@ -38,8 +38,21 @@ class $TransferRecordsTable extends i2.TransferRecords late final i0.GeneratedColumn createdAt = i0.GeneratedColumn( 'created_at', aliasedName, false, type: i0.DriftSqlType.string, requiredDuringInsert: true); + static const i0.VerificationMeta _bucketNameMeta = + const i0.VerificationMeta('bucketName'); @override - List get $columns => [id, uploadId, objectKey, createdAt]; + late final i0.GeneratedColumn bucketName = i0.GeneratedColumn( + 'bucket_name', aliasedName, true, + type: i0.DriftSqlType.string, requiredDuringInsert: false); + static const i0.VerificationMeta _awsRegionMeta = + const i0.VerificationMeta('awsRegion'); + @override + late final i0.GeneratedColumn awsRegion = i0.GeneratedColumn( + 'aws_region', aliasedName, true, + type: i0.DriftSqlType.string, requiredDuringInsert: false); + @override + List get $columns => + [id, uploadId, objectKey, createdAt, bucketName, awsRegion]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -72,6 +85,16 @@ class $TransferRecordsTable extends i2.TransferRecords } else if (isInserting) { context.missing(_createdAtMeta); } + if (data.containsKey('bucket_name')) { + context.handle( + _bucketNameMeta, + bucketName.isAcceptableOrUnknown( + data['bucket_name']!, _bucketNameMeta)); + } + if (data.containsKey('aws_region')) { + context.handle(_awsRegionMeta, + awsRegion.isAcceptableOrUnknown(data['aws_region']!, _awsRegionMeta)); + } return context; } @@ -89,6 +112,10 @@ class $TransferRecordsTable extends i2.TransferRecords .read(i0.DriftSqlType.string, data['${effectivePrefix}object_key'])!, createdAt: attachedDatabase.typeMapping .read(i0.DriftSqlType.string, data['${effectivePrefix}created_at'])!, + bucketName: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}bucket_name']), + awsRegion: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}aws_region']), ); } @@ -111,11 +138,19 @@ class TransferRecord extends i0.DataClass /// Timestamp of [uploadId] creation. final String createdAt; + + /// Amazon S3 bucket name. + final String? bucketName; + + /// AWS region of Amazon S3 bucket. + final String? awsRegion; const TransferRecord( {required this.id, required this.uploadId, required this.objectKey, - required this.createdAt}); + required this.createdAt, + this.bucketName, + this.awsRegion}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -123,6 +158,12 @@ class TransferRecord extends i0.DataClass map['upload_id'] = i0.Variable(uploadId); map['object_key'] = i0.Variable(objectKey); map['created_at'] = i0.Variable(createdAt); + if (!nullToAbsent || bucketName != null) { + map['bucket_name'] = i0.Variable(bucketName); + } + if (!nullToAbsent || awsRegion != null) { + map['aws_region'] = i0.Variable(awsRegion); + } return map; } @@ -132,6 +173,12 @@ class TransferRecord extends i0.DataClass uploadId: i0.Value(uploadId), objectKey: i0.Value(objectKey), createdAt: i0.Value(createdAt), + bucketName: bucketName == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(bucketName), + awsRegion: awsRegion == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(awsRegion), ); } @@ -143,6 +190,8 @@ class TransferRecord extends i0.DataClass uploadId: serializer.fromJson(json['uploadId']), objectKey: serializer.fromJson(json['objectKey']), createdAt: serializer.fromJson(json['createdAt']), + bucketName: serializer.fromJson(json['bucketName']), + awsRegion: serializer.fromJson(json['awsRegion']), ); } @override @@ -153,16 +202,25 @@ class TransferRecord extends i0.DataClass 'uploadId': serializer.toJson(uploadId), 'objectKey': serializer.toJson(objectKey), 'createdAt': serializer.toJson(createdAt), + 'bucketName': serializer.toJson(bucketName), + 'awsRegion': serializer.toJson(awsRegion), }; } i1.TransferRecord copyWith( - {int? id, String? uploadId, String? objectKey, String? createdAt}) => + {int? id, + String? uploadId, + String? objectKey, + String? createdAt, + i0.Value bucketName = const i0.Value.absent(), + i0.Value awsRegion = const i0.Value.absent()}) => i1.TransferRecord( id: id ?? this.id, uploadId: uploadId ?? this.uploadId, objectKey: objectKey ?? this.objectKey, createdAt: createdAt ?? this.createdAt, + bucketName: bucketName.present ? bucketName.value : this.bucketName, + awsRegion: awsRegion.present ? awsRegion.value : this.awsRegion, ); @override String toString() { @@ -170,13 +228,16 @@ class TransferRecord extends i0.DataClass ..write('id: $id, ') ..write('uploadId: $uploadId, ') ..write('objectKey: $objectKey, ') - ..write('createdAt: $createdAt') + ..write('createdAt: $createdAt, ') + ..write('bucketName: $bucketName, ') + ..write('awsRegion: $awsRegion') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, uploadId, objectKey, createdAt); + int get hashCode => + Object.hash(id, uploadId, objectKey, createdAt, bucketName, awsRegion); @override bool operator ==(Object other) => identical(this, other) || @@ -184,7 +245,9 @@ class TransferRecord extends i0.DataClass other.id == this.id && other.uploadId == this.uploadId && other.objectKey == this.objectKey && - other.createdAt == this.createdAt); + other.createdAt == this.createdAt && + other.bucketName == this.bucketName && + other.awsRegion == this.awsRegion); } class TransferRecordsCompanion extends i0.UpdateCompanion { @@ -192,17 +255,23 @@ class TransferRecordsCompanion extends i0.UpdateCompanion { final i0.Value uploadId; final i0.Value objectKey; final i0.Value createdAt; + final i0.Value bucketName; + final i0.Value awsRegion; const TransferRecordsCompanion({ this.id = const i0.Value.absent(), this.uploadId = const i0.Value.absent(), this.objectKey = const i0.Value.absent(), this.createdAt = const i0.Value.absent(), + this.bucketName = const i0.Value.absent(), + this.awsRegion = const i0.Value.absent(), }); TransferRecordsCompanion.insert({ this.id = const i0.Value.absent(), required String uploadId, required String objectKey, required String createdAt, + this.bucketName = const i0.Value.absent(), + this.awsRegion = const i0.Value.absent(), }) : uploadId = i0.Value(uploadId), objectKey = i0.Value(objectKey), createdAt = i0.Value(createdAt); @@ -211,12 +280,16 @@ class TransferRecordsCompanion extends i0.UpdateCompanion { i0.Expression? uploadId, i0.Expression? objectKey, i0.Expression? createdAt, + i0.Expression? bucketName, + i0.Expression? awsRegion, }) { return i0.RawValuesInsertable({ if (id != null) 'id': id, if (uploadId != null) 'upload_id': uploadId, if (objectKey != null) 'object_key': objectKey, if (createdAt != null) 'created_at': createdAt, + if (bucketName != null) 'bucket_name': bucketName, + if (awsRegion != null) 'aws_region': awsRegion, }); } @@ -224,12 +297,16 @@ class TransferRecordsCompanion extends i0.UpdateCompanion { {i0.Value? id, i0.Value? uploadId, i0.Value? objectKey, - i0.Value? createdAt}) { + i0.Value? createdAt, + i0.Value? bucketName, + i0.Value? awsRegion}) { return i1.TransferRecordsCompanion( id: id ?? this.id, uploadId: uploadId ?? this.uploadId, objectKey: objectKey ?? this.objectKey, createdAt: createdAt ?? this.createdAt, + bucketName: bucketName ?? this.bucketName, + awsRegion: awsRegion ?? this.awsRegion, ); } @@ -248,6 +325,12 @@ class TransferRecordsCompanion extends i0.UpdateCompanion { if (createdAt.present) { map['created_at'] = i0.Variable(createdAt.value); } + if (bucketName.present) { + map['bucket_name'] = i0.Variable(bucketName.value); + } + if (awsRegion.present) { + map['aws_region'] = i0.Variable(awsRegion.value); + } return map; } @@ -257,7 +340,9 @@ class TransferRecordsCompanion extends i0.UpdateCompanion { ..write('id: $id, ') ..write('uploadId: $uploadId, ') ..write('objectKey: $objectKey, ') - ..write('createdAt: $createdAt') + ..write('createdAt: $createdAt, ') + ..write('bucketName: $bucketName, ') + ..write('awsRegion: $awsRegion') ..write(')')) .toString(); } @@ -269,6 +354,8 @@ typedef $$TransferRecordsTableInsertCompanionBuilder required String uploadId, required String objectKey, required String createdAt, + i0.Value bucketName, + i0.Value awsRegion, }); typedef $$TransferRecordsTableUpdateCompanionBuilder = i1.TransferRecordsCompanion Function({ @@ -276,6 +363,8 @@ typedef $$TransferRecordsTableUpdateCompanionBuilder i0.Value uploadId, i0.Value objectKey, i0.Value createdAt, + i0.Value bucketName, + i0.Value awsRegion, }); class $$TransferRecordsTableTableManager extends i0.RootTableManager< @@ -303,24 +392,32 @@ class $$TransferRecordsTableTableManager extends i0.RootTableManager< i0.Value uploadId = const i0.Value.absent(), i0.Value objectKey = const i0.Value.absent(), i0.Value createdAt = const i0.Value.absent(), + i0.Value bucketName = const i0.Value.absent(), + i0.Value awsRegion = const i0.Value.absent(), }) => i1.TransferRecordsCompanion( id: id, uploadId: uploadId, objectKey: objectKey, createdAt: createdAt, + bucketName: bucketName, + awsRegion: awsRegion, ), getInsertCompanionBuilder: ({ i0.Value id = const i0.Value.absent(), required String uploadId, required String objectKey, required String createdAt, + i0.Value bucketName = const i0.Value.absent(), + i0.Value awsRegion = const i0.Value.absent(), }) => i1.TransferRecordsCompanion.insert( id: id, uploadId: uploadId, objectKey: objectKey, createdAt: createdAt, + bucketName: bucketName, + awsRegion: awsRegion, ), )); } @@ -360,6 +457,16 @@ class $$TransferRecordsTableFilterComposer column: $state.table.createdAt, builder: (column, joinBuilders) => i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get bucketName => $state.composableBuilder( + column: $state.table.bucketName, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get awsRegion => $state.composableBuilder( + column: $state.table.awsRegion, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); } class $$TransferRecordsTableOrderingComposer extends i0 @@ -384,4 +491,14 @@ class $$TransferRecordsTableOrderingComposer extends i0 column: $state.table.createdAt, builder: (column, joinBuilders) => i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get bucketName => $state.composableBuilder( + column: $state.table.bucketName, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get awsRegion => $state.composableBuilder( + column: $state.table.awsRegion, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/transfer_record.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/transfer_record.dart index 3607e8a0ed..e5911c8d22 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/transfer_record.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/transfer_record.dart @@ -18,6 +18,8 @@ class TransferRecord { required this.uploadId, required this.objectKey, required this.createdAt, + this.bucketName, + this.awsRegion, }); /// creates new [TransferRecord] object from a [json] map. @@ -40,6 +42,12 @@ class TransferRecord { /// Timestamp of [uploadId] creation. final DateTime createdAt; + /// Amazon S3 bucket name. + final String? bucketName; + + /// AWS region of Amazon S3 bucket. + final String? awsRegion; + /// return json map representation of [TransferRecord] object. Map toJson() => _$TransferRecordToJson(this); diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/transfer_record.g.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/transfer_record.g.dart index 270e70dd8f..d9783e3a7c 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/transfer_record.g.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/transfer/database/transfer_record.g.dart @@ -11,6 +11,8 @@ TransferRecord _$TransferRecordFromJson(Map json) => uploadId: json['uploadId'] as String, objectKey: json['objectKey'] as String, createdAt: DateTime.parse(json['createdAt'] as String), + bucketName: json['bucketName'] as String?, + awsRegion: json['awsRegion'] as String?, ); Map _$TransferRecordToJson(TransferRecord instance) => @@ -18,4 +20,6 @@ Map _$TransferRecordToJson(TransferRecord instance) => 'uploadId': instance.uploadId, 'objectKey': instance.objectKey, 'createdAt': instance.createdAt.toIso8601String(), + 'bucketName': instance.bucketName, + 'awsRegion': instance.awsRegion, }; 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 17015eb5f7..405b5c9bf8 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 @@ -534,6 +534,14 @@ void main() { const StorageUploadDataOptions(), ); registerFallbackValue(const S3DataPayload.empty()); + registerFallbackValue( + const StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'bucketName', + region: 'region', + ), + ), + ); }); test('should forward default options to StorageS3Service.uploadData API', @@ -631,6 +639,48 @@ void main() { ); }); + test('should forward bucket to StorageS3Service.uploadData API', + () async { + const testBucket = StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'test-bucket', + region: 'test-region', + ), + ); + when( + () => storageS3Service.uploadData( + path: testPath, + dataPayload: any(named: 'dataPayload'), + options: any(named: 'options'), + bucket: testBucket, + ), + ).thenAnswer((_) => testS3UploadTask); + + when(() => testS3UploadTask.result).thenAnswer((_) async => testItem); + uploadDataOperation = storageS3Plugin.uploadData( + data: testData, + path: testPath, + bucket: testBucket, + ); + final capturedBucket = verify( + () => storageS3Service.uploadData( + path: testPath, + dataPayload: any(named: 'dataPayload'), + options: any( + named: 'options', + ), + bucket: captureAny( + named: 'bucket', + ), + ), + ).captured.last; + + expect( + capturedBucket, + testBucket, + ); + }); + test('should forward options.metadata to StorageS3Service.uploadData API', () async { const testMetadata = { diff --git a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart index aec65db336..0ca3760d5e 100644 --- a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'package:amplify_core/amplify_core.dart' hide PaginatedResult; +import 'package:amplify_core/src/config/amplify_outputs/storage/bucket_outputs.dart'; import 'package:amplify_core/src/config/amplify_outputs/storage/storage_outputs.dart'; import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; import 'package:amplify_storage_s3_dart/src/exception/s3_storage_exception.dart'; @@ -25,9 +26,18 @@ const testPath = StoragePath.fromString('some/path.txt'); void main() { group('StorageS3Service', () { const testBucket = 'bucket1'; + const testBucketName = 'bucket1-name'; const testRegion = 'west-2'; - const storageOutputs = - StorageOutputs(bucketName: testBucket, awsRegion: testRegion); + const testBuckets = BucketOutputs( + name: testBucket, + bucketName: testBucketName, + awsRegion: testRegion, + ); + const storageOutputs = StorageOutputs( + bucketName: testBucket, + awsRegion: testRegion, + buckets: [testBuckets], + ); final pathResolver = TestPathResolver(); late DependencyManager dependencyManager; @@ -35,14 +45,20 @@ void main() { late StorageS3Service storageS3Service; late AWSLogger logger; late AWSSigV4Signer awsSigV4Signer; + late AmplifyUserAgent mockUserAgent; + late AWSHttpClient mockAwsHttpClient; setUp(() { s3Client = MockS3Client(); logger = MockAWSLogger(); awsSigV4Signer = MockAWSSigV4Signer(); + mockUserAgent = MockAmplifyUserAgent(); + mockAwsHttpClient = MockAWSHttpClient(); dependencyManager = DependencyManager() ..addInstance(s3Client) - ..addInstance(awsSigV4Signer); + ..addInstance(awsSigV4Signer) + ..addInstance(mockUserAgent) + ..addInstance(mockAwsHttpClient); storageS3Service = StorageS3Service( storageOutputs: storageOutputs, pathResolver: pathResolver, @@ -69,6 +85,19 @@ void main() { expect(message, contains('Since your bucket name contains dots')); }); + test('creates and caches s3 client info for each storage bucket', () { + final client1 = storageS3Service.getS3ClientInfo( + storageBucket: const StorageBucket.fromBucketInfo( + BucketInfo(bucketName: testBucketName, region: testRegion), + ), + ); + final client2 = storageS3Service.getS3ClientInfo( + storageBucket: StorageBucket.fromOutputs(testBucket), + ); + + expect(client1, client2); + }); + group('list() API', () { late S3ListResult listResult; const testNextContinuationToken = 'get-next-page'; diff --git a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_upload_task_test.dart b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_upload_task_test.dart index 094aef27c4..691523feb6 100644 --- a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_upload_task_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_upload_task_test.dart @@ -27,6 +27,7 @@ void main() { late AWSLogger logger; late transfer.TransferDatabase transferDatabase; const testBucket = 'fake-bucket'; + const testRegion = 'test-region'; const defaultS3ClientConfig = smithy_aws.S3ClientConfig(); final pathResolver = TestPathResolver(); const testUploadDataOptions = StorageUploadDataOptions(); @@ -112,9 +113,10 @@ void main() { final uploadDataTask = S3UploadTask.fromDataPayload( testDataPayload, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: testPath, options: const StorageUploadDataOptions(), logger: logger, @@ -171,9 +173,10 @@ void main() { final uploadDataTask = S3UploadTask.fromDataPayload( testDataPayload, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: testPath, options: testUploadDataOptions, logger: logger, @@ -221,9 +224,10 @@ void main() { final uploadDataTask = S3UploadTask.fromDataPayload( testDataPayloadBytes, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: testPath, options: testUploadDataOptions, logger: logger, @@ -290,9 +294,10 @@ void main() { final uploadDataTask = S3UploadTask.fromDataPayload( testDataPayload, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: testPath, options: testUploadDataOptions, logger: logger, @@ -333,9 +338,10 @@ void main() { final uploadDataTask = S3UploadTask.fromDataPayload( testDataPayload, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: testPath, options: testUploadDataOptions, logger: logger, @@ -368,9 +374,10 @@ void main() { final uploadDataTask = S3UploadTask.fromDataPayload( testDataPayload, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: testPath, options: testUploadDataOptions, logger: logger, @@ -413,9 +420,10 @@ void main() { final uploadDataTask = S3UploadTask.fromDataPayload( testDataPayload, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: testPath, options: testUploadDataOptions, logger: logger, @@ -465,9 +473,10 @@ void main() { final uploadDataTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -525,9 +534,10 @@ void main() { final uploadDataTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -581,9 +591,10 @@ void main() { final uploadDataTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -635,9 +646,10 @@ void main() { final uploadDataTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -773,9 +785,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -955,9 +968,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1047,9 +1061,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFileWithoutContentType, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1148,9 +1163,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1185,9 +1201,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1217,9 +1234,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testBadFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1246,9 +1264,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1290,9 +1309,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: const StorageUploadDataOptions(), logger: logger, @@ -1333,9 +1353,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1376,9 +1397,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1457,9 +1479,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1549,9 +1572,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: const StorageUploadDataOptions(), logger: logger, @@ -1640,9 +1664,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1710,9 +1735,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1866,9 +1892,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1924,9 +1951,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( testLocalFile, s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, + s3ClientConfig: defaultS3ClientConfig, pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, @@ -1988,10 +2016,10 @@ void main() { final uploadTask = S3UploadTask.fromAWSFile( AWSFile.fromPath('fake/file.jpg'), s3Client: s3Client, - defaultS3ClientConfig: - const smithy_aws.S3ClientConfig(usePathStyle: true), + s3ClientConfig: const smithy_aws.S3ClientConfig(usePathStyle: true), pathResolver: pathResolver, bucket: testBucket, + awsRegion: testRegion, path: const StoragePath.fromString(testKey), options: const StorageUploadDataOptions( pluginOptions: S3UploadDataPluginOptions( diff --git a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/transfer/database_html_test.dart b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/transfer/database_html_test.dart index 73ae16eb75..9a92df808e 100644 --- a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/transfer/database_html_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/transfer/database_html_test.dart @@ -12,12 +12,16 @@ void main() { group('TransferDatabase for web', () { const testUploadId = 'test-upload-Id'; const testObjectKey = 'test-object-Key'; + const testBucketName = 'test-bucket-name'; + const testAwsRegion = 'test-aws-region'; final testCreatedAt = DateTime(2022, 1, 1); final testTransferRecord = TransferRecord( uploadId: testUploadId, objectKey: testObjectKey, createdAt: testCreatedAt, + bucketName: testBucketName, + awsRegion: testAwsRegion, ); final testTransferRecordJsonString = testTransferRecord.toJsonString(); diff --git a/packages/storage/amplify_storage_s3_dart/test/test_utils/mocks.dart b/packages/storage/amplify_storage_s3_dart/test/test_utils/mocks.dart index 1136704cde..1954fb75fa 100644 --- a/packages/storage/amplify_storage_s3_dart/test/test_utils/mocks.dart +++ b/packages/storage/amplify_storage_s3_dart/test/test_utils/mocks.dart @@ -27,3 +27,7 @@ class MockS3UploadTask extends Mock implements S3UploadTask {} class MockTransferDatabase extends Mock implements TransferDatabase {} class MockSmithyOperation extends Mock implements SmithyOperation {} + +class MockAmplifyUserAgent extends Mock implements AmplifyUserAgent {} + +class MockAWSHttpClient extends Mock implements AWSHttpClient {} From 8c45501b781f742ed4b9a2a38324bec2b3d735ac Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 11:24:38 -0700 Subject: [PATCH 14/42] allow the s3 list api to accept an optional bucket --- .../category/amplify_storage_category.dart | 1 + .../lib/src/types/storage/list_options.dart | 12 ++- .../example/integration_test/list_test.dart | 80 ++++++++++++++++--- .../lib/src/amplify_storage_s3_dart_impl.dart | 4 +- .../service/storage_s3_service_impl.dart | 10 ++- .../test/amplify_storage_s3_dart_test.dart | 1 + 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/packages/amplify_core/lib/src/category/amplify_storage_category.dart b/packages/amplify_core/lib/src/category/amplify_storage_category.dart index c3f4cfbe2f..84c9603c03 100644 --- a/packages/amplify_core/lib/src/category/amplify_storage_category.dart +++ b/packages/amplify_core/lib/src/category/amplify_storage_category.dart @@ -153,6 +153,7 @@ class StorageCategory extends AmplifyCategory { data: data, onProgress: onProgress, options: options, + bucket: bucket, ), ); } diff --git a/packages/amplify_core/lib/src/types/storage/list_options.dart b/packages/amplify_core/lib/src/types/storage/list_options.dart index c046f90d55..8e292040b1 100644 --- a/packages/amplify_core/lib/src/types/storage/list_options.dart +++ b/packages/amplify_core/lib/src/types/storage/list_options.dart @@ -1,7 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:aws_common/aws_common.dart'; +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; /// {@template amplify_core.storage.list_options} /// Configurable options for `Amplify.Storage.list`. @@ -15,6 +18,7 @@ class StorageListOptions const StorageListOptions({ this.pageSize = 1000, this.nextToken, + this.bucket, this.pluginOptions, }); @@ -26,9 +30,12 @@ class StorageListOptions /// {@macro amplify_core.storage.list_plugin_options} final StorageListPluginOptions? pluginOptions; + + /// an optional bucket to specify which bucket to return the list for + final StorageBucket? bucket; @override - List get props => [pageSize, nextToken, pluginOptions]; + List get props => [pageSize, nextToken, pluginOptions, bucket]; @override String get runtimeTypeName => 'StorageListOptions'; @@ -37,6 +44,7 @@ class StorageListOptions Map toJson() => { 'pageSize': pageSize, 'nextToken': nextToken, + 'bucket': bucket, 'pluginOptions': pluginOptions?.toJson(), }; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart index 22e2a2766b..8cb8eb43af 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart @@ -20,18 +20,28 @@ void main() { '$uniquePrefix/file2.txt', '$uniquePrefix/subdir/file3.txt', '$uniquePrefix/subdir2#file4.txt', + '$uniquePrefix/file5.txt', + '$uniquePrefix/file6.txt', + '$uniquePrefix/subdir3/file7.txt', + '$uniquePrefix/subdir4#file8.txt', ]; group('standard config', () { setUpAll(() async { await configure(amplifyEnvironments['main']!); - - for (final path in uploadedPaths) { + for (var i = 0; i < 4; i++) { await Amplify.Storage.uploadData( - path: StoragePath.fromString(path), + path: StoragePath.fromString(uploadedPaths[i]), data: StorageDataPayload.bytes('test content'.codeUnits), + bucket: StorageBucket.fromOutputs('Storage Integ Test main bucket'), + ).result; + } + for (var i = 4; i < 8; i++) { + await Amplify.Storage.uploadData( + path: StoragePath.fromString(uploadedPaths[i]), + data: StorageDataPayload.bytes('test content'.codeUnits), + bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket'), ).result; } - for (final path in uploadedPaths) { addTearDownPath(StoragePath.fromString(path)); } @@ -39,13 +49,22 @@ void main() { group('list() without options', () { testWidgets('should list all files with unique prefix', (_) async { - final listResult = await Amplify.Storage.list( + final listResultMainBucket = await Amplify.Storage.list( path: StoragePath.fromString(uniquePrefix), ).result; - - for (final uploadedPath in uploadedPaths) { + final listResultSecondaryBucket = await Amplify.Storage.list( + path: StoragePath.fromString(uniquePrefix), + options: StorageListOptions(bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket')), + ).result; + for (var i = 0; i < 4; i++) { + expect( + listResultMainBucket.items.any((item) => item.path == uploadedPaths[i]), + isTrue, + ); + } + for (var i = 4; i < 8; i++) { expect( - listResult.items.any((item) => item.path == uploadedPath), + listResultSecondaryBucket.items.any((item) => item.path == uploadedPaths[i]), isTrue, ); } @@ -101,6 +120,17 @@ void main() { ), ).result as S3ListResult; + final listResultSecondaryBucket = await Amplify.Storage.list( + path: StoragePath.fromString('$uniquePrefix/'), + options: StorageListOptions( + pluginOptions: const S3ListPluginOptions( + excludeSubPaths: true, + delimiter: '#', + ), + bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket'), + ), + ).result as S3ListResult; + expect(listResult.items.length, 3); expect(listResult.items.first.path, contains('file1.txt')); @@ -110,6 +140,16 @@ void main() { '$uniquePrefix/subdir2#', ); expect(listResult.metadata.delimiter, '#'); + + expect(listResultSecondaryBucket.items.length, 3); + expect(listResultSecondaryBucket.items.first.path, contains('file5.txt')); + + expect(listResultSecondaryBucket.metadata.subPaths.length, 1); + expect( + listResultSecondaryBucket.metadata.subPaths.first, + '$uniquePrefix/subdir4#', + ); + expect(listResultSecondaryBucket.metadata.delimiter, '#'); }); }); @@ -123,6 +163,17 @@ void main() { expect(listResult.items.length, 2); expect(listResult.items.first.path, contains('file1.txt')); + + final listResultSecondaryBucket = await Amplify.Storage.list( + path: StoragePath.fromString(uniquePrefix), + options: StorageListOptions( + pageSize: 2, + bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket'), + ), + ).result; + + expect(listResultSecondaryBucket.items.length, 2); + expect(listResultSecondaryBucket.items.first.path, contains('file5.txt')); }); testWidgets('should list files with pagination', (_) async { @@ -157,8 +208,19 @@ void main() { ), ).result; - expect(listResult.items.length, uploadedPaths.length); + expect(listResult.items.length, uploadedPaths.length~/2); expect(listResult.nextToken, isNull); + + final listResultSecondaryBucket = await Amplify.Storage.list( + path: StoragePath.fromString(uniquePrefix), + options: StorageListOptions( + pluginOptions: const S3ListPluginOptions.listAll(), + bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket'), + ), + ).result; + + expect(listResultSecondaryBucket.items.length, uploadedPaths.length~/2); + expect(listResultSecondaryBucket.nextToken, isNull); }); }); }); 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 f4ec4035c4..3afda601ce 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 @@ -124,7 +124,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface }); } - @override + @override S3ListOperation list({ required StoragePath path, StorageListOptions? options, @@ -136,6 +136,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface final s3Options = StorageListOptions( pluginOptions: s3PluginOptions, nextToken: options?.nextToken, + bucket: options?.bucket, pageSize: options?.pageSize ?? 1000, ); @@ -151,6 +152,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); } + @override S3GetPropertiesOperation getProperties({ required StoragePath path, 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 86a355eac9..0191ecff42 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 @@ -125,11 +125,12 @@ class StorageS3Service { const S3ListPluginOptions(); final resolvedPath = await _pathResolver.resolvePath(path: path); + final s3ClientInfo = options.bucket == null ? getS3ClientInfo() : getS3ClientInfo(storageBucket: options.bucket); if (!s3PluginOptions.listAll) { final request = s3.ListObjectsV2Request.build((builder) { builder - ..bucket = _storageOutputs.bucketName + ..bucket = s3ClientInfo.bucketName ..prefix = resolvedPath ..maxKeys = options.pageSize ..continuationToken = options.nextToken @@ -140,7 +141,7 @@ class StorageS3Service { try { return S3ListResult.fromPaginatedResult( - await _defaultS3Client.listObjectsV2(request).result, + await s3ClientInfo.client.listObjectsV2(request).result, ); } on smithy.UnknownSmithyHttpException catch (error) { // S3Client.headObject may return 403 error @@ -156,14 +157,15 @@ class StorageS3Service { try { final request = s3.ListObjectsV2Request.build((builder) { builder - ..bucket = _storageOutputs.bucketName + // ignore: invalid_use_of_internal_member + ..bucket = options.bucket == null ? _storageOutputs.bucketName : options.bucket!.resolveBucketInfo(_storageOutputs).bucketName ..prefix = resolvedPath ..delimiter = s3PluginOptions.excludeSubPaths ? s3PluginOptions.delimiter : null; }); - listResult = await _defaultS3Client.listObjectsV2(request).result; + listResult = await s3ClientInfo.client.listObjectsV2(request).result; recursiveResult = S3ListResult.fromPaginatedResult( listResult, ); 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 405b5c9bf8..9cd6013373 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 @@ -140,6 +140,7 @@ void main() { const testOptions = StorageListOptions( pluginOptions: S3ListPluginOptions(excludeSubPaths: true), nextToken: 'next-token-123', + bucket: StorageBucket.fromBucketInfo(BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2')), pageSize: 2, ); From e734d8e7e3e08c3e528886d413f205c0f3e6f304 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 12:29:13 -0700 Subject: [PATCH 15/42] formatting --- .../example/integration_test/list_test.dart | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart index 8cb8eb43af..d1142b4a5c 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart @@ -39,7 +39,8 @@ void main() { await Amplify.Storage.uploadData( path: StoragePath.fromString(uploadedPaths[i]), data: StorageDataPayload.bytes('test content'.codeUnits), - bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket'), + bucket: StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket'), ).result; } for (final path in uploadedPaths) { @@ -54,17 +55,21 @@ void main() { ).result; final listResultSecondaryBucket = await Amplify.Storage.list( path: StoragePath.fromString(uniquePrefix), - options: StorageListOptions(bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket')), + options: StorageListOptions( + bucket: StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket')), ).result; for (var i = 0; i < 4; i++) { expect( - listResultMainBucket.items.any((item) => item.path == uploadedPaths[i]), + listResultMainBucket.items + .any((item) => item.path == uploadedPaths[i]), isTrue, ); } for (var i = 4; i < 8; i++) { expect( - listResultSecondaryBucket.items.any((item) => item.path == uploadedPaths[i]), + listResultSecondaryBucket.items + .any((item) => item.path == uploadedPaths[i]), isTrue, ); } @@ -127,10 +132,11 @@ void main() { excludeSubPaths: true, delimiter: '#', ), - bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket'), + bucket: StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket'), ), ).result as S3ListResult; - + expect(listResult.items.length, 3); expect(listResult.items.first.path, contains('file1.txt')); @@ -142,7 +148,8 @@ void main() { expect(listResult.metadata.delimiter, '#'); expect(listResultSecondaryBucket.items.length, 3); - expect(listResultSecondaryBucket.items.first.path, contains('file5.txt')); + expect(listResultSecondaryBucket.items.first.path, + contains('file5.txt')); expect(listResultSecondaryBucket.metadata.subPaths.length, 1); expect( @@ -168,12 +175,14 @@ void main() { path: StoragePath.fromString(uniquePrefix), options: StorageListOptions( pageSize: 2, - bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket'), + bucket: StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket'), ), ).result; expect(listResultSecondaryBucket.items.length, 2); - expect(listResultSecondaryBucket.items.first.path, contains('file5.txt')); + expect(listResultSecondaryBucket.items.first.path, + contains('file5.txt')); }); testWidgets('should list files with pagination', (_) async { @@ -208,18 +217,20 @@ void main() { ), ).result; - expect(listResult.items.length, uploadedPaths.length~/2); + expect(listResult.items.length, uploadedPaths.length ~/ 2); expect(listResult.nextToken, isNull); final listResultSecondaryBucket = await Amplify.Storage.list( path: StoragePath.fromString(uniquePrefix), options: StorageListOptions( pluginOptions: const S3ListPluginOptions.listAll(), - bucket: StorageBucket.fromOutputs('Storage Integ Test secondary bucket'), + bucket: StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket'), ), ).result; - expect(listResultSecondaryBucket.items.length, uploadedPaths.length~/2); + expect(listResultSecondaryBucket.items.length, + uploadedPaths.length ~/ 2); expect(listResultSecondaryBucket.nextToken, isNull); }); }); From 119b84336dcfcfe6f2d80f3f214e93eb744e8448 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 12:43:07 -0700 Subject: [PATCH 16/42] trailing commas --- .../example/integration_test/list_test.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart index d1142b4a5c..7ed2a3caaf 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart @@ -40,7 +40,7 @@ void main() { path: StoragePath.fromString(uploadedPaths[i]), data: StorageDataPayload.bytes('test content'.codeUnits), bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket'), + 'Storage Integ Test secondary bucket',), ).result; } for (final path in uploadedPaths) { @@ -57,7 +57,7 @@ void main() { path: StoragePath.fromString(uniquePrefix), options: StorageListOptions( bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket')), + 'Storage Integ Test secondary bucket',),), ).result; for (var i = 0; i < 4; i++) { expect( @@ -133,7 +133,7 @@ void main() { delimiter: '#', ), bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket'), + 'Storage Integ Test secondary bucket',), ), ).result as S3ListResult; @@ -149,7 +149,7 @@ void main() { expect(listResultSecondaryBucket.items.length, 3); expect(listResultSecondaryBucket.items.first.path, - contains('file5.txt')); + contains('file5.txt'),); expect(listResultSecondaryBucket.metadata.subPaths.length, 1); expect( @@ -176,13 +176,13 @@ void main() { options: StorageListOptions( pageSize: 2, bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket'), + 'Storage Integ Test secondary bucket',), ), ).result; expect(listResultSecondaryBucket.items.length, 2); expect(listResultSecondaryBucket.items.first.path, - contains('file5.txt')); + contains('file5.txt'),); }); testWidgets('should list files with pagination', (_) async { @@ -225,12 +225,12 @@ void main() { options: StorageListOptions( pluginOptions: const S3ListPluginOptions.listAll(), bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket'), + 'Storage Integ Test secondary bucket',), ), ).result; expect(listResultSecondaryBucket.items.length, - uploadedPaths.length ~/ 2); + uploadedPaths.length ~/ 2,); expect(listResultSecondaryBucket.nextToken, isNull); }); }); From f3956cfdda7f857b4f86d8fb154f62ee13e5084e Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 14:08:47 -0700 Subject: [PATCH 17/42] formatting --- .../example/integration_test/list_test.dart | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart index 7ed2a3caaf..3977b58b82 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart @@ -40,7 +40,8 @@ void main() { path: StoragePath.fromString(uploadedPaths[i]), data: StorageDataPayload.bytes('test content'.codeUnits), bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket',), + 'Storage Integ Test secondary bucket', + ), ).result; } for (final path in uploadedPaths) { @@ -56,8 +57,10 @@ void main() { final listResultSecondaryBucket = await Amplify.Storage.list( path: StoragePath.fromString(uniquePrefix), options: StorageListOptions( - bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket',),), + bucket: StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket', + ), + ), ).result; for (var i = 0; i < 4; i++) { expect( @@ -133,7 +136,8 @@ void main() { delimiter: '#', ), bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket',), + 'Storage Integ Test secondary bucket', + ), ), ).result as S3ListResult; @@ -148,8 +152,10 @@ void main() { expect(listResult.metadata.delimiter, '#'); expect(listResultSecondaryBucket.items.length, 3); - expect(listResultSecondaryBucket.items.first.path, - contains('file5.txt'),); + expect( + listResultSecondaryBucket.items.first.path, + contains('file5.txt'), + ); expect(listResultSecondaryBucket.metadata.subPaths.length, 1); expect( @@ -176,13 +182,16 @@ void main() { options: StorageListOptions( pageSize: 2, bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket',), + 'Storage Integ Test secondary bucket', + ), ), ).result; expect(listResultSecondaryBucket.items.length, 2); - expect(listResultSecondaryBucket.items.first.path, - contains('file5.txt'),); + expect( + listResultSecondaryBucket.items.first.path, + contains('file5.txt'), + ); }); testWidgets('should list files with pagination', (_) async { @@ -225,12 +234,15 @@ void main() { options: StorageListOptions( pluginOptions: const S3ListPluginOptions.listAll(), bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket',), + 'Storage Integ Test secondary bucket', + ), ), ).result; - expect(listResultSecondaryBucket.items.length, - uploadedPaths.length ~/ 2,); + expect( + listResultSecondaryBucket.items.length, + uploadedPaths.length ~/ 2, + ); expect(listResultSecondaryBucket.nextToken, isNull); }); }); From b14468cc670ab1f60b508ce26e5b518d070af78f Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 15:28:36 -0700 Subject: [PATCH 18/42] format --- .../amplify_core/lib/src/types/storage/list_options.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/amplify_core/lib/src/types/storage/list_options.dart b/packages/amplify_core/lib/src/types/storage/list_options.dart index 8e292040b1..72a2577135 100644 --- a/packages/amplify_core/lib/src/types/storage/list_options.dart +++ b/packages/amplify_core/lib/src/types/storage/list_options.dart @@ -1,9 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - import 'package:amplify_core/amplify_core.dart'; /// {@template amplify_core.storage.list_options} @@ -30,7 +27,7 @@ class StorageListOptions /// {@macro amplify_core.storage.list_plugin_options} final StorageListPluginOptions? pluginOptions; - + /// an optional bucket to specify which bucket to return the list for final StorageBucket? bucket; From ad837d778e01662c606f6ab0e701ae3a7769e7d5 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 16:02:31 -0700 Subject: [PATCH 19/42] fixed formatting and annotated internal member use --- .../lib/src/amplify_storage_s3_dart_impl.dart | 3 +-- .../service/storage_s3_service_impl.dart | 10 +++++++--- .../test/amplify_storage_s3_dart_test.dart | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) 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 3afda601ce..f51ebe37a6 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 @@ -124,7 +124,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface }); } - @override + @override S3ListOperation list({ required StoragePath path, StorageListOptions? options, @@ -152,7 +152,6 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); } - @override S3GetPropertiesOperation getProperties({ required StoragePath path, 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 0191ecff42..cb30427575 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 @@ -125,7 +125,9 @@ class StorageS3Service { const S3ListPluginOptions(); final resolvedPath = await _pathResolver.resolvePath(path: path); - final s3ClientInfo = options.bucket == null ? getS3ClientInfo() : getS3ClientInfo(storageBucket: options.bucket); + final s3ClientInfo = options.bucket == null + ? getS3ClientInfo() + : getS3ClientInfo(storageBucket: options.bucket); if (!s3PluginOptions.listAll) { final request = s3.ListObjectsV2Request.build((builder) { @@ -157,8 +159,10 @@ class StorageS3Service { try { final request = s3.ListObjectsV2Request.build((builder) { builder - // ignore: invalid_use_of_internal_member - ..bucket = options.bucket == null ? _storageOutputs.bucketName : options.bucket!.resolveBucketInfo(_storageOutputs).bucketName + ..bucket = options.bucket == null + ? _storageOutputs.bucketName + // ignore: invalid_use_of_internal_member + : options.bucket!.resolveBucketInfo(_storageOutputs).bucketName ..prefix = resolvedPath ..delimiter = s3PluginOptions.excludeSubPaths ? s3PluginOptions.delimiter 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 9cd6013373..a69726bb1c 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 @@ -140,7 +140,8 @@ void main() { const testOptions = StorageListOptions( pluginOptions: S3ListPluginOptions(excludeSubPaths: true), nextToken: 'next-token-123', - bucket: StorageBucket.fromBucketInfo(BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2')), + bucket: StorageBucket.fromBucketInfo( + BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2')), pageSize: 2, ); From 05bc0a840210078911589b6d848d7d114affc1b7 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Thu, 17 Oct 2024 08:29:49 -0700 Subject: [PATCH 20/42] format --- .../test/amplify_storage_s3_dart_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a69726bb1c..3b69ff6574 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 @@ -141,7 +141,7 @@ void main() { pluginOptions: S3ListPluginOptions(excludeSubPaths: true), nextToken: 'next-token-123', bucket: StorageBucket.fromBucketInfo( - BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2')), + BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2'),), pageSize: 2, ); From 0c023fdb87dbc815132c3d0ddececa7f924e6117 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Thu, 17 Oct 2024 08:47:47 -0700 Subject: [PATCH 21/42] format --- .../test/amplify_storage_s3_dart_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3b69ff6574..6f97dd7abd 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 @@ -141,7 +141,8 @@ void main() { pluginOptions: S3ListPluginOptions(excludeSubPaths: true), nextToken: 'next-token-123', bucket: StorageBucket.fromBucketInfo( - BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2'),), + BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2'), + ), pageSize: 2, ); From 48940f9a3b8411f54754fe4b1fc80bb2e2be3be2 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Fri, 18 Oct 2024 13:58:27 -0700 Subject: [PATCH 22/42] refactored how the default bucket is passed along through the list api --- .../service/storage_s3_service_impl.dart | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) 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 cb30427575..65ae712b0c 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 @@ -125,9 +125,7 @@ class StorageS3Service { const S3ListPluginOptions(); final resolvedPath = await _pathResolver.resolvePath(path: path); - final s3ClientInfo = options.bucket == null - ? getS3ClientInfo() - : getS3ClientInfo(storageBucket: options.bucket); + final s3ClientInfo = getS3ClientInfo(storageBucket: options.bucket); if (!s3PluginOptions.listAll) { final request = s3.ListObjectsV2Request.build((builder) { @@ -159,10 +157,8 @@ class StorageS3Service { try { final request = s3.ListObjectsV2Request.build((builder) { builder - ..bucket = options.bucket == null - ? _storageOutputs.bucketName - // ignore: invalid_use_of_internal_member - : options.bucket!.resolveBucketInfo(_storageOutputs).bucketName + + ..bucket = s3ClientInfo.bucketName ..prefix = resolvedPath ..delimiter = s3PluginOptions.excludeSubPaths ? s3PluginOptions.delimiter From 1ed7691edf16557af325be18723a451d86dbebd0 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:00:34 -0700 Subject: [PATCH 23/42] updated tests to test default bucket functionality --- .../example/integration_test/list_test.dart | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart index 3977b58b82..f1bfd4fe01 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart @@ -26,22 +26,25 @@ void main() { '$uniquePrefix/subdir4#file8.txt', ]; group('standard config', () { + final mainBucket = + StorageBucket.fromOutputs('Storage Integ Test main bucket'); + final secondaryBucket = StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket', + ); setUpAll(() async { await configure(amplifyEnvironments['main']!); for (var i = 0; i < 4; i++) { await Amplify.Storage.uploadData( path: StoragePath.fromString(uploadedPaths[i]), data: StorageDataPayload.bytes('test content'.codeUnits), - bucket: StorageBucket.fromOutputs('Storage Integ Test main bucket'), + bucket: mainBucket, ).result; } for (var i = 4; i < 8; i++) { await Amplify.Storage.uploadData( path: StoragePath.fromString(uploadedPaths[i]), data: StorageDataPayload.bytes('test content'.codeUnits), - bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket', - ), + bucket: secondaryBucket, ).result; } for (final path in uploadedPaths) { @@ -51,15 +54,14 @@ void main() { group('list() without options', () { testWidgets('should list all files with unique prefix', (_) async { + // this will use the main bucket by default when no optional bucket is specified final listResultMainBucket = await Amplify.Storage.list( path: StoragePath.fromString(uniquePrefix), ).result; final listResultSecondaryBucket = await Amplify.Storage.list( path: StoragePath.fromString(uniquePrefix), options: StorageListOptions( - bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket', - ), + bucket: secondaryBucket, ), ).result; for (var i = 0; i < 4; i++) { @@ -135,9 +137,7 @@ void main() { excludeSubPaths: true, delimiter: '#', ), - bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket', - ), + bucket: secondaryBucket, ), ).result as S3ListResult; @@ -181,9 +181,7 @@ void main() { path: StoragePath.fromString(uniquePrefix), options: StorageListOptions( pageSize: 2, - bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket', - ), + bucket: secondaryBucket, ), ).result; @@ -233,9 +231,7 @@ void main() { path: StoragePath.fromString(uniquePrefix), options: StorageListOptions( pluginOptions: const S3ListPluginOptions.listAll(), - bucket: StorageBucket.fromOutputs( - 'Storage Integ Test secondary bucket', - ), + bucket: secondaryBucket, ), ).result; From 980c0d656944b3839cb05ddc497ee48533c66cbe Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:11:34 -0700 Subject: [PATCH 24/42] cleaning up commits --- .../src/storage_s3_service/service/storage_s3_service_impl.dart | 1 - 1 file changed, 1 deletion(-) 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 65ae712b0c..4781d40aac 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 @@ -157,7 +157,6 @@ class StorageS3Service { try { final request = s3.ListObjectsV2Request.build((builder) { builder - ..bucket = s3ClientInfo.bucketName ..prefix = resolvedPath ..delimiter = s3PluginOptions.excludeSubPaths From 8f2e9a1815477b004903ca734adf52016df8624d Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 11:24:38 -0700 Subject: [PATCH 25/42] allow the s3 list api to accept an optional bucket --- packages/amplify_core/lib/src/types/storage/list_options.dart | 3 +++ .../lib/src/amplify_storage_s3_dart_impl.dart | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/amplify_core/lib/src/types/storage/list_options.dart b/packages/amplify_core/lib/src/types/storage/list_options.dart index 72a2577135..59c7bd6621 100644 --- a/packages/amplify_core/lib/src/types/storage/list_options.dart +++ b/packages/amplify_core/lib/src/types/storage/list_options.dart @@ -27,6 +27,9 @@ class StorageListOptions /// {@macro amplify_core.storage.list_plugin_options} final StorageListPluginOptions? pluginOptions; + + /// an optional bucket to specify which bucket to return the list for + final StorageBucket? bucket; /// an optional bucket to specify which bucket to return the list for final StorageBucket? bucket; 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 f51ebe37a6..3afda601ce 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 @@ -124,7 +124,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface }); } - @override + @override S3ListOperation list({ required StoragePath path, StorageListOptions? options, @@ -152,6 +152,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); } + @override S3GetPropertiesOperation getProperties({ required StoragePath path, From 976c6ea80e7a50d7bc3ad3202c1a1bd89bde9cb2 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 15:28:36 -0700 Subject: [PATCH 26/42] format --- packages/amplify_core/lib/src/types/storage/list_options.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amplify_core/lib/src/types/storage/list_options.dart b/packages/amplify_core/lib/src/types/storage/list_options.dart index 59c7bd6621..43aa1b0775 100644 --- a/packages/amplify_core/lib/src/types/storage/list_options.dart +++ b/packages/amplify_core/lib/src/types/storage/list_options.dart @@ -27,7 +27,7 @@ class StorageListOptions /// {@macro amplify_core.storage.list_plugin_options} final StorageListPluginOptions? pluginOptions; - + /// an optional bucket to specify which bucket to return the list for final StorageBucket? bucket; From 81972ab732f1fcfa2a325e2f705b61061bcea613 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 16 Oct 2024 16:02:31 -0700 Subject: [PATCH 27/42] fixed formatting and annotated internal member use --- .../lib/src/amplify_storage_s3_dart_impl.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 3afda601ce..f51ebe37a6 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 @@ -124,7 +124,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface }); } - @override + @override S3ListOperation list({ required StoragePath path, StorageListOptions? options, @@ -152,7 +152,6 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); } - @override S3GetPropertiesOperation getProperties({ required StoragePath path, From 9f4f91adf3a2dd14d2331252a8cb894d5ee0a79a Mon Sep 17 00:00:00 2001 From: NikaHsn Date: Fri, 18 Oct 2024 13:51:57 -0700 Subject: [PATCH 28/42] chore(dep): update mime version to ^2.0.0 (#5568) --- .../repo_snapshot/packages/aws_common/pubspec.yaml | 2 +- .../version_bump/data/repo_snapshot/pubspec.yaml | 2 +- .../amplify_auth_cognito_dart/example/pubspec.yaml | 2 +- packages/aws_common/pubspec.yaml | 2 +- packages/test/pub_server/lib/src/server.dart | 13 ++++++++++--- packages/test/pub_server/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 7 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/aft/test/version_bump/data/repo_snapshot/packages/aws_common/pubspec.yaml b/packages/aft/test/version_bump/data/repo_snapshot/packages/aws_common/pubspec.yaml index 55ac36694f..f4218dcd9f 100644 --- a/packages/aft/test/version_bump/data/repo_snapshot/packages/aws_common/pubspec.yaml +++ b/packages/aft/test/version_bump/data/repo_snapshot/packages/aws_common/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: json_annotation: ">=4.9.0 <4.10.0" logging: ^1.0.0 meta: ^1.7.0 - mime: ^1.0.0 + mime: ">=1.0.0 <3.0.0" os_detect: ^2.0.2 path: ">=1.8.0 <2.0.0" stream_transform: ^2.0.0 diff --git a/packages/aft/test/version_bump/data/repo_snapshot/pubspec.yaml b/packages/aft/test/version_bump/data/repo_snapshot/pubspec.yaml index 7038fca683..392888e035 100644 --- a/packages/aft/test/version_bump/data/repo_snapshot/pubspec.yaml +++ b/packages/aft/test/version_bump/data/repo_snapshot/pubspec.yaml @@ -35,7 +35,7 @@ dependencies: intl: ">=0.18.0 <1.0.0" json_annotation: ">=4.9.0 <4.10.0" json_serializable: 6.8.0 - mime: ^1.0.0 + mime: ">=1.0.0 <3.0.0" oauth2: ^2.0.2 package_info_plus: ^8.0.0 pigeon: ^11.0.0 diff --git a/packages/auth/amplify_auth_cognito_dart/example/pubspec.yaml b/packages/auth/amplify_auth_cognito_dart/example/pubspec.yaml index 7798bd608c..98b9611bce 100644 --- a/packages/auth/amplify_auth_cognito_dart/example/pubspec.yaml +++ b/packages/auth/amplify_auth_cognito_dart/example/pubspec.yaml @@ -28,7 +28,7 @@ dev_dependencies: checks: ^0.3.0 cli_script: ^0.3.0 io: ^1.0.0 - mime: ^1.0.0 + mime: ">=1.0.0 <3.0.0" path: ">=1.8.0 <2.0.0" shelf: ^1.4.0 test: ^1.22.1 diff --git a/packages/aws_common/pubspec.yaml b/packages/aws_common/pubspec.yaml index 00ff68a7e3..250913a29b 100644 --- a/packages/aws_common/pubspec.yaml +++ b/packages/aws_common/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: json_annotation: ">=4.9.0 <4.10.0" logging: ^1.0.0 meta: ^1.7.0 - mime: ^1.0.0 + mime: ">=1.0.0 <3.0.0" os_detect: ^2.0.2 path: ">=1.8.0 <2.0.0" stream_transform: ^2.0.0 diff --git a/packages/test/pub_server/lib/src/server.dart b/packages/test/pub_server/lib/src/server.dart index 0aa06950f6..5cad678038 100644 --- a/packages/test/pub_server/lib/src/server.dart +++ b/packages/test/pub_server/lib/src/server.dart @@ -16,7 +16,7 @@ import 'package:pub_server/src/database.dart'; import 'package:pub_server/src/models.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:shelf/shelf.dart'; -import 'package:shelf_multipart/form_data.dart'; +import 'package:shelf_multipart/shelf_multipart.dart'; import 'package:shelf_router/shelf_router.dart'; part 'server.g.dart'; @@ -146,15 +146,22 @@ class PubServer { /// https://github.com/dart-lang/pub/blob/master/doc/repository-spec-v2.md#publishing-packages @Route.post('/api/packages/versions/newUpload') Future upload(Request request) async { - if (!request.isMultipartForm) { + if (request.multipart() == null) { return Response.badRequest( body: 'Expected multipart request', ); } + final formDataRequest = request.formData(); + if (formDataRequest == null) { + return Response.badRequest( + body: 'Expected multipart request to contain form data', + ); + } + final FormData packageTar; try { - packageTar = await request.multipartFormData.singleWhere( + packageTar = await formDataRequest.formData.singleWhere( (part) => part.filename == 'package.tar.gz', ); } on StateError { diff --git a/packages/test/pub_server/pubspec.yaml b/packages/test/pub_server/pubspec.yaml index 613e271ba2..b29627fbb2 100644 --- a/packages/test/pub_server/pubspec.yaml +++ b/packages/test/pub_server/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: pub_semver: ^2.1.3 pubspec_parse: ^1.2.2 shelf: ^1.4.0 - shelf_multipart: ^1.0.0 + shelf_multipart: ^2.0.0 shelf_router: ^1.1.3 yaml: ^3.1.1 diff --git a/pubspec.yaml b/pubspec.yaml index 7038fca683..392888e035 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,7 @@ dependencies: intl: ">=0.18.0 <1.0.0" json_annotation: ">=4.9.0 <4.10.0" json_serializable: 6.8.0 - mime: ^1.0.0 + mime: ">=1.0.0 <3.0.0" oauth2: ^2.0.2 package_info_plus: ^8.0.0 pigeon: ^11.0.0 From 15b62e42ffba853bc0c893693f23edccaa64c3bf Mon Sep 17 00:00:00 2001 From: Tyler-Larkin Date: Fri, 18 Oct 2024 15:49:49 -0700 Subject: [PATCH 29/42] fix(api): Reconnect WebSocket when resuming app from a paused state (#5567) * fix(api): Reconnect WebSocket when resuming app from a paused state --- packages/api/amplify_api/lib/amplify_api.dart | 6 +- .../amplify_api/lib/src/api_plugin_impl.dart | 2 + .../lib/src/flutter_life_cycle.dart | 42 ++++ .../lib/amplify_api_dart.dart | 1 + .../lib/src/api_plugin_impl.dart | 9 +- .../web_socket/blocs/web_socket_bloc.dart | 56 ++++++ .../web_socket/types/process_life_cycle.dart | 33 +++ .../web_socket/types/web_socket_event.dart | 13 ++ .../amplify_api_dart/test/graphql_test.dart | 4 + packages/api/amplify_api_dart/test/util.dart | 11 + .../test/web_socket/web_socket_bloc_test.dart | 190 ++++++++++++++++++ 11 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 packages/api/amplify_api/lib/src/flutter_life_cycle.dart create mode 100644 packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/process_life_cycle.dart diff --git a/packages/api/amplify_api/lib/amplify_api.dart b/packages/api/amplify_api/lib/amplify_api.dart index d27a2141f7..c7dbfa3f0a 100644 --- a/packages/api/amplify_api/lib/amplify_api.dart +++ b/packages/api/amplify_api/lib/amplify_api.dart @@ -5,4 +5,8 @@ library amplify_api; export 'package:amplify_api/src/api_plugin_impl.dart'; export 'package:amplify_api_dart/amplify_api_dart.dart' - hide AmplifyAPIDart, ConnectivityPlatform, ConnectivityStatus; + hide + AmplifyAPIDart, + ConnectivityPlatform, + ProcessLifeCycle, + ConnectivityStatus; diff --git a/packages/api/amplify_api/lib/src/api_plugin_impl.dart b/packages/api/amplify_api/lib/src/api_plugin_impl.dart index abd05fe314..dd23b97739 100644 --- a/packages/api/amplify_api/lib/src/api_plugin_impl.dart +++ b/packages/api/amplify_api/lib/src/api_plugin_impl.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_api/src/connectivity_plus_platform.dart'; +import 'package:amplify_api/src/flutter_life_cycle.dart'; import 'package:amplify_api_dart/amplify_api_dart.dart'; import 'package:amplify_core/amplify_core.dart'; @@ -14,6 +15,7 @@ class AmplifyAPI extends AmplifyAPIDart with AWSDebuggable { super.options, }) : super( connectivity: const ConnectivityPlusPlatform(), + processLifeCycle: FlutterLifeCycle(), ); @override diff --git a/packages/api/amplify_api/lib/src/flutter_life_cycle.dart b/packages/api/amplify_api/lib/src/flutter_life_cycle.dart new file mode 100644 index 0000000000..92405745eb --- /dev/null +++ b/packages/api/amplify_api/lib/src/flutter_life_cycle.dart @@ -0,0 +1,42 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:async'; + +import 'package:amplify_api_dart/amplify_api_dart.dart'; +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; + +/// {@template amplify_api.flutter_life_cycle} +/// Creates a stream of [ProcessStatus] mapped from [AppLifecycleListener](https://api.flutter.dev/flutter/widgets/AppLifecycleListener-class.html). +/// {@endtemplate} +@internal +class FlutterLifeCycle extends ProcessLifeCycle { + /// {@macro amplify_api.flutter_life_cycle} + FlutterLifeCycle() { + AppLifecycleListener( + onStateChange: _onStateChange, + ); + } + + final _stateController = + StreamController.broadcast(sync: true); + + @override + Stream get onStateChanged => _stateController.stream; + + void _onStateChange(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.detached: + _stateController.add(ProcessStatus.detached); + case AppLifecycleState.paused: + _stateController.add(ProcessStatus.paused); + case AppLifecycleState.hidden: + _stateController.add(ProcessStatus.hidden); + case AppLifecycleState.inactive: + _stateController.add(ProcessStatus.inactive); + case AppLifecycleState.resumed: + _stateController.add(ProcessStatus.resumed); + } + } +} diff --git a/packages/api/amplify_api_dart/lib/amplify_api_dart.dart b/packages/api/amplify_api_dart/lib/amplify_api_dart.dart index e25c15fb25..d689d1ccde 100644 --- a/packages/api/amplify_api_dart/lib/amplify_api_dart.dart +++ b/packages/api/amplify_api_dart/lib/amplify_api_dart.dart @@ -19,3 +19,4 @@ export 'src/graphql/model_helpers/model_subscriptions.dart'; /// Network connectivity util not needed by consumers of Flutter package amplify_api. export 'src/graphql/web_socket/types/connectivity_platform.dart'; +export 'src/graphql/web_socket/types/process_life_cycle.dart'; diff --git a/packages/api/amplify_api_dart/lib/src/api_plugin_impl.dart b/packages/api/amplify_api_dart/lib/src/api_plugin_impl.dart index 1cda540419..c4248bbc45 100644 --- a/packages/api/amplify_api_dart/lib/src/api_plugin_impl.dart +++ b/packages/api/amplify_api_dart/lib/src/api_plugin_impl.dart @@ -11,6 +11,7 @@ import 'package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.da import 'package:amplify_api_dart/src/graphql/web_socket/services/web_socket_service.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/state/web_socket_state.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/connectivity_platform.dart'; +import 'package:amplify_api_dart/src/graphql/web_socket/types/process_life_cycle.dart'; import 'package:amplify_api_dart/src/util/amplify_api_config.dart'; import 'package:amplify_api_dart/src/util/amplify_authorization_rest_client.dart'; import 'package:amplify_core/amplify_core.dart'; @@ -30,8 +31,10 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { AmplifyAPIDart({ APIPluginOptions options = const APIPluginOptions(), ConnectivityPlatform connectivity = const ConnectivityPlatform(), + ProcessLifeCycle processLifeCycle = const ProcessLifeCycle(), }) : _options = options, - _connectivity = connectivity { + _connectivity = connectivity, + _processLifeCycle = processLifeCycle { _options.authProviders.forEach(registerAuthProvider); } @@ -43,6 +46,9 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { /// Creates a stream representing network connectivity at the hardware level. final ConnectivityPlatform _connectivity; + /// Creates a stream representing the process life cycle state. + final ProcessLifeCycle _processLifeCycle; + /// A map of the keys from the Amplify API config with auth modes to HTTP clients /// to use for requests to that endpoint/auth mode. e.g. { "myEndpoint.AWS_IAM": AWSHttpClient} final Map _clientPool = {}; @@ -277,6 +283,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { wsService: AmplifyWebSocketService(), subscriptionOptions: _options.subscriptionOptions, connectivity: _connectivity, + processLifeCycle: _processLifeCycle, ); } diff --git a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart index 0903db904b..b3a3d90089 100644 --- a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart +++ b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart @@ -9,6 +9,7 @@ import 'package:amplify_api_dart/src/graphql/web_socket/services/web_socket_serv import 'package:amplify_api_dart/src/graphql/web_socket/state/web_socket_state.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/state/ws_subscriptions_state.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/connectivity_platform.dart'; +import 'package:amplify_api_dart/src/graphql/web_socket/types/process_life_cycle.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/subscriptions_event.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/web_socket_types.dart'; import 'package:amplify_core/amplify_core.dart' hide SubscriptionEvent; @@ -33,8 +34,10 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { required WebSocketService wsService, required GraphQLSubscriptionOptions subscriptionOptions, required ConnectivityPlatform connectivity, + required ProcessLifeCycle processLifeCycle, AWSHttpClient? pollClientOverride, }) : _connectivity = connectivity, + _processLifeCycle = processLifeCycle, _pollClient = pollClientOverride ?? AWSHttpClient() { final subBlocs = >{}; @@ -49,6 +52,7 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { ); final blocStream = _wsEventStream.asyncExpand(_eventTransformer); _networkSubscription = _getConnectivityStream(); + _processLifeCycleSubscription = _getProcessLifecycleStream(); _stateSubscription = blocStream.listen(_emit); add(const InitEvent()); } @@ -81,10 +85,14 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { late final Stream _wsEventStream = _wsEventController.stream; late final StreamSubscription _stateSubscription; late final StreamSubscription _networkSubscription; + late final StreamSubscription _processLifeCycleSubscription; /// Creates a stream representing network connectivity at the hardware level. final ConnectivityPlatform _connectivity; + /// Creates a stream representing the process life cycle state. + final ProcessLifeCycle _processLifeCycle; + /// The underlying event stream, used only in testing. @visibleForTesting Stream get wsEventStream => _wsEventStream; @@ -164,6 +172,8 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { yield* _networkLoss(); } else if (event is NetworkFoundEvent) { yield* _networkFound(); + } else if (event is ProcessResumeEvent) { + yield* _processResumed(); } else if (event is PollSuccessEvent) { yield* _pollSuccess(); } else if (event is PollFailedEvent) { @@ -328,6 +338,16 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { yield* const Stream.empty(); } + Stream _processResumed() async* { + final state = _currentState; + if (state is ConnectedState) { + yield state.reconnecting(networkState: NetworkState.disconnected); + add(const ReconnectEvent()); + } + // TODO(dnys1): Yield broken on web debug build. + yield* const Stream.empty(); + } + /// Handle successful polls Stream _pollSuccess() async* { // TODO(dnys1): Yield broken on web debug build. @@ -467,6 +487,7 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { await Future.wait([ // TODO(equartey): https://github.com/fluttercommunity/plus_plugins/issues/1382 if (!isWindows()) _networkSubscription.cancel(), + _processLifeCycleSubscription.cancel(), Future.value(_pollClient.close()), _stateSubscription.cancel(), _wsEventController.close(), @@ -507,6 +528,41 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { ); } + /// Process life cycle stream monitors when the process resumes from a paused state. + StreamSubscription _getProcessLifecycleStream() { + var prev = ProcessStatus.detached; + return _processLifeCycle.onStateChanged.listen( + (state) { + if (_isResuming(state, prev)) { + // ignore: invalid_use_of_internal_member + if (!WebSocketOptions.autoReconnect) { + _shutdownWithException( + const NetworkException( + 'Unable to recover network connection, web socket will close.', + recoverySuggestion: 'Avoid pausing the process.', + ), + StackTrace.current, + ); + } else { + add(const ProcessResumeEvent()); + } + } + + prev = state; + }, + onError: (Object e, StackTrace st) => + logger.error('Error in process life cycle stream $e, $st'), + ); + } + + bool _isResuming(ProcessStatus current, ProcessStatus previous) { + if (previous != ProcessStatus.paused) return false; + + return current == ProcessStatus.hidden || + current == ProcessStatus.inactive || + current == ProcessStatus.resumed; + } + Future _poll() async { try { final res = await _sendPollRequest(); diff --git a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/process_life_cycle.dart b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/process_life_cycle.dart new file mode 100644 index 0000000000..ca9ed6e085 --- /dev/null +++ b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/process_life_cycle.dart @@ -0,0 +1,33 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/// Possible process life cycle states +enum ProcessStatus { + /// Engine is running without a view. + detached, + + /// Application is not visible to the user or responding to user input. + paused, + + /// All views of an application are hidden. + hidden, + + /// A view of the application is visible, but none have input. + inactive, + + /// Default running mode. + resumed, +} + +/// {@template amplify_api_dart.process_life_cycle} +/// Used to create a stream representing the process life cycle state. +/// +/// The generated stream is empty. +/// {@endtemplate} +class ProcessLifeCycle { + /// {@macro amplify_api_dart.process_life_cycle} + const ProcessLifeCycle(); + + /// Generates a new stream of [ProcessStatus]. + Stream get onStateChanged => const Stream.empty(); +} diff --git a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/web_socket_event.dart b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/web_socket_event.dart index 907cd6f28d..a4f5d31b36 100644 --- a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/web_socket_event.dart +++ b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/web_socket_event.dart @@ -88,6 +88,19 @@ class NetworkLossEvent extends NetworkEvent { String get runtimeTypeName => 'NetworkLossEvent'; } +/// Discrete class for when the process is resumed +/// Triggers when AppLifecycleListener detects the process has been resumed. +class ProcessResumeEvent extends WebSocketEvent { + /// Create a process resumed event + const ProcessResumeEvent(); + + @override + String get runtimeTypeName => 'ProcessResumeEvent'; + + @override + Map toJson() => const {}; +} + /// Triggers when a successful ping to AppSync is made class PollSuccessEvent extends WebSocketEvent { /// Create a successful Poll event diff --git a/packages/api/amplify_api_dart/test/graphql_test.dart b/packages/api/amplify_api_dart/test/graphql_test.dart index c1399a0025..c2dd679594 100644 --- a/packages/api/amplify_api_dart/test/graphql_test.dart +++ b/packages/api/amplify_api_dart/test/graphql_test.dart @@ -280,6 +280,7 @@ void main() { 'payload': {'data': mockSubscriptionData}, }; + mockProcessLifeCycleController = StreamController(); mockWebSocketService = MockWebSocketService(); const subscriptionOptions = GraphQLSubscriptionOptions( pollInterval: Duration(seconds: 1), @@ -292,6 +293,7 @@ void main() { subscriptionOptions: subscriptionOptions, pollClientOverride: mockClient.client, connectivity: const ConnectivityPlatform(), + processLifeCycle: const MockProcessLifeCycle(), ); sendMockConnectionAck(mockWebSocketBloc!, mockWebSocketService!); @@ -599,6 +601,7 @@ void main() { }); test('should have correct state flow during a failure', () async { + mockProcessLifeCycleController = StreamController(); mockWebSocketService = MockWebSocketService(); const subscriptionOptions = GraphQLSubscriptionOptions( pollInterval: Duration(seconds: 1), @@ -613,6 +616,7 @@ void main() { subscriptionOptions: subscriptionOptions, pollClientOverride: mockClient.client, connectivity: const ConnectivityPlatform(), + processLifeCycle: const MockProcessLifeCycle(), ); final blocReady = Completer(); diff --git a/packages/api/amplify_api_dart/test/util.dart b/packages/api/amplify_api_dart/test/util.dart index f4d7e9e390..0d65f7ee93 100644 --- a/packages/api/amplify_api_dart/test/util.dart +++ b/packages/api/amplify_api_dart/test/util.dart @@ -10,6 +10,7 @@ import 'package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.da import 'package:amplify_api_dart/src/graphql/web_socket/services/web_socket_service.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/state/web_socket_state.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/connectivity_platform.dart'; +import 'package:amplify_api_dart/src/graphql/web_socket/types/process_life_cycle.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/web_socket_types.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_core/src/config/amplify_outputs/data/data_outputs.dart'; @@ -329,6 +330,16 @@ class MockConnectivity extends ConnectivityPlatform { mockNetworkStreamController.stream; } +late StreamController mockProcessLifeCycleController; + +class MockProcessLifeCycle extends ProcessLifeCycle { + const MockProcessLifeCycle(); + + @override + Stream get onStateChanged => + mockProcessLifeCycleController.stream; +} + /// Ensures a query predicate converts to JSON correctly. void testQueryPredicateTranslation( QueryPredicate? queryPredicate, diff --git a/packages/api/amplify_api_dart/test/web_socket/web_socket_bloc_test.dart b/packages/api/amplify_api_dart/test/web_socket/web_socket_bloc_test.dart index cf4ca8f769..297970268f 100644 --- a/packages/api/amplify_api_dart/test/web_socket/web_socket_bloc_test.dart +++ b/packages/api/amplify_api_dart/test/web_socket/web_socket_bloc_test.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/state/web_socket_state.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/connectivity_platform.dart'; +import 'package:amplify_api_dart/src/graphql/web_socket/types/process_life_cycle.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/web_socket_types.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:test/test.dart'; @@ -54,6 +55,7 @@ void main() { if (!noConnectivity) { mockNetworkStreamController = StreamController(); } + mockProcessLifeCycleController = StreamController(); mockPollClient = MockPollClient(); service = MockWebSocketService(); @@ -66,6 +68,7 @@ void main() { connectivity: noConnectivity ? const ConnectivityPlatform() : const MockConnectivity(), + processLifeCycle: const MockProcessLifeCycle(), ); sendMockConnectionAck(bloc!, service!); @@ -307,6 +310,149 @@ void main() { mockPollClient.induceTimeout = false; }); + test('should reconnect when process resumes', () async { + var dataCompleter = Completer(); + final subscribeEvent = SubscribeEvent( + subscriptionRequest, + () { + service!.channel.sink.add(mockDataString); + }, + ); + + final bloc = getWebSocketBloc(); + + expect( + bloc.stream, + emitsInOrder( + [ + isA(), + isA(), + isA(), + isA(), + isA(), + isA(), + ], + ), + ); + + bloc.subscribe(subscribeEvent).listen( + expectAsync1( + (event) { + expect(event.data, json.encode(mockSubscriptionData)); + dataCompleter.complete(event.data); + }, + count: 2, + ), + ); + + await dataCompleter.future; + dataCompleter = Completer(); + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + + await expectLater(bloc.stream, emitsThrough(isA())); + + service!.channel.sink.add(mockDataString); + await dataCompleter.future; + }); + + test('should throttle reconnect after repeatedly resuming', () async { + final blocReady = Completer(); + final subscribeEvent = SubscribeEvent( + subscriptionRequest, + blocReady.complete, + ); + + final bloc = getWebSocketBloc(); + + expect( + bloc.stream, + emitsInOrder( + [ + isA(), + isA(), + isA(), + isA(), + isA(), + isA(), + ], + ), + reason: + 'Bloc should debounce multiple reconnection triggers while resuming.', + ); + + bloc.subscribe( + subscribeEvent, + ); + + await blocReady.future; + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed) + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed) + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed) + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed) + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + }); + + test('should reconnect multiple times after resuming', () async { + final blocReady = Completer(); + final subscribeEvent = SubscribeEvent( + subscriptionRequest, + blocReady.complete, + ); + + final bloc = getWebSocketBloc() + ..subscribe( + subscribeEvent, + ); + + await blocReady.future; + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + + await expectLater(bloc.stream, emitsThrough(isA())); + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + + await expectLater(bloc.stream, emitsThrough(isA())); + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + + await expectLater(bloc.stream, emitsThrough(isA())); + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + + await expectLater(bloc.stream, emitsThrough(isA())); + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + + await expectLater(bloc.stream, emitsThrough(isA())); + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + + await expectLater(bloc.stream, emitsThrough(isA())); + }); + test( 'subscribe() ignores a WebSocket message that comes while the bloc is disconnected', () async { @@ -348,6 +494,7 @@ void main() { final badService = MockWebSocketService(badInit: true); mockNetworkStreamController = StreamController(); + mockProcessLifeCycleController = StreamController(); final bloc = WebSocketBloc( config: testApiKeyConfig, authProviderRepo: getTestAuthProviderRepo(), @@ -355,6 +502,7 @@ void main() { subscriptionOptions: subscriptionOptions, pollClientOverride: mockPollClient.client, connectivity: const MockConnectivity(), + processLifeCycle: const MockProcessLifeCycle(), ); expect( @@ -491,6 +639,48 @@ void main() { await bloc.done.future; }); + + test('a process resumes with autoReconnect disabled', () async { + final blocReady = Completer(); + final subscribeEvent = SubscribeEvent( + subscriptionRequest, + blocReady.complete, + ); + final bloc = getWebSocketBloc(); + + expect( + bloc.stream, + emitsInOrder( + [ + isA(), + isA(), + isA(), + isA(), + isA(), + isA(), + ], + ), + ); + + // ignore: invalid_use_of_internal_member + WebSocketOptions.autoReconnect = false; + + bloc.subscribe(subscribeEvent).listen( + null, + onError: expectAsync1((event) { + expect( + event, + isA(), + ); + }), + ); + + await blocReady.future; + + mockProcessLifeCycleController + ..add(ProcessStatus.paused) + ..add(ProcessStatus.resumed); + }); }); }); } From 1ee2ff6234c1553d76d4eea2466eb3cd26708172 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Wed, 23 Oct 2024 09:44:27 -0500 Subject: [PATCH 30/42] chore: Cherry Pick Bump version (#5593) chore(version): Bump version ### Features - feat(api): move App Sync subscription headers to protocol ([#5301](https://github.com/aws-amplify/amplify-flutter/pull/5301)) - feat(authenticator): export unmet password requirements ([#5437](https://github.com/aws-amplify/amplify-flutter/pull/5437)) ### Fixes - fix(api): Reconnect WebSocket when resuming app from a paused state ([#5567](https://github.com/aws-amplify/amplify-flutter/pull/5567)) Updated-Components: amplify_lints, Amplify Flutter, Amplify Dart, Amplify UI, DB Common, Secure Storage, AWS Common, Smithy, Worker Bee --- packages/amplify_core/CHANGELOG.md | 8 ++++++++ packages/amplify_core/lib/src/version.dart | 2 +- packages/amplify_core/pubspec.yaml | 4 ++-- packages/amplify_datastore/CHANGELOG.md | 4 ++++ packages/amplify_datastore/pubspec.yaml | 4 ++-- packages/api/amplify_api/CHANGELOG.md | 5 +++++ packages/api/amplify_api/pubspec.yaml | 6 +++--- packages/api/amplify_api_dart/CHANGELOG.md | 8 ++++++++ packages/api/amplify_api_dart/pubspec.yaml | 6 +++--- packages/auth/amplify_auth_cognito/CHANGELOG.md | 4 ++++ packages/auth/amplify_auth_cognito/pubspec.yaml | 6 +++--- packages/auth/amplify_auth_cognito_dart/CHANGELOG.md | 4 ++++ packages/auth/amplify_auth_cognito_dart/pubspec.yaml | 6 +++--- packages/authenticator/amplify_authenticator/CHANGELOG.md | 5 +++++ .../amplify_authenticator/lib/src/version.dart | 2 +- packages/authenticator/amplify_authenticator/pubspec.yaml | 8 ++++---- packages/aws_common/CHANGELOG.md | 4 ++++ packages/aws_common/pubspec.yaml | 2 +- .../push/amplify_push_notifications/CHANGELOG.md | 4 ++++ .../push/amplify_push_notifications/pubspec.yaml | 4 ++-- 20 files changed, 71 insertions(+), 25 deletions(-) diff --git a/packages/amplify_core/CHANGELOG.md b/packages/amplify_core/CHANGELOG.md index 69bd5e98e2..57b663a848 100644 --- a/packages/amplify_core/CHANGELOG.md +++ b/packages/amplify_core/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.4.2 + +### Features +- feat(api): move App Sync subscription headers to protocol ([#5301](https://github.com/aws-amplify/amplify-flutter/pull/5301)) + +### Fixes +- fix(api): Reconnect WebSocket when resuming app from a paused state ([#5567](https://github.com/aws-amplify/amplify-flutter/pull/5567)) + ## 2.4.1 ### Fixes diff --git a/packages/amplify_core/lib/src/version.dart b/packages/amplify_core/lib/src/version.dart index 38a2ff8a2f..b4cefe9232 100644 --- a/packages/amplify_core/lib/src/version.dart +++ b/packages/amplify_core/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '2.4.1'; +const packageVersion = '2.4.2'; diff --git a/packages/amplify_core/pubspec.yaml b/packages/amplify_core/pubspec.yaml index 4ebcdaceed..7de06f6b02 100644 --- a/packages/amplify_core/pubspec.yaml +++ b/packages/amplify_core/pubspec.yaml @@ -1,6 +1,6 @@ name: amplify_core description: The base package containing common types and utilities that are shared across the Amplify Flutter packages. -version: 2.4.1 +version: 2.4.2 homepage: https://docs.amplify.aws/lib/q/platform/flutter/ repository: https://github.com/aws-amplify/amplify-flutter/tree/main/packages/amplify_core issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues @@ -10,7 +10,7 @@ environment: dependencies: async: ^2.10.0 - aws_common: ">=0.7.3 <0.8.0" + aws_common: ">=0.7.4 <0.8.0" aws_signature_v4: ">=0.6.3 <0.7.0" collection: ^1.15.0 graphs: ^2.1.0 diff --git a/packages/amplify_datastore/CHANGELOG.md b/packages/amplify_datastore/CHANGELOG.md index f999eeb60e..4194b55191 100644 --- a/packages/amplify_datastore/CHANGELOG.md +++ b/packages/amplify_datastore/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.2 + +- Minor bug fixes and improvements + ## 2.4.1 ### Fixes diff --git a/packages/amplify_datastore/pubspec.yaml b/packages/amplify_datastore/pubspec.yaml index 07899e3863..39fdc3fabe 100644 --- a/packages/amplify_datastore/pubspec.yaml +++ b/packages/amplify_datastore/pubspec.yaml @@ -1,6 +1,6 @@ name: amplify_datastore description: The Amplify Flutter DataStore category plugin, providing a queryable, on-device data store. -version: 2.4.1 +version: 2.4.2 homepage: https://docs.amplify.aws/lib/q/platform/flutter/ repository: https://github.com/aws-amplify/amplify-flutter/tree/main/packages/amplify_datastore issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues @@ -13,7 +13,7 @@ dependencies: flutter: sdk: flutter amplify_datastore_plugin_interface: ">=2.4.1 <2.5.0" - amplify_core: ">=2.4.1 <2.5.0" + amplify_core: ">=2.4.2 <2.5.0" plugin_platform_interface: ^2.0.0 meta: ^1.7.0 collection: ^1.14.13 diff --git a/packages/api/amplify_api/CHANGELOG.md b/packages/api/amplify_api/CHANGELOG.md index 2e7c3ff146..bec52dc75b 100644 --- a/packages/api/amplify_api/CHANGELOG.md +++ b/packages/api/amplify_api/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.4.2 + +### Fixes +- fix(api): Reconnect WebSocket when resuming app from a paused state ([#5567](https://github.com/aws-amplify/amplify-flutter/pull/5567)) + ## 2.4.1 - Minor bug fixes and improvements diff --git a/packages/api/amplify_api/pubspec.yaml b/packages/api/amplify_api/pubspec.yaml index 63ebd86fc9..d401c15680 100644 --- a/packages/api/amplify_api/pubspec.yaml +++ b/packages/api/amplify_api/pubspec.yaml @@ -1,6 +1,6 @@ name: amplify_api description: The Amplify Flutter API category plugin, supporting GraphQL and REST operations. -version: 2.4.1 +version: 2.4.2 homepage: https://docs.amplify.aws/lib/q/platform/flutter/ repository: https://github.com/aws-amplify/amplify-flutter/tree/main/packages/api/amplify_api issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues @@ -19,8 +19,8 @@ platforms: web: dependencies: - amplify_api_dart: ">=0.5.5 <0.6.0" - amplify_core: ">=2.4.1 <2.5.0" + amplify_api_dart: ">=0.5.6 <0.6.0" + amplify_core: ">=2.4.2 <2.5.0" amplify_flutter: ">=2.4.1 <2.5.0" connectivity_plus: ^6.0.1 flutter: diff --git a/packages/api/amplify_api_dart/CHANGELOG.md b/packages/api/amplify_api_dart/CHANGELOG.md index a29df4e351..4d8fd7e87f 100644 --- a/packages/api/amplify_api_dart/CHANGELOG.md +++ b/packages/api/amplify_api_dart/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.5.6 + +### Features +- feat(api): move App Sync subscription headers to protocol ([#5301](https://github.com/aws-amplify/amplify-flutter/pull/5301)) + +### Fixes +- fix(api): Reconnect WebSocket when resuming app from a paused state ([#5567](https://github.com/aws-amplify/amplify-flutter/pull/5567)) + ## 0.5.5 ### Fixes diff --git a/packages/api/amplify_api_dart/pubspec.yaml b/packages/api/amplify_api_dart/pubspec.yaml index b8baaa6fb3..7d7b3110c9 100644 --- a/packages/api/amplify_api_dart/pubspec.yaml +++ b/packages/api/amplify_api_dart/pubspec.yaml @@ -1,6 +1,6 @@ name: amplify_api_dart description: The Amplify API category plugin in Dart-only, supporting GraphQL and REST operations. -version: 0.5.5 +version: 0.5.6 homepage: https://docs.amplify.aws/lib/q/platform/flutter/ repository: https://github.com/aws-amplify/amplify-flutter/tree/main/packages/api/amplify_api_dart issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues @@ -9,9 +9,9 @@ environment: sdk: ^3.3.0 dependencies: - amplify_core: ">=2.4.1 <2.5.0" + amplify_core: ">=2.4.2 <2.5.0" async: ^2.10.0 - aws_common: ">=0.7.3 <0.8.0" + aws_common: ">=0.7.4 <0.8.0" collection: ^1.15.0 json_annotation: ">=4.9.0 <4.10.0" meta: ^1.7.0 diff --git a/packages/auth/amplify_auth_cognito/CHANGELOG.md b/packages/auth/amplify_auth_cognito/CHANGELOG.md index 4a6f8dea81..c548a18633 100644 --- a/packages/auth/amplify_auth_cognito/CHANGELOG.md +++ b/packages/auth/amplify_auth_cognito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.2 + +- Minor bug fixes and improvements + ## 2.4.1 - Minor bug fixes and improvements diff --git a/packages/auth/amplify_auth_cognito/pubspec.yaml b/packages/auth/amplify_auth_cognito/pubspec.yaml index f8e194b01b..e97fcf589a 100644 --- a/packages/auth/amplify_auth_cognito/pubspec.yaml +++ b/packages/auth/amplify_auth_cognito/pubspec.yaml @@ -1,6 +1,6 @@ name: amplify_auth_cognito description: The Amplify Flutter Auth category plugin using the AWS Cognito provider. -version: 2.4.1 +version: 2.4.2 homepage: https://docs.amplify.aws/lib/q/platform/flutter/ repository: https://github.com/aws-amplify/amplify-flutter/tree/main/packages/auth/amplify_auth_cognito issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues @@ -21,8 +21,8 @@ platforms: dependencies: amplify_analytics_pinpoint: ">=2.4.1 <2.5.0" amplify_analytics_pinpoint_dart: ">=0.4.5 <0.5.0" - amplify_auth_cognito_dart: ">=0.11.5 <0.12.0" - amplify_core: ">=2.4.1 <2.5.0" + amplify_auth_cognito_dart: ">=0.11.6 <0.12.0" + amplify_core: ">=2.4.2 <2.5.0" amplify_flutter: ">=2.4.1 <2.5.0" amplify_secure_storage: ">=0.5.7 <0.6.0" async: ^2.10.0 diff --git a/packages/auth/amplify_auth_cognito_dart/CHANGELOG.md b/packages/auth/amplify_auth_cognito_dart/CHANGELOG.md index 91e78afdd0..02f5560230 100644 --- a/packages/auth/amplify_auth_cognito_dart/CHANGELOG.md +++ b/packages/auth/amplify_auth_cognito_dart/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.6 + +- Minor bug fixes and improvements + ## 0.11.5 - Minor bug fixes and improvements diff --git a/packages/auth/amplify_auth_cognito_dart/pubspec.yaml b/packages/auth/amplify_auth_cognito_dart/pubspec.yaml index 3c834f8dc1..c60f314f78 100644 --- a/packages/auth/amplify_auth_cognito_dart/pubspec.yaml +++ b/packages/auth/amplify_auth_cognito_dart/pubspec.yaml @@ -1,6 +1,6 @@ name: amplify_auth_cognito_dart description: A Dart-only implementation of the Amplify Auth plugin for Cognito. -version: 0.11.5 +version: 0.11.6 homepage: https://docs.amplify.aws/lib/q/platform/flutter/ repository: https://github.com/aws-amplify/amplify-flutter/tree/next/packages/auth/amplify_auth_cognito_dart issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues @@ -10,10 +10,10 @@ environment: dependencies: amplify_analytics_pinpoint_dart: ">=0.4.5 <0.5.0" - amplify_core: ">=2.4.1 <2.5.0" + amplify_core: ">=2.4.2 <2.5.0" amplify_secure_storage_dart: ">=0.5.3 <0.6.0" async: ^2.10.0 - aws_common: ">=0.7.3 <0.8.0" + aws_common: ">=0.7.4 <0.8.0" aws_signature_v4: ">=0.6.3 <0.7.0" built_collection: ^5.0.0 built_value: ^8.6.0 diff --git a/packages/authenticator/amplify_authenticator/CHANGELOG.md b/packages/authenticator/amplify_authenticator/CHANGELOG.md index c8c0a9914f..65b285f298 100644 --- a/packages/authenticator/amplify_authenticator/CHANGELOG.md +++ b/packages/authenticator/amplify_authenticator/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.2.0 + +### Features +- feat(authenticator): export unmet password requirements ([#5437](https://github.com/aws-amplify/amplify-flutter/pull/5437)) + ## 2.1.3 - Minor bug fixes and improvements diff --git a/packages/authenticator/amplify_authenticator/lib/src/version.dart b/packages/authenticator/amplify_authenticator/lib/src/version.dart index d09e279270..3f25c8a68c 100644 --- a/packages/authenticator/amplify_authenticator/lib/src/version.dart +++ b/packages/authenticator/amplify_authenticator/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '2.1.3'; +const packageVersion = '2.2.0'; diff --git a/packages/authenticator/amplify_authenticator/pubspec.yaml b/packages/authenticator/amplify_authenticator/pubspec.yaml index 924d21c87e..8410a0b182 100644 --- a/packages/authenticator/amplify_authenticator/pubspec.yaml +++ b/packages/authenticator/amplify_authenticator/pubspec.yaml @@ -1,6 +1,6 @@ name: amplify_authenticator description: A prebuilt Sign In and Sign Up experience for the Amplify Auth category -version: 2.1.3 +version: 2.2.0 homepage: https://ui.docs.amplify.aws/flutter/connected-components/authenticator repository: https://github.com/aws-amplify/amplify-flutter/tree/main/packages/authenticator/amplify_authenticator issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues @@ -10,11 +10,11 @@ environment: flutter: ">=3.19.0" dependencies: - amplify_auth_cognito: ">=2.4.1 <2.5.0" - amplify_core: ">=2.4.1 <2.5.0" + amplify_auth_cognito: ">=2.4.2 <2.5.0" + amplify_core: ">=2.4.2 <2.5.0" amplify_flutter: ">=2.4.1 <2.5.0" async: ^2.10.0 - aws_common: ">=0.7.3 <0.8.0" + aws_common: ">=0.7.4 <0.8.0" collection: ^1.15.0 flutter: sdk: flutter diff --git a/packages/aws_common/CHANGELOG.md b/packages/aws_common/CHANGELOG.md index fa9b3e93a9..49a0fe51e9 100644 --- a/packages/aws_common/CHANGELOG.md +++ b/packages/aws_common/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.4 + +- Minor bug fixes and improvements + ## 0.7.3 ### Features diff --git a/packages/aws_common/pubspec.yaml b/packages/aws_common/pubspec.yaml index 250913a29b..47ffbf3839 100644 --- a/packages/aws_common/pubspec.yaml +++ b/packages/aws_common/pubspec.yaml @@ -1,6 +1,6 @@ name: aws_common description: Common types and utilities used across AWS and Amplify packages. -version: 0.7.3 +version: 0.7.4 homepage: https://docs.amplify.aws/lib/q/platform/flutter/ repository: https://github.com/aws-amplify/amplify-flutter/tree/main/packages/aws_common issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues diff --git a/packages/notifications/push/amplify_push_notifications/CHANGELOG.md b/packages/notifications/push/amplify_push_notifications/CHANGELOG.md index 402f0c7c8e..ecf9fa7938 100644 --- a/packages/notifications/push/amplify_push_notifications/CHANGELOG.md +++ b/packages/notifications/push/amplify_push_notifications/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.2 + +- Minor bug fixes and improvements + ## 2.4.1 - Minor bug fixes and improvements diff --git a/packages/notifications/push/amplify_push_notifications/pubspec.yaml b/packages/notifications/push/amplify_push_notifications/pubspec.yaml index d207d517f6..a3046d98bd 100644 --- a/packages/notifications/push/amplify_push_notifications/pubspec.yaml +++ b/packages/notifications/push/amplify_push_notifications/pubspec.yaml @@ -1,6 +1,6 @@ name: amplify_push_notifications description: The Amplify Flutter Push Notifications package implementing features agnostic of an AWS Service such as Pinpoint. -version: 2.4.1 +version: 2.4.2 homepage: https://docs.amplify.aws/lib/q/platform/flutter/ issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues @@ -9,7 +9,7 @@ environment: flutter: ">=3.19.0" dependencies: - amplify_core: ">=2.4.1 <2.5.0" + amplify_core: ">=2.4.2 <2.5.0" amplify_secure_storage: ">=0.5.5 <0.6.0" async: ^2.10.0 flutter: From 3551eb36b939cf4302a449bbd87be329f198fedc Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Tue, 17 Sep 2024 14:50:21 -0700 Subject: [PATCH 31/42] seperated bucket out into its own class instead of using a map --- .../storage/storage_output_bucket.dart | 16 ++++++++++++++++ .../amplify_outputs/storage/storage_outputs.dart | 1 + 2 files changed, 17 insertions(+) create mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart new file mode 100644 index 0000000000..66cc099dbc --- /dev/null +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + + + +/// {@template amplify_core.amplify_outputs.amazon_pinpoint_channel} +/// Supported channels for Amazon Pinpoint. +/// {@endtemplate} +class StorageOutputBucket { + StorageOutputBucket(this.name, this.bucketName, this.awsRegion); + factory StorageOutputBucket.fromJson(Map json) => StorageOutputBucket(json['name'].toString(), json['bucket_name'].toString(), json['aws_region'].toString()); + String name; + String bucketName; + String awsRegion; + Map toJson() => {'name':name, 'bucket_name':bucketName, 'aws_region':awsRegion}; +} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart index fec0ec0662..f6db4ba19e 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_outputs.dart @@ -19,6 +19,7 @@ class StorageOutputs this.buckets, }); + factory StorageOutputs.fromJson(Map json) => _$StorageOutputsFromJson(json); From 08d3ed0d23e98159d4a5e1be79898affb0e7bbe3 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 18 Sep 2024 11:31:25 -0700 Subject: [PATCH 32/42] made bucket_output class a proper output class like the others in amplify-flutter --- packages/amplify_core/doc/lib/auth.dart | 6 +-- .../storage/bucket_output.dart | 37 +++++++++++++++++++ .../storage/bucket_output.g.dart | 34 +++++++++++++++++ .../storage/storage_output_bucket.dart | 16 -------- 4 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart create mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart delete mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart diff --git a/packages/amplify_core/doc/lib/auth.dart b/packages/amplify_core/doc/lib/auth.dart index 6e48014a9b..7572dc0855 100644 --- a/packages/amplify_core/doc/lib/auth.dart +++ b/packages/amplify_core/doc/lib/auth.dart @@ -110,13 +110,13 @@ Future _handleSignInResult(SignInResult result) async { // #enddocregion handle-signin, handle-confirm-signin-sms, handle-confirm-signin-new-password, handle-confirm-signin-custom-challenge, handle-confirm-signin-reset-password, handle-confirm-signin-confirm-signup, handle-confirm-signin-done, handle-confirm-signin-mfa-selection, handle-confirm-signin-totp-setup, handle-confirm-signin-totp-code // #docregion handle-confirm-signin-mfa-selection case AuthSignInStep.continueSignInWithMfaSelection: - final allowedMfaTypes = result.nextStep.allowedMfaTypes!; + final allowedMfaTypes = result.nextStep.allowedMfaTypes; final selection = await _promptUserPreference(allowedMfaTypes); return _handleMfaSelection(selection); // #enddocregion handle-confirm-signin-mfa-selection // #docregion handle-confirm-signin-totp-setup case AuthSignInStep.continueSignInWithTotpSetup: - final totpSetupDetails = result.nextStep.totpSetupDetails!; + final totpSetupDetails = result.nextStep.totpSetupDetails; final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); safePrint('Open URI to complete setup: $setupUri'); // #enddocregion handle-confirm-signin-totp-setup @@ -255,7 +255,7 @@ Future signOutGlobally() async { if (result is CognitoCompleteSignOut) { safePrint('Sign out completed successfully'); } else if (result is CognitoPartialSignOut) { - final globalSignOutException = result.globalSignOutException!; + final globalSignOutException = result.globalSignOutException; final accessToken = globalSignOutException.accessToken; // Retry the global sign out using the access token, if desired // ... diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart new file mode 100644 index 0000000000..9c09884ee3 --- /dev/null +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart @@ -0,0 +1,37 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; + +part 'bucket_output.g.dart'; + +@zAmplifyOutputsSerializable +class BucketOutput + with AWSEquatable, AWSSerializable, AWSDebuggable{ + + const BucketOutput({required this.name, required this.bucketName, required this.awsRegion}); + + factory BucketOutput.fromJson(Map json) => + _$BucketOutputFromJson(json); + + final String name; + + final String bucketName; + + final String awsRegion; + + @override + List get props => [ + name, + bucketName, + awsRegion, + ]; + + @override + String get runtimeTypeName => 'BucketOutput'; + + @override + Object? toJson() { + return _$BucketOutputToJson(this); + } +} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart new file mode 100644 index 0000000000..331517e83e --- /dev/null +++ b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: deprecated_member_use_from_same_package + +part of 'bucket_output.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +BucketOutput _$BucketOutputFromJson(Map json) => + $checkedCreate( + 'BucketOutput', + json, + ($checkedConvert) { + final val = BucketOutput( + name: $checkedConvert('name', (v) => v as String), + bucketName: $checkedConvert('bucket_name', (v) => v as String), + awsRegion: $checkedConvert('aws_region', (v) => v as String), + ); + return val; + }, + fieldKeyMap: const { + 'bucketName': 'bucket_name', + 'awsRegion': 'aws_region' + }, + ); + +Map _$BucketOutputToJson(BucketOutput instance) => + { + 'name': instance.name, + 'bucket_name': instance.bucketName, + 'aws_region': instance.awsRegion, + }; diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart deleted file mode 100644 index 66cc099dbc..0000000000 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/storage_output_bucket.dart +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - - - -/// {@template amplify_core.amplify_outputs.amazon_pinpoint_channel} -/// Supported channels for Amazon Pinpoint. -/// {@endtemplate} -class StorageOutputBucket { - StorageOutputBucket(this.name, this.bucketName, this.awsRegion); - factory StorageOutputBucket.fromJson(Map json) => StorageOutputBucket(json['name'].toString(), json['bucket_name'].toString(), json['aws_region'].toString()); - String name; - String bucketName; - String awsRegion; - Map toJson() => {'name':name, 'bucket_name':bucketName, 'aws_region':awsRegion}; -} From 56b64ee4ce701729349a84efa7b1a06f9eb75a11 Mon Sep 17 00:00:00 2001 From: ekjotmultani Date: Wed, 18 Sep 2024 12:19:27 -0700 Subject: [PATCH 33/42] added doc comments and changed name of bucket class --- devtools_options.yaml | 3 ++ .../storage/bucket_output.dart | 37 ------------------- .../storage/bucket_output.g.dart | 34 ----------------- 3 files changed, 3 insertions(+), 71 deletions(-) create mode 100644 devtools_options.yaml delete mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart delete mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart deleted file mode 100644 index 9c09884ee3..0000000000 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.dart +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; - -part 'bucket_output.g.dart'; - -@zAmplifyOutputsSerializable -class BucketOutput - with AWSEquatable, AWSSerializable, AWSDebuggable{ - - const BucketOutput({required this.name, required this.bucketName, required this.awsRegion}); - - factory BucketOutput.fromJson(Map json) => - _$BucketOutputFromJson(json); - - final String name; - - final String bucketName; - - final String awsRegion; - - @override - List get props => [ - name, - bucketName, - awsRegion, - ]; - - @override - String get runtimeTypeName => 'BucketOutput'; - - @override - Object? toJson() { - return _$BucketOutputToJson(this); - } -} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart b/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart deleted file mode 100644 index 331517e83e..0000000000 --- a/packages/amplify_core/lib/src/config/amplify_outputs/storage/bucket_output.g.dart +++ /dev/null @@ -1,34 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ignore_for_file: deprecated_member_use_from_same_package - -part of 'bucket_output.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -BucketOutput _$BucketOutputFromJson(Map json) => - $checkedCreate( - 'BucketOutput', - json, - ($checkedConvert) { - final val = BucketOutput( - name: $checkedConvert('name', (v) => v as String), - bucketName: $checkedConvert('bucket_name', (v) => v as String), - awsRegion: $checkedConvert('aws_region', (v) => v as String), - ); - return val; - }, - fieldKeyMap: const { - 'bucketName': 'bucket_name', - 'awsRegion': 'aws_region' - }, - ); - -Map _$BucketOutputToJson(BucketOutput instance) => - { - 'name': instance.name, - 'bucket_name': instance.bucketName, - 'aws_region': instance.awsRegion, - }; From ec7a7a6c0996df21cacb6e2a72edc90b38015b56 Mon Sep 17 00:00:00 2001 From: ekjotmultani <43255916+ekjotmultani@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:42:38 -0700 Subject: [PATCH 34/42] Update auth.dart --- packages/amplify_core/doc/lib/auth.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/amplify_core/doc/lib/auth.dart b/packages/amplify_core/doc/lib/auth.dart index 7572dc0855..6e48014a9b 100644 --- a/packages/amplify_core/doc/lib/auth.dart +++ b/packages/amplify_core/doc/lib/auth.dart @@ -110,13 +110,13 @@ Future _handleSignInResult(SignInResult result) async { // #enddocregion handle-signin, handle-confirm-signin-sms, handle-confirm-signin-new-password, handle-confirm-signin-custom-challenge, handle-confirm-signin-reset-password, handle-confirm-signin-confirm-signup, handle-confirm-signin-done, handle-confirm-signin-mfa-selection, handle-confirm-signin-totp-setup, handle-confirm-signin-totp-code // #docregion handle-confirm-signin-mfa-selection case AuthSignInStep.continueSignInWithMfaSelection: - final allowedMfaTypes = result.nextStep.allowedMfaTypes; + final allowedMfaTypes = result.nextStep.allowedMfaTypes!; final selection = await _promptUserPreference(allowedMfaTypes); return _handleMfaSelection(selection); // #enddocregion handle-confirm-signin-mfa-selection // #docregion handle-confirm-signin-totp-setup case AuthSignInStep.continueSignInWithTotpSetup: - final totpSetupDetails = result.nextStep.totpSetupDetails; + final totpSetupDetails = result.nextStep.totpSetupDetails!; final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); safePrint('Open URI to complete setup: $setupUri'); // #enddocregion handle-confirm-signin-totp-setup @@ -255,7 +255,7 @@ Future signOutGlobally() async { if (result is CognitoCompleteSignOut) { safePrint('Sign out completed successfully'); } else if (result is CognitoPartialSignOut) { - final globalSignOutException = result.globalSignOutException; + final globalSignOutException = result.globalSignOutException!; final accessToken = globalSignOutException.accessToken; // Retry the global sign out using the access token, if desired // ... From f625f3b475a86270da0e9d14ba46f2817275b702 Mon Sep 17 00:00:00 2001 From: ekjotmultani <43255916+ekjotmultani@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:30:21 -0700 Subject: [PATCH 35/42] Delete devtools_options.yaml --- devtools_options.yaml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml deleted file mode 100644 index fa0b357c4f..0000000000 --- a/devtools_options.yaml +++ /dev/null @@ -1,3 +0,0 @@ -description: This file stores settings for Dart & Flutter DevTools. -documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states -extensions: From 54c5f62884afab306818414985f3857000aafc2b Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:19:35 -0700 Subject: [PATCH 36/42] updated doc message to be consistent with other apis, used proper variables in tests --- .../lib/src/types/storage/list_options.dart | 2 +- .../example/integration_test/list_test.dart | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/amplify_core/lib/src/types/storage/list_options.dart b/packages/amplify_core/lib/src/types/storage/list_options.dart index 43aa1b0775..4a63e1ec2b 100644 --- a/packages/amplify_core/lib/src/types/storage/list_options.dart +++ b/packages/amplify_core/lib/src/types/storage/list_options.dart @@ -28,7 +28,7 @@ class StorageListOptions /// {@macro amplify_core.storage.list_plugin_options} final StorageListPluginOptions? pluginOptions; - /// an optional bucket to specify which bucket to return the list for + /// Optionally specify which bucket to retrieve final StorageBucket? bucket; /// an optional bucket to specify which bucket to return the list for diff --git a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart index f1bfd4fe01..a1d3d651f2 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart @@ -33,16 +33,20 @@ void main() { ); setUpAll(() async { await configure(amplifyEnvironments['main']!); - for (var i = 0; i < 4; i++) { + for (var pathIndex = 0; + pathIndex < uploadedPaths.length ~/ 2; + pathIndex++) { await Amplify.Storage.uploadData( - path: StoragePath.fromString(uploadedPaths[i]), + path: StoragePath.fromString(uploadedPaths[pathIndex]), data: StorageDataPayload.bytes('test content'.codeUnits), bucket: mainBucket, ).result; } - for (var i = 4; i < 8; i++) { + for (var pathIndex = uploadedPaths.length ~/ 2; + pathIndex < uploadedPaths.length; + pathIndex++) { await Amplify.Storage.uploadData( - path: StoragePath.fromString(uploadedPaths[i]), + path: StoragePath.fromString(uploadedPaths[pathIndex]), data: StorageDataPayload.bytes('test content'.codeUnits), bucket: secondaryBucket, ).result; @@ -64,17 +68,21 @@ void main() { bucket: secondaryBucket, ), ).result; - for (var i = 0; i < 4; i++) { + for (var pathIndex = 0; + pathIndex < uploadedPaths.length ~/ 2; + pathIndex++) { expect( listResultMainBucket.items - .any((item) => item.path == uploadedPaths[i]), + .any((item) => item.path == uploadedPaths[pathIndex]), isTrue, ); } - for (var i = 4; i < 8; i++) { + for (var pathIndex = uploadedPaths.length ~/ 2; + pathIndex < uploadedPaths.length; + pathIndex++) { expect( listResultSecondaryBucket.items - .any((item) => item.path == uploadedPaths[i]), + .any((item) => item.path == uploadedPaths[pathIndex]), isTrue, ); } From 132e22c43a6e15b6460a0e0bad4616d3344d8200 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Thu, 24 Oct 2024 09:07:01 -0700 Subject: [PATCH 37/42] feat(storage): multi bucket get properties api (#5577) --- .../types/storage/get_properties_options.dart | 9 +- .../integration_test/get_properties_test.dart | 114 ++++++++++++++++++ .../lib/src/amplify_storage_s3_dart_impl.dart | 1 + .../service/storage_s3_service_impl.dart | 4 +- .../test/amplify_storage_s3_dart_test.dart | 3 + 5 files changed, 127 insertions(+), 4 deletions(-) diff --git a/packages/amplify_core/lib/src/types/storage/get_properties_options.dart b/packages/amplify_core/lib/src/types/storage/get_properties_options.dart index bef609f7a7..dbc5b8c112 100644 --- a/packages/amplify_core/lib/src/types/storage/get_properties_options.dart +++ b/packages/amplify_core/lib/src/types/storage/get_properties_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.get_properties_options} /// Configurable options for `Amplify.Storage.getProperties`. @@ -14,13 +14,17 @@ class StorageGetPropertiesOptions /// {@macro amplify_core.storage.get_properties_options} const StorageGetPropertiesOptions({ this.pluginOptions, + this.bucket, }); /// {@macro amplify_core.storage.download_get_properties_plugin_options} final StorageGetPropertiesPluginOptions? pluginOptions; + /// Optionally specify which bucket to retrieve + final StorageBucket? bucket; + @override - List get props => [pluginOptions]; + List get props => [pluginOptions, bucket]; @override String get runtimeTypeName => 'StorageGetPropertiesOptions'; @@ -28,6 +32,7 @@ class StorageGetPropertiesOptions @override Map toJson() => { 'pluginOptions': pluginOptions?.toJson(), + 'bucket': bucket, }; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/get_properties_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/get_properties_test.dart index 9540e49650..06116cbb75 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/get_properties_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/get_properties_test.dart @@ -101,5 +101,119 @@ void main() { expect(result.storageItem.size, data.length); }); }); + group('multibucket config', () { + final mainBucket = + StorageBucket.fromOutputs('Storage Integ Test main bucket'); + final secondaryBucket = + StorageBucket.fromOutputs('Storage Integ Test secondary bucket'); + setUpAll(() async { + await configure(amplifyEnvironments['main']!); + addTearDownPath(StoragePath.fromString(path)); + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: StoragePath.fromString(path), + options: const StorageUploadDataOptions(metadata: metadata), + bucket: mainBucket, + ).result; + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: StoragePath.fromString(path), + options: const StorageUploadDataOptions(metadata: metadata), + bucket: secondaryBucket, + ).result; + }); + + testWidgets('String StoragePath', (_) async { + final result = await Amplify.Storage.getProperties( + path: StoragePath.fromString(path), + options: StorageGetPropertiesOptions( + bucket: mainBucket, + ), + ).result; + expect(result.storageItem.path, path); + expect(result.storageItem.metadata, metadata); + expect(result.storageItem.eTag, isNotNull); + expect(result.storageItem.size, data.length); + + final resultSecondaryBucket = await Amplify.Storage.getProperties( + path: StoragePath.fromString(path), + options: StorageGetPropertiesOptions( + bucket: secondaryBucket, + ), + ).result; + expect(resultSecondaryBucket.storageItem.path, path); + expect(resultSecondaryBucket.storageItem.metadata, metadata); + expect(resultSecondaryBucket.storageItem.eTag, isNotNull); + expect(resultSecondaryBucket.storageItem.size, data.length); + }); + + testWidgets('with identity ID', (_) async { + final userIdentityId = await signInNewUser(); + final name = 'get-properties-with-identity-id-${uuid()}'; + final data = 'with identity ID'.codeUnits; + final expectedResolvedPath = 'private/$userIdentityId/$name'; + addTearDownPath(StoragePath.fromString(expectedResolvedPath)); + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: StoragePath.fromString(expectedResolvedPath), + options: const StorageUploadDataOptions(metadata: metadata), + bucket: secondaryBucket, + ).result; + final result = await Amplify.Storage.getProperties( + path: StoragePath.fromIdentityId( + ((identityId) => 'private/$identityId/$name'), + ), + options: StorageGetPropertiesOptions( + bucket: secondaryBucket, + ), + ).result; + expect(result.storageItem.path, expectedResolvedPath); + expect(result.storageItem.metadata, metadata); + expect(result.storageItem.eTag, isNotNull); + expect(result.storageItem.size, data.length); + }); + + testWidgets('not existent path', (_) async { + // we expect StorageNotFoundException here since there is no data uploaded to either bucket on this path + await expectLater( + () => Amplify.Storage.getProperties( + path: const StoragePath.fromString('public/not-existent-path'), + options: StorageGetPropertiesOptions( + bucket: mainBucket, + ), + ).result, + throwsA(isA()), + ); + await expectLater( + () => Amplify.Storage.getProperties( + path: const StoragePath.fromString('public/not-existent-path'), + options: StorageGetPropertiesOptions( + bucket: secondaryBucket, + ), + ).result, + throwsA(isA()), + ); + }); + testWidgets('unauthorized path', (_) async { + await expectLater( + () => Amplify.Storage.getProperties( + path: const StoragePath.fromString('unauthorized/path'), + options: StorageGetPropertiesOptions( + bucket: mainBucket, + ), + ).result, + throwsA(isA()), + ); + await expectLater( + () => Amplify.Storage.getProperties( + path: const StoragePath.fromString('unauthorized/path'), + options: StorageGetPropertiesOptions( + bucket: secondaryBucket, + ), + ).result, + throwsA(isA()), + ); + }); + }); }); } 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 f51ebe37a6..8994c27d83 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 @@ -164,6 +164,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface final s3Options = StorageGetPropertiesOptions( pluginOptions: s3PluginOptions, + bucket: options?.bucket, ); return S3GetPropertiesOperation( 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 4781d40aac..449dd3d7f6 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 @@ -198,12 +198,12 @@ class StorageS3Service { required StorageGetPropertiesOptions options, }) async { final resolvedPath = await _pathResolver.resolvePath(path: path); - + final s3ClientInfo = getS3ClientInfo(storageBucket: options.bucket); return S3GetPropertiesResult( storageItem: S3Item.fromHeadObjectOutput( await headObject( s3client: _defaultS3Client, - bucket: _storageOutputs.bucketName, + bucket: s3ClientInfo.bucketName, key: resolvedPath, ), path: resolvedPath, 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 6f97dd7abd..2b187e664f 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 @@ -248,6 +248,9 @@ void main() { () async { const testOptions = StorageGetPropertiesOptions( pluginOptions: S3GetPropertiesPluginOptions(), + bucket: StorageBucket.fromBucketInfo( + BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2'), + ), ); when( From b2783fb38ec3d2bfa880917e95c3eb6bc172a365 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Mon, 28 Oct 2024 14:04:42 -0500 Subject: [PATCH 38/42] feat(storage): multi bucket remove (#5598) --- .../lib/src/types/storage/remove_options.dart | 9 +- .../example/integration_test/remove_test.dart | 110 ++++++++++++++++++ .../integration_test/utils/object_exists.dart | 7 +- .../integration_test/utils/tear_down.dart | 21 ++++ .../lib/src/amplify_storage_s3_dart_impl.dart | 1 + .../service/storage_s3_service_impl.dart | 7 +- 6 files changed, 148 insertions(+), 7 deletions(-) diff --git a/packages/amplify_core/lib/src/types/storage/remove_options.dart b/packages/amplify_core/lib/src/types/storage/remove_options.dart index f898e77190..95be578c1c 100644 --- a/packages/amplify_core/lib/src/types/storage/remove_options.dart +++ b/packages/amplify_core/lib/src/types/storage/remove_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_options} /// Configurable options for `Amplify.Storage.remove`. @@ -14,13 +14,17 @@ class StorageRemoveOptions /// {@macro amplify_core.storage.remove_options} const StorageRemoveOptions({ this.pluginOptions, + this.bucket, }); /// {@macro amplify_core.storage.remove_plugin_options} final StorageRemovePluginOptions? pluginOptions; + /// Optionally specify which bucket to target + final StorageBucket? bucket; + @override - List get props => [pluginOptions]; + List get props => [pluginOptions, bucket]; @override String get runtimeTypeName => 'StorageRemoveOptions'; @@ -28,6 +32,7 @@ class StorageRemoveOptions @override Map toJson() => { 'pluginOptions': pluginOptions?.toJson(), + 'bucket': bucket, }; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/remove_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/remove_test.dart index a46d68e66e..6a8d441be7 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/remove_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/remove_test.dart @@ -64,6 +64,116 @@ void main() { }); }); + group('Multi-bucket', () { + final mainBucket = StorageBucket.fromOutputs( + 'Storage Integ Test main bucket', + ); + final secondaryBucket = StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket', + ); + final path = 'public/multi-bucket-remove-${uuid()}'; + final storagePath = StoragePath.fromString(path); + setUp(() async { + // upload to main bucket + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes('data'.codeUnits), + path: storagePath, + bucket: mainBucket, + ).result; + }); + + testWidgets('removes from multiple buckets', (_) async { + expect( + await objectExists( + storagePath, + bucket: mainBucket, + ), + true, + ); + + // upload to secondary bucket + await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes('data'.codeUnits), + path: storagePath, + bucket: secondaryBucket, + ).result; + + expect( + await objectExists( + storagePath, + bucket: secondaryBucket, + ), + true, + ); + + final mainResult = await Amplify.Storage.remove( + path: storagePath, + options: StorageRemoveOptions(bucket: mainBucket), + ).result; + expect(mainResult.removedItem.path, path); + + // Assert path was only removed from the main bucket + expect( + await objectExists( + storagePath, + bucket: mainBucket, + ), + false, + ); + expect( + await objectExists( + storagePath, + bucket: secondaryBucket, + ), + true, + ); + + final secondaryResult = await Amplify.Storage.remove( + path: storagePath, + options: StorageRemoveOptions(bucket: secondaryBucket), + ).result; + expect(secondaryResult.removedItem.path, path); + expect( + await objectExists( + storagePath, + bucket: secondaryBucket, + ), + false, + ); + }); + + testWidgets('removes when present in bucket', (_) async { + expect( + await objectExists( + storagePath, + bucket: mainBucket, + ), + true, + ); + final mainResult = await Amplify.Storage.remove( + path: storagePath, + options: StorageRemoveOptions(bucket: mainBucket), + ).result; + expect(mainResult.removedItem.path, path); + expect( + await objectExists( + storagePath, + bucket: mainBucket, + ), + false, + ); + + await expectLater( + Amplify.Storage.remove( + path: storagePath, + options: StorageRemoveOptions(bucket: secondaryBucket), + ).result, + completes, + reason: 'non existent path does not throw', + ); + }); + }); + testWidgets('unauthorized path', (_) async { await expectLater( () => Amplify.Storage.remove( diff --git a/packages/storage/amplify_storage_s3/example/integration_test/utils/object_exists.dart b/packages/storage/amplify_storage_s3/example/integration_test/utils/object_exists.dart index c6e533f5ab..aa3e98f97e 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/utils/object_exists.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/utils/object_exists.dart @@ -1,9 +1,12 @@ import 'package:amplify_core/amplify_core.dart'; /// Returns true if an object exists at the given [path]. -Future objectExists(StoragePath path) async { +Future objectExists(StoragePath path, {StorageBucket? bucket}) async { try { - await Amplify.Storage.getProperties(path: path).result; + await Amplify.Storage.getProperties( + path: path, + options: StorageGetPropertiesOptions(bucket: bucket), + ).result; return true; } on StorageNotFoundException { return false; diff --git a/packages/storage/amplify_storage_s3/example/integration_test/utils/tear_down.dart b/packages/storage/amplify_storage_s3/example/integration_test/utils/tear_down.dart index 5c9bd036f2..319cc05ee9 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/utils/tear_down.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/utils/tear_down.dart @@ -36,6 +36,27 @@ void addTearDownPaths(List paths) { ); } +/// Adds a tear down to remove the same object in multiple [buckets]. +void addTearDownMultiBucket(StoragePath path, List buckets) { + addTearDown( + () { + try { + return Future.wait( + buckets.map( + (bucket) => Amplify.Storage.remove( + path: path, + options: StorageRemoveOptions(bucket: bucket), + ).result, + ), + ); + } on Exception catch (e) { + _logger.warn('Failed to remove files after test', e); + rethrow; + } + }, + ); +} + /// Adds a tear down to delete the current user. void addTearDownCurrentUser() { addTearDown(() { 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 8994c27d83..98470073e1 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 @@ -389,6 +389,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface final s3Options = StorageRemoveOptions( pluginOptions: s3PluginOptions, + bucket: options?.bucket, ); return S3RemoveOperation( 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 449dd3d7f6..136ee9f992 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 @@ -202,7 +202,7 @@ class StorageS3Service { return S3GetPropertiesResult( storageItem: S3Item.fromHeadObjectOutput( await headObject( - s3client: _defaultS3Client, + s3client: s3ClientInfo.client, bucket: s3ClientInfo.bucketName, key: resolvedPath, ), @@ -457,11 +457,12 @@ class StorageS3Service { required StoragePath path, required StorageRemoveOptions options, }) async { + final s3ClientInfo = getS3ClientInfo(storageBucket: options.bucket); final resolvedPath = await _pathResolver.resolvePath(path: path); await _deleteObject( - s3client: _defaultS3Client, - bucket: _storageOutputs.bucketName, + s3client: s3ClientInfo.client, + bucket: s3ClientInfo.bucketName, key: resolvedPath, ); From a4d65af2cb56d99010750ad77481f7caa718d200 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Mon, 28 Oct 2024 14:21:30 -0500 Subject: [PATCH 39/42] feat(storage): multi bucket download data (#5599) --- .../types/storage/download_data_options.dart | 9 +++++++-- .../integration_test/download_data_test.dart | 18 ++++++++++++++++++ .../lib/src/amplify_storage_s3_dart_impl.dart | 1 + .../service/storage_s3_service_impl.dart | 7 ++++--- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/amplify_core/lib/src/types/storage/download_data_options.dart b/packages/amplify_core/lib/src/types/storage/download_data_options.dart index 25c59523f1..020856dfe1 100644 --- a/packages/amplify_core/lib/src/types/storage/download_data_options.dart +++ b/packages/amplify_core/lib/src/types/storage/download_data_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.download_data_options} /// Configurable options for `Amplify.Storage.downloadData`. @@ -14,13 +14,17 @@ class StorageDownloadDataOptions /// {@macro amplify_core.storage.download_data_options} const StorageDownloadDataOptions({ this.pluginOptions, + this.bucket, }); /// {@macro amplify_core.storage.download_data_plugin_options} final StorageDownloadDataPluginOptions? pluginOptions; + /// Optionally specify which bucket to target + final StorageBucket? bucket; + @override - List get props => [pluginOptions]; + List get props => [pluginOptions, bucket]; @override String get runtimeTypeName => 'StorageDownloadDataOptions'; @@ -28,6 +32,7 @@ class StorageDownloadDataOptions @override Map toJson() => { 'pluginOptions': pluginOptions?.toJson(), + 'bucket': bucket, }; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart index eb028d3919..a88c3631aa 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart @@ -132,6 +132,24 @@ void main() { expect(utf8.decode(downloadResult.bytes), 'data'); expect(downloadResult.downloadedItem.path, publicPath); }); + + testWidgets('multi bucket', (_) async { + final mainBucket = + StorageBucket.fromOutputs('Storage Integ Test main bucket'); + + // TODO(equartey): Add download check for secondary bucket when upload supports multibucket + final downloadResult = await Amplify.Storage.downloadData( + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$identityName', + ), + options: StorageDownloadDataOptions(bucket: mainBucket), + ).result; + expect(downloadResult.bytes, identityData); + expect( + downloadResult.downloadedItem.path, + 'private/$userIdentityId/$identityName', + ); + }); }); group('download progress', () { 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 98470073e1..f746c1c845 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 @@ -218,6 +218,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface final s3Options = StorageDownloadDataOptions( pluginOptions: s3PluginOptions, + bucket: options?.bucket, ); final bytes = BytesBuilder(); 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 136ee9f992..42f30f155b 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 @@ -300,10 +300,11 @@ class StorageS3Service { FutureOr Function()? onDone, FutureOr Function()? onError, }) { + final s3ClientInfo = getS3ClientInfo(storageBucket: options.bucket); final downloadDataTask = S3DownloadTask( - s3Client: _defaultS3Client, - defaultS3ClientConfig: _defaultS3ClientConfig, - bucket: _storageOutputs.bucketName, + s3Client: s3ClientInfo.client, + defaultS3ClientConfig: s3ClientInfo.config, + bucket: s3ClientInfo.bucketName, path: path, options: options, pathResolver: _pathResolver, From 3999314ae4f2fb99559b3500f5dd610d92fddf5d Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Tue, 29 Oct 2024 08:41:01 -0500 Subject: [PATCH 40/42] feat(storage): multi bucket upload file (#5600) --- .../types/storage/upload_file_options.dart | 9 ++- .../integration_test/download_data_test.dart | 33 ++++++-- .../integration_test/upload_file_test.dart | 78 +++++++++++++++++++ .../integration_test/utils/tear_down.dart | 7 +- .../lib/src/amplify_storage_s3_dart_impl.dart | 1 + .../service/storage_s3_service_impl.dart | 7 +- .../test/amplify_storage_s3_dart_test.dart | 16 +++- 7 files changed, 137 insertions(+), 14 deletions(-) diff --git a/packages/amplify_core/lib/src/types/storage/upload_file_options.dart b/packages/amplify_core/lib/src/types/storage/upload_file_options.dart index bad1529468..6b025257fb 100644 --- a/packages/amplify_core/lib/src/types/storage/upload_file_options.dart +++ b/packages/amplify_core/lib/src/types/storage/upload_file_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.upload_file_options} /// Configurable options for `Amplify.Storage.uploadFile`. @@ -15,6 +15,7 @@ class StorageUploadFileOptions const StorageUploadFileOptions({ this.metadata = const {}, this.pluginOptions, + this.bucket, }); /// The metadata attached to the object to be uploaded. @@ -23,8 +24,11 @@ class StorageUploadFileOptions /// {@macro amplify_core.storage.upload_file_plugin_options} final StorageUploadFilePluginOptions? pluginOptions; + /// Optionally specify which bucket to target + final StorageBucket? bucket; + @override - List get props => [metadata, pluginOptions]; + List get props => [metadata, pluginOptions, bucket]; @override String get runtimeTypeName => 'StorageUploadFileOptions'; @@ -33,6 +37,7 @@ class StorageUploadFileOptions Map toJson() => { 'metadata': metadata, 'pluginOptions': pluginOptions?.toJson(), + 'bucket': bucket, }; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart index a88c3631aa..96dd7c7c36 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart @@ -136,18 +136,39 @@ void main() { testWidgets('multi bucket', (_) async { final mainBucket = StorageBucket.fromOutputs('Storage Integ Test main bucket'); + final secondaryBucket = StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket', + ); + await Amplify.Storage.uploadData( + path: StoragePath.fromString(publicPath), + data: StorageDataPayload.bytes(bytesData), + bucket: secondaryBucket, + ).result; - // TODO(equartey): Add download check for secondary bucket when upload supports multibucket final downloadResult = await Amplify.Storage.downloadData( - path: StoragePath.fromIdentityId( - (identityId) => 'private/$identityId/$identityName', - ), + path: StoragePath.fromString(publicPath), options: StorageDownloadDataOptions(bucket: mainBucket), ).result; - expect(downloadResult.bytes, identityData); + expect( + downloadResult.bytes, + bytesData, + ); expect( downloadResult.downloadedItem.path, - 'private/$userIdentityId/$identityName', + publicPath, + ); + + final downloadSecondaryResult = await Amplify.Storage.downloadData( + path: StoragePath.fromString(publicPath), + options: StorageDownloadDataOptions(bucket: secondaryBucket), + ).result; + expect( + downloadSecondaryResult.bytes, + bytesData, + ); + expect( + downloadSecondaryResult.downloadedItem.path, + publicPath, ); }); }); diff --git a/packages/storage/amplify_storage_s3/example/integration_test/upload_file_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/upload_file_test.dart index 6e1eb0581e..9113a3c4c3 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/upload_file_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/upload_file_test.dart @@ -11,6 +11,7 @@ import 'package:integration_test/integration_test.dart'; import 'utils/configure.dart'; import 'utils/create_file/create_file.dart'; +import 'utils/object_exists.dart'; import 'utils/sign_in_new_user.dart'; import 'utils/tear_down.dart'; @@ -220,6 +221,83 @@ void main() { }); }); + group('multi-bucket', () { + final mainBucket = + StorageBucket.fromOutputs('Storage Integ Test main bucket'); + final secondaryBucket = StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket', + ); + + testWidgets('uploads to multiple buckets', (_) async { + final fileId = uuid(); + final path = 'public/multi-bucket-upload-file-$fileId'; + final storagePath = StoragePath.fromString(path); + const content = 'upload file'; + final data = content.codeUnits; + final filePath = await createFile(path: fileId, content: content); + addTearDownMultiBucket( + storagePath, + [mainBucket, secondaryBucket], + ); + // main bucket + final mainResult = await Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath(filePath), + path: storagePath, + options: StorageUploadFileOptions( + pluginOptions: const S3UploadFilePluginOptions( + useAccelerateEndpoint: true, + ), + bucket: mainBucket, + ), + ).result; + expect(mainResult.uploadedItem.path, path); + + final downloadMainResult = await Amplify.Storage.downloadData( + path: storagePath, + options: StorageDownloadDataOptions( + bucket: mainBucket, + ), + ).result; + expect(downloadMainResult.bytes, data); + + // secondary bucket + final secondaryResult = await Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath(filePath), + path: storagePath, + options: StorageUploadFileOptions( + pluginOptions: const S3UploadFilePluginOptions( + useAccelerateEndpoint: true, + ), + bucket: secondaryBucket, + ), + ).result; + expect(secondaryResult.uploadedItem.path, path); + + final downloadSecondaryResult = await Amplify.Storage.downloadData( + path: storagePath, + options: StorageDownloadDataOptions( + bucket: secondaryBucket, + ), + ).result; + expect(downloadSecondaryResult.bytes, data); + + expect( + await objectExists( + storagePath, + bucket: mainBucket, + ), + true, + ); + expect( + await objectExists( + storagePath, + bucket: secondaryBucket, + ), + true, + ); + }); + }); + group('upload progress', () { testWidgets('reports progress', (_) async { final fileId = uuid(); diff --git a/packages/storage/amplify_storage_s3/example/integration_test/utils/tear_down.dart b/packages/storage/amplify_storage_s3/example/integration_test/utils/tear_down.dart index 319cc05ee9..21cddc0675 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/utils/tear_down.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/utils/tear_down.dart @@ -7,11 +7,14 @@ import 'package:flutter_test/flutter_test.dart'; final _logger = AmplifyLogger().createChild('StorageTests'); /// Adds a tear down to remove the object at [path]. -void addTearDownPath(StoragePath path) { +void addTearDownPath(StoragePath path, {StorageBucket? bucket}) { addTearDown( () { try { - return Amplify.Storage.remove(path: path).result; + return Amplify.Storage.remove( + path: path, + options: StorageRemoveOptions(bucket: bucket), + ).result; } on Exception catch (e) { _logger.warn('Failed to remove file after test', e); rethrow; 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 f746c1c845..79b2c5fe48 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 @@ -325,6 +325,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface final s3Options = StorageUploadFileOptions( metadata: options?.metadata ?? const {}, pluginOptions: s3PluginOptions, + bucket: options?.bucket, ); final uploadTask = storageS3Service.uploadFile( 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 42f30f155b..99b7237057 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 @@ -363,6 +363,7 @@ class StorageS3Service { FutureOr Function()? onDone, FutureOr Function()? onError, }) { + final s3ClientInfo = getS3ClientInfo(storageBucket: options.bucket); final s3PluginOptions = options.pluginOptions as S3UploadFilePluginOptions? ?? const S3UploadFilePluginOptions(); @@ -375,9 +376,9 @@ class StorageS3Service { ); final uploadDataTask = S3UploadTask.fromAWSFile( localFile, - s3Client: _defaultS3Client, - s3ClientConfig: _defaultS3ClientConfig, - bucket: _storageOutputs.bucketName, + s3Client: s3ClientInfo.client, + s3ClientConfig: s3ClientInfo.config, + bucket: s3ClientInfo.bucketName, awsRegion: _storageOutputs.awsRegion, path: path, options: uploadDataOptions, 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 2b187e664f..ee636b2379 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 @@ -418,6 +418,9 @@ void main() { () async { const defaultOptions = StorageDownloadDataOptions( pluginOptions: S3DownloadDataPluginOptions(), + bucket: StorageBucket.fromBucketInfo( + BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2'), + ), ); when( @@ -435,6 +438,7 @@ void main() { downloadDataOperation = storageS3Plugin.downloadData( path: const StoragePath.fromString('public/$testKey'), + options: defaultOptions, ); final capturedOptions = verify( @@ -769,6 +773,9 @@ void main() { () async { const defaultOptions = StorageUploadFileOptions( pluginOptions: S3UploadFilePluginOptions(), + bucket: StorageBucket.fromBucketInfo( + BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2'), + ), ); when( @@ -787,6 +794,7 @@ void main() { uploadFileOperation = storageS3Plugin.uploadFile( path: testPath, localFile: testLocalFile, + options: defaultOptions, ); final capturedParams = verify( @@ -1012,6 +1020,9 @@ void main() { () async { const defaultOptions = StorageRemoveOptions( pluginOptions: S3RemovePluginOptions(), + bucket: StorageBucket.fromBucketInfo( + BucketInfo(bucketName: 'unit-test-bucket', region: 'us-east-2'), + ), ); when( () => storageS3Service.remove( @@ -1020,7 +1031,10 @@ void main() { ), ).thenAnswer((_) async => testResult); - final removeOperation = storageS3Plugin.remove(path: testPath); + final removeOperation = storageS3Plugin.remove( + path: testPath, + options: defaultOptions, + ); final capturedOptions = verify( () => storageS3Service.remove( From e4b9d7f5ff92315de4ff25270bc771bae735e53c Mon Sep 17 00:00:00 2001 From: NikaHsn Date: Fri, 1 Nov 2024 10:18:33 -0700 Subject: [PATCH 41/42] chore(storage): add e2e tests for upload data api (#5622) --- .../category/amplify_storage_category.dart | 2 - .../amplify_storage_plugin_interface.dart | 1 - .../types/storage/upload_data_options.dart | 9 +++- .../integration_test/download_data_test.dart | 4 +- .../integration_test/get_properties_test.dart | 18 ++++--- .../example/integration_test/remove_test.dart | 8 ++- .../integration_test/upload_data_test.dart | 53 +++++++++++++++++++ .../lib/src/amplify_storage_s3_dart_impl.dart | 3 +- .../service/storage_s3_service_impl.dart | 3 +- .../test/amplify_storage_s3_dart_test.dart | 48 +++-------------- 10 files changed, 89 insertions(+), 60 deletions(-) diff --git a/packages/amplify_core/lib/src/category/amplify_storage_category.dart b/packages/amplify_core/lib/src/category/amplify_storage_category.dart index 84c9603c03..416446d12e 100644 --- a/packages/amplify_core/lib/src/category/amplify_storage_category.dart +++ b/packages/amplify_core/lib/src/category/amplify_storage_category.dart @@ -144,7 +144,6 @@ class StorageCategory extends AmplifyCategory { required StoragePath path, void Function(StorageTransferProgress)? onProgress, StorageUploadDataOptions? options, - StorageBucket? bucket, }) { return identifyCall( StorageCategoryMethod.uploadData, @@ -153,7 +152,6 @@ class StorageCategory extends AmplifyCategory { data: data, onProgress: onProgress, options: options, - bucket: bucket, ), ); } diff --git a/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart b/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart index efb891fbef..c4d874d8a0 100644 --- a/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart +++ b/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart @@ -63,7 +63,6 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { required StorageDataPayload data, void Function(StorageTransferProgress)? onProgress, StorageUploadDataOptions? options, - StorageBucket? bucket, }) { throw UnimplementedError('uploadData() has not been implemented.'); } diff --git a/packages/amplify_core/lib/src/types/storage/upload_data_options.dart b/packages/amplify_core/lib/src/types/storage/upload_data_options.dart index eb997552de..3b7bd9eb7b 100644 --- a/packages/amplify_core/lib/src/types/storage/upload_data_options.dart +++ b/packages/amplify_core/lib/src/types/storage/upload_data_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.upload_data_options} /// Configurable options for `Amplify.Storage.uploadData`. @@ -15,16 +15,20 @@ class StorageUploadDataOptions const StorageUploadDataOptions({ this.metadata = const {}, this.pluginOptions, + this.bucket, }); /// The metadata attached to the object to be uploaded. final Map metadata; + /// Optionally specify which bucket to target. + final StorageBucket? bucket; + /// {@macro amplify_core.storage.upload_data_plugin_options} final StorageUploadDataPluginOptions? pluginOptions; @override - List get props => [metadata, pluginOptions]; + List get props => [metadata, pluginOptions, bucket]; @override String get runtimeTypeName => 'StorageUploadDataOptions'; @@ -33,6 +37,7 @@ class StorageUploadDataOptions Map toJson() => { 'metadata': metadata, 'pluginOptions': pluginOptions?.toJson(), + 'bucket': bucket, }; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart index 96dd7c7c36..bf5f7b96f0 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/download_data_test.dart @@ -142,7 +142,9 @@ void main() { await Amplify.Storage.uploadData( path: StoragePath.fromString(publicPath), data: StorageDataPayload.bytes(bytesData), - bucket: secondaryBucket, + options: StorageUploadDataOptions( + bucket: secondaryBucket, + ), ).result; final downloadResult = await Amplify.Storage.downloadData( diff --git a/packages/storage/amplify_storage_s3/example/integration_test/get_properties_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/get_properties_test.dart index 06116cbb75..5bdf3170b0 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/get_properties_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/get_properties_test.dart @@ -112,14 +112,18 @@ void main() { await Amplify.Storage.uploadData( data: StorageDataPayload.bytes(data), path: StoragePath.fromString(path), - options: const StorageUploadDataOptions(metadata: metadata), - bucket: mainBucket, + options: StorageUploadDataOptions( + metadata: metadata, + bucket: mainBucket, + ), ).result; await Amplify.Storage.uploadData( data: StorageDataPayload.bytes(data), path: StoragePath.fromString(path), - options: const StorageUploadDataOptions(metadata: metadata), - bucket: secondaryBucket, + options: StorageUploadDataOptions( + metadata: metadata, + bucket: secondaryBucket, + ), ).result; }); @@ -156,8 +160,10 @@ void main() { await Amplify.Storage.uploadData( data: StorageDataPayload.bytes(data), path: StoragePath.fromString(expectedResolvedPath), - options: const StorageUploadDataOptions(metadata: metadata), - bucket: secondaryBucket, + options: StorageUploadDataOptions( + metadata: metadata, + bucket: secondaryBucket, + ), ).result; final result = await Amplify.Storage.getProperties( path: StoragePath.fromIdentityId( diff --git a/packages/storage/amplify_storage_s3/example/integration_test/remove_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/remove_test.dart index 6a8d441be7..c54822a190 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/remove_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/remove_test.dart @@ -78,7 +78,9 @@ void main() { await Amplify.Storage.uploadData( data: StorageDataPayload.bytes('data'.codeUnits), path: storagePath, - bucket: mainBucket, + options: StorageUploadDataOptions( + bucket: mainBucket, + ), ).result; }); @@ -95,7 +97,9 @@ void main() { await Amplify.Storage.uploadData( data: StorageDataPayload.bytes('data'.codeUnits), path: storagePath, - bucket: secondaryBucket, + options: StorageUploadDataOptions( + bucket: secondaryBucket, + ), ).result; expect( diff --git a/packages/storage/amplify_storage_s3/example/integration_test/upload_data_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/upload_data_test.dart index 89880ae4ef..68fd42aa35 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/upload_data_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/upload_data_test.dart @@ -252,6 +252,59 @@ void main() { }); }); + group('multi-bucket', () { + final mainBucket = + StorageBucket.fromOutputs('Storage Integ Test main bucket'); + final secondaryBucket = StorageBucket.fromOutputs( + 'Storage Integ Test secondary bucket', + ); + + testWidgets('uploads to multiple buckets', (_) async { + final path = 'public/multi-bucket-upload-data-${uuid()}'; + final storagePath = StoragePath.fromString(path); + final data = 'multi bucket upload data byte'.codeUnits; + addTearDownMultiBucket( + storagePath, + [mainBucket, secondaryBucket], + ); + // main bucket + final mainResult = await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: storagePath, + options: StorageUploadDataOptions( + bucket: mainBucket, + ), + ).result; + expect(mainResult.uploadedItem.path, path); + + final downloadMainResult = await Amplify.Storage.downloadData( + path: storagePath, + options: StorageDownloadDataOptions( + bucket: mainBucket, + ), + ).result; + expect(downloadMainResult.bytes, data); + + // secondary bucket + final secondaryResult = await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: storagePath, + options: StorageUploadDataOptions( + bucket: secondaryBucket, + ), + ).result; + expect(secondaryResult.uploadedItem.path, path); + + final downloadSecondaryResult = await Amplify.Storage.downloadData( + path: storagePath, + options: StorageDownloadDataOptions( + bucket: secondaryBucket, + ), + ).result; + expect(downloadSecondaryResult.bytes, data); + }); + }); + group('upload progress', () { testWidgets('reports progress for byte data', (_) async { final path = 'public/upload-data-progress-bytes-${uuid()}'; 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 79b2c5fe48..41cbcc5df8 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 @@ -277,7 +277,6 @@ class AmplifyStorageS3Dart extends StoragePluginInterface required StoragePath path, void Function(S3TransferProgress)? onProgress, StorageUploadDataOptions? options, - StorageBucket? bucket, }) { final s3PluginOptions = reifyPluginOptions( pluginOptions: options?.pluginOptions, @@ -286,6 +285,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface final s3Options = StorageUploadDataOptions( metadata: options?.metadata ?? const {}, + bucket: options?.bucket, pluginOptions: s3PluginOptions, ); @@ -294,7 +294,6 @@ class AmplifyStorageS3Dart extends StoragePluginInterface dataPayload: data, options: s3Options, onProgress: onProgress, - bucket: bucket, ); return S3UploadDataOperation( 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 99b7237057..783eed55c2 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 @@ -330,9 +330,8 @@ class StorageS3Service { void Function(S3TransferProgress)? onProgress, FutureOr Function()? onDone, FutureOr Function()? onError, - StorageBucket? bucket, }) { - final s3ClientInfo = getS3ClientInfo(storageBucket: bucket); + final s3ClientInfo = getS3ClientInfo(storageBucket: options.bucket); final uploadDataTask = S3UploadTask.fromDataPayload( dataPayload, s3Client: s3ClientInfo.client, 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 ee636b2379..239b97187b 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 @@ -613,6 +613,12 @@ void main() { test('should forward options to StorageS3Service.uploadData API', () async { const testOptions = StorageUploadDataOptions( + bucket: StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'test-bucket', + region: 'test-region', + ), + ), pluginOptions: S3UploadDataPluginOptions( getProperties: true, useAccelerateEndpoint: true, @@ -649,48 +655,6 @@ void main() { ); }); - test('should forward bucket to StorageS3Service.uploadData API', - () async { - const testBucket = StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'test-bucket', - region: 'test-region', - ), - ); - when( - () => storageS3Service.uploadData( - path: testPath, - dataPayload: any(named: 'dataPayload'), - options: any(named: 'options'), - bucket: testBucket, - ), - ).thenAnswer((_) => testS3UploadTask); - - when(() => testS3UploadTask.result).thenAnswer((_) async => testItem); - uploadDataOperation = storageS3Plugin.uploadData( - data: testData, - path: testPath, - bucket: testBucket, - ); - final capturedBucket = verify( - () => storageS3Service.uploadData( - path: testPath, - dataPayload: any(named: 'dataPayload'), - options: any( - named: 'options', - ), - bucket: captureAny( - named: 'bucket', - ), - ), - ).captured.last; - - expect( - capturedBucket, - testBucket, - ); - }); - test('should forward options.metadata to StorageS3Service.uploadData API', () async { const testMetadata = { From 309fca4526f46725a2883deb1f2dd704ab48b3b1 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:22:31 -0800 Subject: [PATCH 42/42] updated tests to account for changes to uploadData api --- .../amplify_core/lib/src/types/storage/list_options.dart | 3 --- .../example/integration_test/list_test.dart | 8 ++++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/amplify_core/lib/src/types/storage/list_options.dart b/packages/amplify_core/lib/src/types/storage/list_options.dart index 4a63e1ec2b..f0a1d80962 100644 --- a/packages/amplify_core/lib/src/types/storage/list_options.dart +++ b/packages/amplify_core/lib/src/types/storage/list_options.dart @@ -31,9 +31,6 @@ class StorageListOptions /// Optionally specify which bucket to retrieve final StorageBucket? bucket; - /// an optional bucket to specify which bucket to return the list for - final StorageBucket? bucket; - @override List get props => [pageSize, nextToken, pluginOptions, bucket]; diff --git a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart index a1d3d651f2..15c3737225 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/list_test.dart @@ -39,7 +39,9 @@ void main() { await Amplify.Storage.uploadData( path: StoragePath.fromString(uploadedPaths[pathIndex]), data: StorageDataPayload.bytes('test content'.codeUnits), - bucket: mainBucket, + options: StorageUploadDataOptions( + bucket: mainBucket, + ), ).result; } for (var pathIndex = uploadedPaths.length ~/ 2; @@ -48,7 +50,9 @@ void main() { await Amplify.Storage.uploadData( path: StoragePath.fromString(uploadedPaths[pathIndex]), data: StorageDataPayload.bytes('test content'.codeUnits), - bucket: secondaryBucket, + options: StorageUploadDataOptions( + bucket: secondaryBucket, + ), ).result; } for (final path in uploadedPaths) {