From 9926a9bab0ca9fe68fad5ef6cb99fa3fb6b08d53 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:34:47 -0700 Subject: [PATCH 01/88] chore: analytics transfer --- src/data/platforms.ts | 4 +- .../flutter/enable-disable/enable.mdx | 2 +- .../flutter/getting-started/10_preReq.mdx | 14 ++++-- .../flutter/getting-started/20_installLib.mdx | 11 ++--- .../getting-started/30_initAnalytics.mdx | 8 +++- .../flutter/getting-started/40_record.mdx | 2 +- .../lib-v1/analytics/flutter/identifyuser.mdx | 47 ++++++++++++------- .../lib-v1/analytics/flutter/record.mdx | 43 +++++++++++++++-- 8 files changed, 93 insertions(+), 38 deletions(-) diff --git a/src/data/platforms.ts b/src/data/platforms.ts index 43c15c08edb..9d73bfcb718 100644 --- a/src/data/platforms.ts +++ b/src/data/platforms.ts @@ -49,8 +49,8 @@ export const PLATFORM_VERSIONS = { current: 'v2' }, flutter: { - prev: 'v0', - current: 'v1' + prev: 'v1', + current: 'v2' }, javascript: { prev: 'v5', diff --git a/src/fragments/lib-v1/analytics/flutter/enable-disable/enable.mdx b/src/fragments/lib-v1/analytics/flutter/enable-disable/enable.mdx index b4bea5bebd0..6eb1405d78f 100644 --- a/src/fragments/lib-v1/analytics/flutter/enable-disable/enable.mdx +++ b/src/fragments/lib-v1/analytics/flutter/enable-disable/enable.mdx @@ -1,3 +1,3 @@ ```dart -await Amplify.Analytics.enable(); +Amplify.Analytics.enable(); ``` diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx index ac8c9802dab..167ff29e0f0 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx @@ -1,5 +1,11 @@ -- [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) -- A Flutter application targeting Flutter SDK >= 2.10.0 (stable version) with Amplify libraries integrated - - An iOS configuration targeting at least iOS 11.0 - - An Android configuration targeting at least Android API level 21 (Android 5.0) or above +- A Flutter application targeting Flutter SDK >=3.3.0 with Amplify libraries integrated + + The following are also required, depending on which platforms you are targeting: + + - An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 + - An Android configuration targeting at least Android API level 24 (Android 7.0) or above + - Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) + - Any Windows OS meeting Flutter minimums + - macOS version 10.15 or higher + - Any Ubuntu Linux distribution meeting Flutter minimums - For a full example please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/20_installLib.mdx index 0e680af9d42..c88f89f169f 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/20_installLib.mdx @@ -6,13 +6,10 @@ Add Analytics by adding these libraries into your dependencies block: ```yaml environment: - sdk: '>=2.15.0 <3.0.0' + sdk: '>=2.18.0 <4.0.0' dependencies: - # Should already be added during Project Setup walkthrough - amplify_flutter: ^0.6.0 - - # Add these lines in `dependencies` if you have not added it earlier during the Project Setup - amplify_auth_cognito: ^0.6.0 - amplify_analytics_pinpoint: ^0.6.0 + amplify_analytics_pinpoint: ^1.0.0 + amplify_auth_cognito: ^1.0.0 + amplify_flutter: ^1.0.0 ``` diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx index 717a7d8182c..63341f691c5 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx @@ -16,7 +16,13 @@ Future _configureAmplify() async { } ``` -Make sure that the `amplifyconfiguration.dart` file generated in the project setup is included and sent to `Amplify.configure`: + + +When running your app on MacOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/[platform]/start/project-setup/platform-setup/#enable-keychain). + + + +Make sure that the amplifyconfiguration.dart file generated in the project setup is included and sent to Amplify.configure: ```dart import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/40_record.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/40_record.mdx index 90d1c775b38..454da0f318b 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/40_record.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/40_record.mdx @@ -6,7 +6,7 @@ To record an event, create an `AnalyticsEvent` and call `Amplify.Analytics.recor Future trackEventsWithProperties() async { final event = AnalyticsEvent('test'); - event.properties + event.customProperties ..addBoolProperty('boolKey', true) ..addDoubleProperty('doubleKey', 10) ..addIntProperty('intKey', 10) diff --git a/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx b/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx index 338718a2f7a..63d0e75bda2 100644 --- a/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx +++ b/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx @@ -2,7 +2,16 @@ This call sends information that you have specified about a user to Amazon Pinpo You can get the current user's ID from the Amplify Auth category as shown per the Auth category documentation. Be sure to have it ready before you set it as shown below (Check out the [Authentication Getting Started](/gen1/[platform]/prev/build-a-backend/auth/set-up-auth/) guide for detailed explanation). -If you have asked for location access and received permission, you can also provide that in `AnalyticsUserProfileLocation`. +If you have asked for location access and received permission, you can also provide that in `UserProfileLocation` + + + +Breaking changes from v0 to v1: + +The Analytics- prefix of the original `AnalyticsUserProfile` and `AnalyticsUserProfileLocation` classes is removed. Furthermore, `AnalyticsProperties` is renamed to `CustomProperties`. + + + ```dart Future addAnalyticsWithLocation({ @@ -12,23 +21,21 @@ Future addAnalyticsWithLocation({ required String phoneNumber, required int age, }) async { - final location = AnalyticsUserProfileLocation() - ..latitude = 47.606209 - ..longitude = -122.332069 - ..postalCode = '98122' - ..city = 'Seattle' - ..region = 'WA' - ..country = 'USA'; - - final properties = AnalyticsProperties() - ..addStringProperty('phoneNumber', phoneNumber) - ..addIntProperty('age', age); - - final userProfile = AnalyticsUserProfile() - ..name = name - ..email = email - ..location = location - ..properties = properties; + final userProfile = UserProfile( + name: name, + email: email, + location: const UserProfileLocation( + latitude: 47.606209, + longitude: -122.332069, + postalCode: '98122', + city: 'Seattle', + region: 'WA', + country: 'USA', + ), + customProperties: CustomProperties() + ..addStringProperty('phoneNumber', phoneNumber) + ..addIntProperty('age', age), + ); await Amplify.Analytics.identifyUser( userId: userId, @@ -36,3 +43,7 @@ Future addAnalyticsWithLocation({ ); } ``` + +import flutter0 from "/src/fragments/lib/analytics/native_common/identify-use-cases.mdx"; + + diff --git a/src/fragments/lib-v1/analytics/flutter/record.mdx b/src/fragments/lib-v1/analytics/flutter/record.mdx index 3245e07bb2b..a454009f2c9 100644 --- a/src/fragments/lib-v1/analytics/flutter/record.mdx +++ b/src/fragments/lib-v1/analytics/flutter/record.mdx @@ -6,13 +6,13 @@ The Amplify analytics plugin also makes it easy to record custom events within t Future recordCustomEvent() async { final event = AnalyticsEvent('PasswordReset'); - event.properties + event.customProperties ..addStringProperty('Channel', 'SMS') ..addBoolProperty('Successful', true); // You can also add the properties one by one like the following - event.properties.addIntProperty('ProcessDuration', 792); - event.properties.addDoubleProperty('doubleKey', 120.3); + event.customProperties.addIntProperty('ProcessDuration', 792); + event.customProperties.addDoubleProperty('doubleKey', 120.3); await Amplify.Analytics.recordEvent(event: event); } @@ -28,6 +28,33 @@ However, it can take upwards of 30 minutes for the event to display in the Filte ## Flush events +Events have default configuration to flush out to the network every 30 seconds. If you would like to change this, update `amplifyconfiguration.dart` with the value in milliseconds you would like for `autoFlushEventsInterval`. This configuration will flush events every 10 seconds: + +```json +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "analytics": { + "plugins": { + "awsPinpointAnalyticsPlugin": { + "pinpointAnalytics": { + "appId": "AppID", + "region": "Region" + }, + "pinpointTargeting": { + "region": "Region" + }, + "autoFlushEventsInterval": 10 + } + } + } +} +``` + +> **Note** +> +> Setting `autoFlushEventsInterval` to 0 will **disable** the automatic flush of events and you will be responsible for submitting them. + To manually flush events, call: ```dart @@ -40,7 +67,7 @@ You can register global properties which will be sent along with all invocations ```dart Future registerGlobalProperties() async { - final properties = AnalyticsProperties() + final properties = CustomProperties() ..addStringProperty('AppStyle', 'DarkMode'); await Amplify.Analytics.registerGlobalProperties( globalProperties: properties, @@ -57,3 +84,11 @@ Future unregisterGlobalProperties() async { ); } ``` + +Furthermore, you can remove all global properties by calling `unregisterGlobalProperties` without `propertyNames`: + +```dart +Future unregisterAllGlobalProperties() async { + await Amplify.Analytics.unregisterGlobalProperties(); +} +``` From 28f8974ab899c79d1aa5826ce1dcbd08bfb31569 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 23 Apr 2024 17:25:08 -0700 Subject: [PATCH 02/88] chore: authentication transfer from current to prev --- .../10_fetchAuthSession.mdx | 35 +- .../lib-v1/auth/flutter/advanced/advanced.mdx | 81 ++++ .../flutter/delete_user/10_delete_user.mdx | 6 +- .../device_features/10_rememberDevice.mdx | 6 +- .../device_features/20_forgetDevice.mdx | 18 +- .../device_features/30_fetchDevice.mdx | 6 +- .../10_existingResources.mdx | 2 +- .../flutter/getting_started/10_preReq.mdx | 9 +- .../flutter/getting_started/20_installLib.mdx | 2 +- .../flutter/getting_started/30_initAuth.mdx | 2 +- .../getting_started/70_configureBackend.mdx | 184 +++++++++ .../flutter/hub_events/10_listen_events.mdx | 23 +- .../10_managing_credentials.mdx | 60 ++- .../password_management/10_reset_password.mdx | 31 +- .../20_confirm_reset_password.mdx | 19 +- .../30_change_password.mdx | 13 +- .../lib-v1/auth/flutter/signin/10_signUp.mdx | 54 ++- .../auth/flutter/signin/20_confirmSignUp.mdx | 25 +- .../lib-v1/auth/flutter/signin/30_signIn.mdx | 64 ++- .../flutter/signin/40_multi_factor_signup.mdx | 26 +- .../signin/50_multi_factor_confirm_signin.mdx | 11 +- .../flutter/signin/60_runtime_auth_flow.mdx | 15 +- .../flutter/signin_next_steps/10_signin.mdx | 50 +++ .../signin_next_steps/20_confirm_sms_mfa.mdx | 40 ++ .../30_confirm_custom_challenge.mdx | 39 ++ .../40_confirm_new_password.mdx | 27 ++ .../signin_next_steps/50_reset_password.mdx | 36 ++ .../signin_next_steps/60_confirm_signup.mdx | 53 +++ .../flutter/signin_next_steps/70_done.mdx | 13 + .../flutter/signin_web_ui/10_cli_setup.mdx | 14 +- .../20_platform_specific_setup.mdx | 70 ++-- .../auth/flutter/signin_web_ui/30_signin.mdx | 12 +- .../signin_web_ui/40_private_session.mdx | 12 +- .../signin_with_custom_flow/20_signup.mdx | 19 +- .../signin_with_custom_flow/30_signin.mdx | 20 +- .../40_custom_challenge.mdx | 13 +- .../auth/flutter/signout/10_local_signout.mdx | 9 +- .../flutter/signout/20_global_signout.mdx | 19 +- .../auth/flutter/sms/confirm_sign_in.mdx | 2 +- .../auth/flutter/sms/confirm_sign_up.mdx | 5 +- .../lib-v1/auth/flutter/sms/sign_up.mdx | 8 +- .../social_signin_web_ui/20_signin.mdx | 12 +- .../user_attributes/10_fetch_attributes.mdx | 4 +- .../20_update_user_attribute.mdx | 74 ++-- .../user_attributes/30_confirm_attribute.mdx | 7 +- .../user_attributes/40_resend_code.mdx | 9 +- .../user_attributes/50_custom_attributes.mdx | 26 ++ .../auth/admin_actions/index.mdx | 369 ++++++++++++++++++ .../auth/existing-resources-no-cli/index.mdx | 33 ++ .../auth/import-existing-resources/index.mdx | 111 ++++++ .../auth/override-cognito/index.mdx | 185 +++++++++ .../auth/user-group-management/index.mdx | 117 ++++++ .../auth/advanced-workflows/index.mdx | 5 + .../auth/existing-resources/index.mdx | 2 +- .../auth/under-the-hood/index.mdx | 1 - 55 files changed, 1809 insertions(+), 299 deletions(-) create mode 100644 src/fragments/lib-v1/auth/flutter/advanced/advanced.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/10_signin.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/20_confirm_sms_mfa.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/40_confirm_new_password.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/70_done.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/auth/admin_actions/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx diff --git a/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx b/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx index a776ffd5363..34434da6f25 100644 --- a/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx +++ b/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx @@ -1,33 +1,32 @@ +However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by calling fetchAuthSession on the Cognito Auth Plugin. This will return a `CognitoAuthSession`, which has additional attributes compared to `AuthSession`, which is typically returned by fetchAuthSession. See the example below: + ```dart Future fetchAuthSession() async { try { - final result = await Amplify.Auth.fetchAuthSession( - options: CognitoSessionOptions(getAWSCredentials: true), - ); - String identityId = (result as CognitoAuthSession).identityId!; - safePrint('identityId: $identityId'); + final result = await Amplify.Auth.fetchAuthSession(); + safePrint('User is signed in: ${result.isSignedIn}'); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error retrieving auth session: ${e.message}'); } } ``` -If the `getAWSCredentials` option is true, the result will contain AWS credentials and tokens. If it is set to false, the result will contain a simple `isSignedIn` flag. - -### Setting a timeout for fetchAuthSession +### Retrieving AWS credentials -On spotty networks, the `fetchAuthSession` call can take upwards of a minute to either complete or fail due to internal retries. If this is too long, consider adding a custom timeout using the `timeout` function as shown in the below example. +Sometimes it can be helpful to retrieve the instance of the underlying plugin +which has more specific typing. In the case of Cognito, calling `fetchAuthSession` +on the Cognito plugin returns AWS-specific values such as the identity ID, +AWS credentials, and Cognito User Pool tokens. ```dart -Future fetchAuthSessionWithTimeout() async { +Future fetchCognitoAuthSession() async { try { - final result = await Amplify.Auth.fetchAuthSession().timeout( - const Duration(seconds: 5), - ); - final identityId = (result as CognitoAuthSession).identityId!; - safePrint('identityId: $identityId'); - } on Exception catch (e) { - safePrint('Something went wrong while fetching the session: $e'); + final cognitoPlugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); + final result = await cognitoPlugin.fetchAuthSession(); + final identityId = result.identityIdResult.value; + safePrint("Current user's identity ID: $identityId"); + } on AuthException catch (e) { + safePrint('Error retrieving auth session: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/advanced/advanced.mdx b/src/fragments/lib-v1/auth/flutter/advanced/advanced.mdx new file mode 100644 index 00000000000..0b6c999d76f --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/advanced/advanced.mdx @@ -0,0 +1,81 @@ +## Identity Pool Federation + +With identity federation, you don't need to create custom sign-in code or manage your own user identities. Instead, users of your app can sign in using a well-known external identity +provider (IdP), such as Login with Amazon, Facebook, Google, or any other OpenID Connect (OIDC)-compatible IdP. They can receive an authentication token, and then exchange that token for +temporary security credentials in AWS that map to an IAM role with permissions to use the resources in your AWS account. Using an IdP helps you keep your AWS account secure because you +don't have to embed and distribute long-term security credentials with your application. + +Imagine that you are creating a mobile app that accesses AWS resources, such as a game that runs on a mobile device and stores player and score information using Amazon S3 and DynamoDB. + +When you write such an app, you make requests to AWS services that must be signed with an AWS access key. However, we strongly recommend that you do not embed or distribute long-term +AWS credentials with apps that a user downloads to a device, even in an encrypted store. Instead, build your app so that it requests temporary AWS security credentials dynamically when +needed using identity federation. The supplied temporary credentials map to an AWS role that has only the permissions needed to perform the tasks required by the mobile app. + +You can use `federateToIdentityPool` to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. If you logged in with `Auth.signIn` you **cannot** +call `federateToIdentityPool` as Amplify will perform this federation automatically for you in the background. In general, you should only call `Auth.federatedSignIn()` when using OAuth flows. + +You can use the escape hatch API `federateToIdentityPool` with a valid token from other social providers. + +```dart +final cognitoPlugin = + Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); +const googleIdToken = 'idToken'; +final session = await cognitoPlugin.federateToIdentityPool( + token: googleIdToken, + provider: AuthProvider.google, +); +``` + + + +Note that when federated, APIs such as `Auth.getCurrentUser` will throw an error as the user is not authenticated with User Pools. + + + +### Retrieve Session + +After federated login, you can retrieve the session using the `Auth.fetchAuthSession` API. + +### Token Refresh + + + +Automatic authentication token refresh is NOT supported when federated. + + + +By default, Amplify will **NOT** automatically refresh the tokens from the federated providers. You will need to handle the token refresh logic and provide the new token to the `federateToIdentityPool` API. + +### Clear Session + +You can clear the federated session using the `clearFederationToIdentityPool` API. + +```dart +final cognitoPlugin = + Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); +await cognitoPlugin.clearFederationToIdentityPool(); +``` + + + +`clearFederationToIdentityPool` will only clear the session from the local cache; the developer needs to handle signing out from the federated identity provider. + + + +### Provide Custom Identity ID + +You can provide a custom identity ID to the `federateToIdentityPool` API. This is useful when you want to use the same identity ID across multiple sessions. + +```dart +final cognitoPlugin = + Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); +const googleIdToken = 'idToken'; +const identityId = 'us-west-2:b4cd4809-7ab1-42e1-b044-07dab9eaa768'; +final session = await cognitoPlugin.federateToIdentityPool( + token: googleIdToken, + provider: AuthProvider.google, + options: FederateToIdentityPoolOptions( + developerProvidedIdentityId: identityId, + ), +); +``` diff --git a/src/fragments/lib-v1/auth/flutter/delete_user/10_delete_user.mdx b/src/fragments/lib-v1/auth/flutter/delete_user/10_delete_user.mdx index fae1d39af5f..85a9fbe9838 100644 --- a/src/fragments/lib-v1/auth/flutter/delete_user/10_delete_user.mdx +++ b/src/fragments/lib-v1/auth/flutter/delete_user/10_delete_user.mdx @@ -2,9 +2,9 @@ Future deleteUser() async { try { await Amplify.Auth.deleteUser(); - print('Delete user succeeded'); - } on Exception catch (e) { - print('Delete user failed with error: $e'); + safePrint('Delete user succeeded'); + } on AuthException catch (e) { + safePrint('Delete user failed with error: $e'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/device_features/10_rememberDevice.mdx b/src/fragments/lib-v1/auth/flutter/device_features/10_rememberDevice.mdx index 43bacb50aa7..bdae2b1fea9 100644 --- a/src/fragments/lib-v1/auth/flutter/device_features/10_rememberDevice.mdx +++ b/src/fragments/lib-v1/auth/flutter/device_features/10_rememberDevice.mdx @@ -2,9 +2,9 @@ Future rememberCurrentDevice() async { try { await Amplify.Auth.rememberDevice(); - print('Remember device succeeded'); - } on Exception catch (e) { - print('Remember device failed with error: $e'); + safePrint('Remember device succeeded'); + } on AuthException catch (e) { + safePrint('Remember device failed with error: $e'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/device_features/20_forgetDevice.mdx b/src/fragments/lib-v1/auth/flutter/device_features/20_forgetDevice.mdx index cdff5fa7419..be260d1d20f 100644 --- a/src/fragments/lib-v1/auth/flutter/device_features/20_forgetDevice.mdx +++ b/src/fragments/lib-v1/auth/flutter/device_features/20_forgetDevice.mdx @@ -6,9 +6,9 @@ Future forgetCurrentDevice() async { try { await Amplify.Auth.forgetDevice(); - print('Forget device succeeded'); - } on Exception catch (e) { - print('Forget device failed with error: $e'); + safePrint('Forget device succeeded'); + } on AuthException catch (e) { + safePrint('Forget device failed with error: $e'); } } ``` @@ -17,13 +17,13 @@ Future forgetCurrentDevice() async { ```dart -Future forgetSpecificDevice() async { +// A device that was fetched via Amplify.Auth.fetchDevices() +Future forgetSpecificDevice(AuthDevice myDevice) async { try { - const device = CognitoDevice(id: 'us-west-2_38284cea-6c7f-4a8c-bcfa-ac8946a0d1eb'); - await Amplify.Auth.forgetDevice(device); - print('Forget device succeeded'); - } on Exception catch (e) { - print('Forget device failed with error: $e'); + await Amplify.Auth.forgetDevice(myDevice); + safePrint('Forget device succeeded'); + } on AuthException catch (e) { + safePrint('Forget device failed with error: $e'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/device_features/30_fetchDevice.mdx b/src/fragments/lib-v1/auth/flutter/device_features/30_fetchDevice.mdx index 2e6bdeabc04..4e8454369f0 100644 --- a/src/fragments/lib-v1/auth/flutter/device_features/30_fetchDevice.mdx +++ b/src/fragments/lib-v1/auth/flutter/device_features/30_fetchDevice.mdx @@ -3,10 +3,10 @@ Future fetchAllDevices() async { try { final devices = await Amplify.Auth.fetchDevices(); for (final device in devices) { - print('Device: $device'); + safePrint('Device: $device'); } - } on Exception catch (e) { - print('Fetch devices failed with error: $e'); + } on AuthException catch (e) { + safePrint('Fetch devices failed with error: $e'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/existing_resources/10_existingResources.mdx b/src/fragments/lib-v1/auth/flutter/existing_resources/10_existingResources.mdx index 382e1c4f8d9..8314a534d1d 100644 --- a/src/fragments/lib-v1/auth/flutter/existing_resources/10_existingResources.mdx +++ b/src/fragments/lib-v1/auth/flutter/existing_resources/10_existingResources.mdx @@ -40,7 +40,7 @@ const amplifyconfig = ''' { "profile", "aws.cognito.signin.user.admin" ] - }, + } } } } diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx index 6b9fe0c90c9..3f857db3479 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx @@ -1,8 +1,3 @@ -A Flutter application targeting Flutter SDK >= 2.10.0 with Amplify libraries integrated. +A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated. -The following are also required, depending on which platforms you are targeting: - - * An iOS configuration targeting at least iOS 11.0 - * An Android configuration targeting at least Android API level 21 (Android 5.0) or above - -For a full example please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) +Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx index 99f37ef9869..91716d5c21f 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx @@ -1,5 +1,5 @@ Add the following dependency to your **app**'s `pubspec.yaml` along with others you added above in **Prerequisites**: -import flutter0 from "/src/fragments/lib-v1/project-setup/flutter/create-application/60_dependencies.mdx"; +import flutter0 from "/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx"; \ No newline at end of file diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/30_initAuth.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/30_initAuth.mdx index be239633988..2107fbafe41 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/30_initAuth.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/30_initAuth.mdx @@ -14,7 +14,7 @@ void main() { } class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({super.key}); @override State createState() => _MyAppState(); diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx new file mode 100644 index 00000000000..cf7c8988832 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx @@ -0,0 +1,184 @@ + + + +> Prerequisites: [Install and configure](/[platform]/start/project-setup/prerequisites/#install-and-configure-the-amplify-cli) the Amplify CLI in addition to the Amplify libraries and [necessary dependencies](/[platform]/build-a-backend/auth/set-up-auth/#install-amplify-libraries). + +To start provisioning auth resources in the backend, go to your project directory and **execute the command**: + +```bash +amplify add auth +``` + +Enter the following when prompted: + +```console +? Do you want to use the default authentication and security configuration? + `Default configuration` +? How do you want users to be able to sign in? + `Username` +? Do you want to configure advanced settings? + `No, I am done.` +``` + +> If you have previously enabled an Amplify category that uses Auth behind the scenes (e.g. API category), you can run the `amplify update auth` command to edit your configuration if needed. + +To push your changes to the cloud, **execute the command**: + +```bash +amplify push +``` + +import ios3 from '/src/fragments/lib/auth/ios/getting_started/12_amplifyConfig.mdx'; + + + +import android4 from '/src/fragments/lib/auth/android/getting_started/12_amplifyConfig.mdx'; + + + +import flutter5 from '/src/fragments/lib/auth/flutter/getting_started/12_amplifyConfig.mdx'; + + + + + + + +> Prerequisites: [Install and configure](/[platform]/start/project-setup/prerequisites/#install-and-configure-the-amplify-cli) the Amplify CLI in addition to the Amplify libraries and [necessary dependencies](/[platform]/build-a-backend/auth/set-up-auth/#install-amplify-libraries). + +To import existing Amazon Cognito resources into your Amplify project, **execute the command**: + +```bash +amplify import auth +``` + +```console +? What type of auth resource do you want to import? + Cognito User Pool and Identity Pool + Cognito User Pool only +``` + +Once you've selected an option, you'll be able to search for and import an existing Cognito User Pool and Identity Pool (or User Pool only) within your AWS account. The `amplify import auth` command will also do the following: + +- Automatically populate your Amplify Library configuration file (`amplifyconfiguration.dart`) with your chosen Amazon Cognito resource information +- Provide your designated existing Cognito resource as the authentication & authorization mechanism for all auth-dependent categories (API, Storage and more) +- Enable Lambda functions to access the chosen Cognito resource if you permit it + +> If you have previously enabled an Amplify category that uses Auth behind the scenes (e.g. API category), you can run the `amplify update auth` command to edit your configuration if needed. + +After configuring your Authentication options, update your backend and deploy the service by running the `push` command: + +```bash +amplify push +``` + +Now, the authentication service has been deployed and you can start using it. To view the deployed services in your project at any time, go to Amplify Console by running the following command: + +```bash +amplify console +``` + +For more details, see how to [Use an existing Cognito User Pool and Identity Pool](/[platform]/tools/cli/commands/#auth-import). + + + + + +> Prerequisites: [Install and configure](/[platform]/start/project-setup/prerequisites/#install-and-configure-the-amplify-cli) the Amplify CLI in addition to the Amplify libraries and [necessary dependencies](/[platform]/build-a-backend/auth/set-up-auth/#install-amplify-libraries). + +Amplify Studio allows you create auth resources, set up authorization rules, implement Multi-factor authentication (MFA), and more via an intuitive UI. To set up Authentication through the Amplify Studio, take the following steps: + +1. **Sign in** to the [AWS Management Console](https://console.aws.amazon.com/console/home) and open AWS Amplify. +2. In the navigation pane, **choose an application**. +3. On the application information page, choose the **Backend environments** tab, then choose **Launch Studio**. +4. On the **Set up** menu, choose **Authentication**. +5. In the **Configure log in** section, choose a login mechanism to add from the **Add login mechanism** list. Valid options are _Username_, _Phone number_, _Facebook_, _Google_, _Amazon_, and _Sign in with Apple_. If you choose one of the social sign-in mechanisms (i.e. _Facebook_, _Google_, _Amazon_, or _Sign in with Apple_), you will also need to enter your _App ID_, _App Secret_, and redirect URLs. +6. (Optional) Add multi-factor authentication (MFA). MFA is set to **Off** by default. To turn on MFA, do the following in the **Multi-factor authentication** section: + +- Choose **Enforced** to require MFA for all users or choose **Optional** to allow individual users to enable MFA. +- (Optional) Choose **SMS**, and enter your SMS message. +- (Optional) Choose **Authenticator Application** if you want your app to load with an authentication flow that includes sign up and sign in. + +7. In the **Configure sign up** section, expand **Password protection settings** and customize the password policy settings to enforce. u6. Choose **Save and Deploy**. This starts a CloudFormation deployment with the progress displayed in the upper right corner of the page. +8. After creating and configuring your auth resources, you'll need to pull them down from Amplify Studio. To do so, simply click on "Local setup instructions" in the upper right hand corner of the Studio console and execute the CLI command it provides at the root directory of your app. + +> You can also [import](/[platform]/tools/console/auth/import/) existing Amazon Cognito resources and [manage users and groups](/[platform]/tools/console/auth/user-management/) through the Amplify Studio UI. + + + + + +Existing Authentication resources from AWS (e.g. Amazon Cognito UserPools or Identity Pools) can be used with the Amplify Libraries by calling the `Amplify.configure()` method. + +If you are not using the Amplify CLI, a Cognito User Pool and Identity Pool can be used by referencing them in your `amplifyconfiguration.dart` file: + +```dart +const amplifyconfig = ''' { + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "auth": { + "plugins": { + "awsCognitoAuthPlugin": { + "IdentityManager": { + "Default": {} + }, + "CredentialsProvider": { + "CognitoIdentity": { + "Default": { + "PoolId": "[COGNITO IDENTITY POOL ID]", + "Region": "[REGION]" + } + } + }, + "CognitoUserPool": { + "Default": { + "PoolId": "[COGNITO USER POOL ID]", + "AppClientId": "[COGNITO USER POOL APP CLIENT ID]", + "Region": "[REGION]" + } + }, + "Auth": { + "Default": { + "authenticationFlowType": "USER_SRP_AUTH", + "OAuth": { + "WebDomain": "[YOUR COGNITO DOMAIN ]", + "AppClientId": "[COGNITO USER POOL APP CLIENT ID]", + "SignInRedirectURI": "[CUSTOM REDIRECT SCHEME AFTER SIGN IN, e.g. myapp://]", + "SignOutRedirectURI": "[CUSTOM REDIRECT SCHEME AFTER SIGN OUT, e.g. myapp://]", + "Scopes": [ + "phone", + "email", + "openid", + "profile", + "aws.cognito.signin.user.admin" + ] + } + } + } + } + } + } +}'''; +``` + +- **CredentialsProvider**: + - **Cognito Identity**: + - **Default**: + - **PoolID**: ID of the Amazon Cognito Identity Pool (e.g. `us-east-1:123e4567-e89b-12d3-a456-426614174000`) + - **Region**: AWS Region where the resources are provisioned (e.g. `us-east-1`) +- **CognitoUserPool**: + - **Default**: + - **PoolId**: ID of the Amazon Cognito User Pool (e.g. `us-east-1_abcdefghi`) + - **AppClientId**: ID for the client used to authenticate against the user pool + - **Region**: AWS Region where the resources are provisioned (e.g. `us-east-1`) +- **Auth**: + - **Default**: + - **authenticationFlowType**: The authentication flow type, takes values `USER_SRP_AUTH`, `CUSTOM_AUTH`, and `USER_PASSWORD_AUTH`. Default is `USER_SRP_AUTH`. + - **OAuth**: Hosted UI Configuration (only include this if using the Hosted UI flow) + - **Scopes:** Scopes should match the scopes enables in Cognito under "App client settings" + +If you are using a Cognito User Pool without a Cognito Identity Pool, you can omit the **CredentialsProvider** section in the configuration. + + + + diff --git a/src/fragments/lib-v1/auth/flutter/hub_events/10_listen_events.mdx b/src/fragments/lib-v1/auth/flutter/hub_events/10_listen_events.mdx index 905211e0aa4..8362777570c 100644 --- a/src/fragments/lib-v1/auth/flutter/hub_events/10_listen_events.mdx +++ b/src/fragments/lib-v1/auth/flutter/hub_events/10_listen_events.mdx @@ -1,20 +1,17 @@ ```dart -// Do not forget to import the following for StreamSubscription -import 'dart:async'; - -StreamSubscription hubSubscription = Amplify.Hub.listen([HubChannel.Auth], (hubEvent) { - switch(hubEvent.eventName) { - case 'SIGNED_IN': - print('USER IS SIGNED IN'); +final subscription = Amplify.Hub.listen(HubChannel.Auth, (AuthHubEvent event) { + switch (event.type) { + case AuthHubEventType.signedIn: + safePrint('User is signed in.'); break; - case 'SIGNED_OUT': - print('USER IS SIGNED OUT'); + case AuthHubEventType.signedOut: + safePrint('User is signed out.'); break; - case 'SESSION_EXPIRED': - print('SESSION HAS EXPIRED'); + case AuthHubEventType.sessionExpired: + safePrint('The session has expired.'); break; - case 'USER_DELETED': - print('USER HAS BEEN DELETED'); + case AuthHubEventType.userDeleted: + safePrint('The user has been deleted.'); break; } }); diff --git a/src/fragments/lib-v1/auth/flutter/managing_credentials/10_managing_credentials.mdx b/src/fragments/lib-v1/auth/flutter/managing_credentials/10_managing_credentials.mdx index c9cc9af28bf..10a019e5ebc 100644 --- a/src/fragments/lib-v1/auth/flutter/managing_credentials/10_managing_credentials.mdx +++ b/src/fragments/lib-v1/auth/flutter/managing_credentials/10_managing_credentials.mdx @@ -1 +1,59 @@ -Amplify Flutter uses the underlying storage mechanisms used by amplify-android (EncryptedSharedPreferences) and amplify-ios (Keychain), and does not offer customization. +The Amplify Auth category persists authentication-related information to make it available to other Amplify categories and to your application. + +Amplify Flutter securely manages credentials and user identity information. You do not need to store, refresh, or delete credentials yourself. Amplify Flutter stores auth data on the device using platform capabilities such as [Keychain Services](https://developer.apple.com/documentation/security/keychain_services/) on iOS and macOS and [EncryptedSharedPreferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences) on Android. + + + +Amplify will refresh the [Access Token](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html) and [ID Token](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-id-token.html) as long as the [Refresh Token](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-refresh-token.html) is valid. Once the Refresh token expires, the user will need to reauthenticate to obtain a new one. + + + +Some platform specific option can be customized with the out of the box options. In the example below, credentials will be stored in-memory on Web instead of the default behavior of using browser storage. + +```dart +await Amplify.addPlugin( + AmplifyAuthCognito( + secureStorageFactory: AmplifySecureStorage.factoryFrom( + webOptions: WebSecureStorageOptions( + persistenceOption: WebPersistenceOption.inMemory, + ), + ), + ), +); +``` + +If you would like further customization, you can provide your own factory for creating `SecureStorageInterface` instances to `AmplifyAuthCognito`. The example below shows the use of a custom implementation that stores data in-memory on all platforms. + +```dart +await Amplify.addPlugin( + AmplifyAuthCognito(secureStorageFactory: InMemoryStorage.new), +); +``` + +```dart +class InMemoryStorage implements SecureStorageInterface { + InMemoryStorage(this.scope); + + /// The scope of the item being stored. + /// + /// This can be used as a namespace for stored items. + final AmplifySecureStorageScope scope; + + static final Map _data = {}; + + @override + void write({required String key, required String value}) { + _data['${scope.name}.$key'] = value; + } + + @override + String? read({required String key}) { + return _data['${scope.name}.$key']; + } + + @override + void delete({required String key}) { + _data.remove('${scope.name}.$key'); + } +} +``` diff --git a/src/fragments/lib-v1/auth/flutter/password_management/10_reset_password.mdx b/src/fragments/lib-v1/auth/flutter/password_management/10_reset_password.mdx index a08f59ec500..79d9cff5ef1 100644 --- a/src/fragments/lib-v1/auth/flutter/password_management/10_reset_password.mdx +++ b/src/fragments/lib-v1/auth/flutter/password_management/10_reset_password.mdx @@ -1,19 +1,26 @@ ```dart -// Create this value on the class level to use as a state -bool isPasswordReset = false; - -... - -Future resetPassword() async { +Future resetPassword(String username) async { try { final result = await Amplify.Auth.resetPassword( - username: 'myusername', + username: username, ); - setState(() { - isPasswordReset = result.isPasswordReset; - }); - } on AmplifyException catch (e) { - safePrint(e); + await _handleResetPasswordResult(result); + } on AuthException catch (e) { + safePrint('Error resetting password: ${e.message}'); } } ``` + +```dart +Future _handleResetPasswordResult(ResetPasswordResult result) async { + switch (result.nextStep.updateStep) { + case AuthResetPasswordStep.confirmResetPasswordWithCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + break; + case AuthResetPasswordStep.done: + safePrint('Successfully reset password'); + break; + } +} +``` \ No newline at end of file diff --git a/src/fragments/lib-v1/auth/flutter/password_management/20_confirm_reset_password.mdx b/src/fragments/lib-v1/auth/flutter/password_management/20_confirm_reset_password.mdx index 2beed0fbf3e..c3731cd3831 100644 --- a/src/fragments/lib-v1/auth/flutter/password_management/20_confirm_reset_password.mdx +++ b/src/fragments/lib-v1/auth/flutter/password_management/20_confirm_reset_password.mdx @@ -1,13 +1,18 @@ ```dart -Future confirmResetPassword() async { +Future confirmResetPassword({ + required String username, + required String newPassword, + required String confirmationCode, +}) async { try { - await Amplify.Auth.confirmResetPassword( - username: 'myusername', - newPassword: 'mynewpassword', - confirmationCode: '123456' + final result = await Amplify.Auth.confirmResetPassword( + username: username, + newPassword: newPassword, + confirmationCode: confirmationCode, ); - } on AmplifyException catch (e) { - print(e); + safePrint('Password reset complete: ${result.isPasswordReset}'); + } on AuthException catch (e) { + safePrint('Error resetting password: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/password_management/30_change_password.mdx b/src/fragments/lib-v1/auth/flutter/password_management/30_change_password.mdx index 0aae7f1d7ba..8546d7ad6e1 100644 --- a/src/fragments/lib-v1/auth/flutter/password_management/30_change_password.mdx +++ b/src/fragments/lib-v1/auth/flutter/password_management/30_change_password.mdx @@ -1,12 +1,15 @@ ```dart -Future updatePassword() async { +Future updatePassword({ + required String oldPassword, + required String newPassword, +}) async { try { await Amplify.Auth.updatePassword( - newPassword: 'mynewpassword', - oldPassword: 'myoldpassword' + oldPassword: oldPassword, + newPassword: newPassword, ); - } on AmplifyException catch (e) { - print(e); + } on AuthException catch (e) { + safePrint('Error updating password: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/signin/10_signUp.mdx b/src/fragments/lib-v1/auth/flutter/signin/10_signUp.mdx index ef09c6ca96f..da7834a291e 100644 --- a/src/fragments/lib-v1/auth/flutter/signin/10_signUp.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin/10_signUp.mdx @@ -1,27 +1,49 @@ ```dart -// Create a boolean for checking the sign up status -bool isSignUpComplete = false; - -... - -Future signUpUser() async { +/// Signs a user up with a username, password, and email. The required +/// attributes may be different depending on your app's configuration. +Future signUpUser({ + required String username, + required String password, + required String email, + String? phoneNumber, +}) async { try { - final userAttributes = { - CognitoUserAttributeKey.email: 'email@domain.com', - CognitoUserAttributeKey.phoneNumber: '+15559101234', + final userAttributes = { + AuthUserAttributeKey.email: email, + if (phoneNumber != null) AuthUserAttributeKey.phoneNumber: phoneNumber, // additional attributes as needed }; final result = await Amplify.Auth.signUp( - username: 'myusername', - password: 'mysupersecurepassword', - options: CognitoSignUpOptions(userAttributes: userAttributes), + username: username, + password: password, + options: SignUpOptions( + userAttributes: userAttributes, + ), ); - setState(() { - isSignUpComplete = result.isSignUpComplete; - }); + await _handleSignUpResult(result); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error signing up user: ${e.message}'); } } ``` +```dart +Future _handleSignUpResult(SignUpResult result) async { + switch (result.nextStep.signUpStep) { + case AuthSignUpStep.confirmSignUp: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + break; + case AuthSignUpStep.done: + safePrint('Sign up is complete'); + break; + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` \ No newline at end of file diff --git a/src/fragments/lib-v1/auth/flutter/signin/20_confirmSignUp.mdx b/src/fragments/lib-v1/auth/flutter/signin/20_confirmSignUp.mdx index 573a72529a8..6e4d4f2d37b 100644 --- a/src/fragments/lib-v1/auth/flutter/signin/20_confirmSignUp.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin/20_confirmSignUp.mdx @@ -1,23 +1,18 @@ ```dart -// Use the boolean created before -bool isSignUpComplete = false; - -... - -Future confirmUser() async { +Future confirmUser({ + required String username, + required String confirmationCode, +}) async { try { final result = await Amplify.Auth.confirmSignUp( - username: 'myusername', - confirmationCode: '123456' + username: username, + confirmationCode: confirmationCode, ); - - setState(() { - isSignUpComplete = result.isSignUpComplete; - }); - + // Check if further confirmations are needed or if + // the sign up is complete. + await _handleSignUpResult(result); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error confirming user: ${e.message}'); } } ``` - diff --git a/src/fragments/lib-v1/auth/flutter/signin/30_signIn.mdx b/src/fragments/lib-v1/auth/flutter/signin/30_signIn.mdx index a8a1a9c6201..df1312d3e9a 100644 --- a/src/fragments/lib-v1/auth/flutter/signin/30_signIn.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin/30_signIn.mdx @@ -1,25 +1,65 @@ -```dart -// Create a boolean for checking the sign in status -bool isSignedIn = false; + + +Please note that you will be prevented from successfully calling `signIn` if a user has already signed in and a valid session is active. You must first call `signOut` to remove the original session. + + +```dart Future signInUser(String username, String password) async { try { final result = await Amplify.Auth.signIn( username: username, password: password, ); - - setState(() { - isSignedIn = result.isSignedIn; - }); - + await _handleSignInResult(result); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error signing in: ${e.message}'); } } ``` - -Please note that you will be prevented from successfully calling `signIn` if a user has already signed in and a valid session is active. You must first call `signOut` to remove the original session. When running on the iOS platform, you will be able to call `signIn` if the session has expired, while on Android you must first call `signOut` regardless. - +Depending on your configuration and how the user signed up, one or more confirmations will be necessary. +Use the `SignInResult` returned from `Amplify.Auth.signIn` to check the next step for signing in. When +the value is `done`, the user has successfully signed in. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + case AuthSignInStep.confirmSignInWithSmsMfaCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + break; + case AuthSignInStep.confirmSignInWithNewPassword: + safePrint('Enter a new password to continue signing in'); + break; + case AuthSignInStep.confirmSignInWithCustomChallenge: + final parameters = result.nextStep.additionalInfo; + final prompt = parameters['prompt']!; + safePrint(prompt); + break; + case AuthSignInStep.resetPassword: + final resetResult = await Amplify.Auth.resetPassword( + username: username, + ); + await _handleResetPasswordResult(resetResult); + break; + case AuthSignInStep.confirmSignUp: + // Resend the sign up code to the registered device. + final resendResult = await Amplify.Auth.resendSignUpCode( + username: username, + ); + _handleCodeDelivery(resendResult.codeDeliveryDetails); + break; + case AuthSignInStep.done: + safePrint('Sign in is complete'); + break; + } +} +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` diff --git a/src/fragments/lib-v1/auth/flutter/signin/40_multi_factor_signup.mdx b/src/fragments/lib-v1/auth/flutter/signin/40_multi_factor_signup.mdx index 5b440c2848f..6d6c1075c1c 100644 --- a/src/fragments/lib-v1/auth/flutter/signin/40_multi_factor_signup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin/40_multi_factor_signup.mdx @@ -1,29 +1,19 @@ ```dart -// Create a boolean for checking the sign up status -bool isSignUpComplete = false; - -... - Future setUpMFASignUp() async { try { - final userAttributes = { - CognitoUserAttributeKey.email: 'email@domain.com', + final userAttributes = { + AuthUserAttributeKey.email: 'email@domain.com', // Note: phone_number requires country code - CognitoUserAttributeKey.phoneNumber: '+15559101234', + AuthUserAttributeKey.phoneNumber: '+15559101234', }; final result = await Amplify.Auth.signUp( - username: 'myusername', - password: 'mysupersecurepassword', - options: CognitoSignUpOptions( - userAttributes: userAttributes - ) + username: 'myusername', + password: 'mysupersecurepassword', + options: SignUpOptions(userAttributes: userAttributes), ); - setState(() { - isSignUpComplete = result.isSignUpComplete; - }); + await _handleSignUpResult(result); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error signing up: ${e.message}'); } } ``` - diff --git a/src/fragments/lib-v1/auth/flutter/signin/50_multi_factor_confirm_signin.mdx b/src/fragments/lib-v1/auth/flutter/signin/50_multi_factor_confirm_signin.mdx index 52b0ff5c0bc..5758a46842f 100644 --- a/src/fragments/lib-v1/auth/flutter/signin/50_multi_factor_confirm_signin.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin/50_multi_factor_confirm_signin.mdx @@ -1,19 +1,12 @@ ```dart -// Use the boolean created before -bool isSignUpComplete = false; - -... - Future confirmMFAUser() async { try { final result = await Amplify.Auth.confirmSignIn( confirmationValue: '123456', ); - setState(() { - isSignedIn = result.isSignedIn; - }); + await _handleSignInResult(result); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error confirming sign in: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/signin/60_runtime_auth_flow.mdx b/src/fragments/lib-v1/auth/flutter/signin/60_runtime_auth_flow.mdx index 2ce54c66406..efcfecbfa0b 100644 --- a/src/fragments/lib-v1/auth/flutter/signin/60_runtime_auth_flow.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin/60_runtime_auth_flow.mdx @@ -1,17 +1,22 @@ -### Switching authentication flow at runtime +### Switching authentication flow at runtime -By default, the [`authenticationFlowType`](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html) value specified in your `amplifyconfiguration.dart` will be used when authenticating with Cognito. You can change the default behavior at runtime with `CognitoSignInOptions`: +By default, the [`authenticationFlowType`](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html) value specified in your `amplifyconfiguration.dart` will be used when authenticating with Cognito. You can change the default behavior at runtime with `CognitoSignInPluginOptions`: ```dart -Future signInUser(String username, String password) async { +Future signInCustom(String username, String password) async { try { final result = await Amplify.Auth.signIn( username: username, password: password, - options: CognitoSignInOptions(authFlowType: AuthenticationFlowType.customAuth), + options: const SignInOptions( + pluginOptions: CognitoSignInPluginOptions( + authFlowType: AuthenticationFlowType.customAuthWithSrp, + ), + ), ); + await _handleSignInResult(result); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error signing in: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/10_signin.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/10_signin.mdx new file mode 100644 index 00000000000..c8ad0b53aba --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/10_signin.mdx @@ -0,0 +1,50 @@ + +The `Amplify.Auth.signIn` API returns a `SignInResult` object which indicates whether the sign-in flow is +complete or whether additional steps are required before the user is signed in. + +To see if additional signin steps are required, inspect the sign in result's `nextStep.signInStep` property. +- If the sign-in step is `done`, the flow is complete and the user is signed in. +- If the sign-in step is not `done`, one or more additional steps are required. These are explained in detail below. + + + + +The `signInStep` property is an enum of type `AuthSignInStep`. Depending on its value, your code should take one of the actions mentioned on this page. + + + +```dart +Future signInWithCognito( + String username, + String password, +) async { + final SignInResult result = await Amplify.Auth.signIn( + username: username, + password: password, + ); + return _handleSignInResult(result); +} + +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + case AuthSignInStep.continueSignInWithMfaSelection: + // Handle select from MFA methods case + case AuthSignInStep.continueSignInWithTotpSetup: + // Handle TOTP setup case + case AuthSignInStep.confirmSignInWithTotpMfaCode: + // Handle TOTP MFA case + case AuthSignInStep.confirmSignInWithSmsMfaCode: + // Handle SMS MFA case + case AuthSignInStep.confirmSignInWithNewPassword: + // Handle new password case + case AuthSignInStep.confirmSignInWithCustomChallenge: + // Handle custom challenge case + case AuthSignInStep.resetPassword: + // Handle reset password case + case AuthSignInStep.confirmSignUp: + // Handle confirm sign up case + case AuthSignInStep.done: + safePrint('Sign in is complete'); + } +} +``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/20_confirm_sms_mfa.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/20_confirm_sms_mfa.mdx new file mode 100644 index 00000000000..e0c715be144 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/20_confirm_sms_mfa.mdx @@ -0,0 +1,40 @@ +If the next step is `confirmSignInWithSmsMfaCode`, Amplify Auth has sent the user a random code over SMS and is waiting for the user to verify that code. +To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. + + + +The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of +the SMS recipient, which can be used to prompt the user on where to look for the code. + + + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + case AuthSignInStep.confirmSignInWithSmsMfaCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + // ... + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + +```dart +Future confirmMfaUser(String mfaCode) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: mfaCode, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming MFA code: ${e.message}'); + } +} +``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx new file mode 100644 index 00000000000..72ec86e6801 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx @@ -0,0 +1,39 @@ +If the next step is `confirmSignInWithCustomChallenge`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you configured as part of a [custom sign in flow](/[platform]/build-a-backend/auth/sign-in-custom-flow/). + +For example, your custom challenge Lambda may pass a prompt to the frontend which requires the user to enter a secret code. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.confirmSignInWithCustomChallenge: + final parameters = result.nextStep.additionalInfo; + final hint = parameters['hint']!; + safePrint(hint); // "Enter the secret code" + // ... + } +} +``` + +To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. + +```dart +Future confirmCustomChallenge(String answer) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: answer, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming custom challenge: ${e.message}'); + } +} +``` + + + +Special Handling on confirmSignIn + +If `failAuthentication=true` is returned by the Lambda, Cognito will invalidate the session of the request. This is represented by a `NotAuthorizedException` and requires restarting the sign-in flow by calling `Amplify.Auth.signIn` again. + + diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/40_confirm_new_password.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/40_confirm_new_password.mdx new file mode 100644 index 00000000000..deea8d90bc2 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/40_confirm_new_password.mdx @@ -0,0 +1,27 @@ +If the next step is `confirmSignInWithNewPassword`, Amplify Auth requires the user choose a new password they proceeding with the sign in. + +Prompt the user for a new password and pass it to the `confirmSignIn` API. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.confirmSignInWithNewPassword: + safePrint('Please enter a new password'); + // ... + } +} +``` + +```dart +Future confirmNewPassword(String newPassword) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: newPassword, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming new password: ${e.message}'); + } +} +``` \ No newline at end of file diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx new file mode 100644 index 00000000000..ba09c389051 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx @@ -0,0 +1,36 @@ +If the next step is `resetPassword`, Amplify Auth requires that the user reset their password before proceeding. +Use the `resetPassword` API to guide the user through resetting their password, then call `Amplify.Auth.signIn` +when that's complete to restart the sign-in flow. + +See the [reset password](/[platform]/build-a-backend/auth/manage-passwords/) docs for more information. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.resetPassword: + final resetResult = await Amplify.Auth.resetPassword( + username: username, + ); + await _handleResetPasswordResult(resetResult); + // ... + } +} + +Future _handleResetPasswordResult(ResetPasswordResult result) async { + switch (result.nextStep.updateStep) { + case AuthResetPasswordStep.confirmResetPasswordWithCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + case AuthResetPasswordStep.done: + safePrint('Successfully reset password'); + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx new file mode 100644 index 00000000000..ce940a3c8b6 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx @@ -0,0 +1,53 @@ +If the next step is `resetPassword`, Amplify Auth requires that the user confirm their email or phone number before proceeding. +Use the `resendSignUpCode` API to send a new sign up code to the registered email or phone number, followed by `confirmSignUp` +to complete the sign up. + +See the [confirm sign up](/[platform]/build-a-backend/auth/enable-sign-in/#register-a-user) docs for more information. + + + +The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of +the SMS recipient, which can be used to prompt the user on where to look for the code. + + + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.confirmSignUp: + // Resend the sign up code to the registered device. + final resendResult = await Amplify.Auth.resendSignUpCode( + username: username, + ); + _handleCodeDelivery(resendResult.codeDeliveryDetails); + // ... + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + +```dart +Future confirmSignUp({ + required String username, + required String confirmationCode, +}) async { + try { + await Amplify.Auth.confirmSignUp( + username: username, + confirmationCode: confirmationCode, + ); + } on AuthException catch (e) { + safePrint('Error confirming sign up: ${e.message}'); + } +} +``` + +Once the sign up is confirmed, call `Amplify.Auth.signIn` again to restart the sign-in flow. + diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/70_done.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/70_done.mdx new file mode 100644 index 00000000000..4456ec4a727 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/70_done.mdx @@ -0,0 +1,13 @@ +The sign-in flow is complete when the next step is `done`, which means the user is successfully authenticated. +As a convenience, the `SignInResult` also provides the `isSignedIn` property, which will be true if the next step is `done`. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.done: + // Could also check that `result.isSignedIn` is `true` + safePrint('Sign in is complete'); + } +} +``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_web_ui/10_cli_setup.mdx b/src/fragments/lib-v1/auth/flutter/signin_web_ui/10_cli_setup.mdx index c62883075d7..7611f0f2d31 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_web_ui/10_cli_setup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_web_ui/10_cli_setup.mdx @@ -11,14 +11,26 @@ In terminal, navigate to your project, run `amplify add auth` (or if you've alre `(default)` ? Enter your redirect signin URI: `myapp://` +? Do you want to add another redirect signin URI + `Yes` +? Enter your redirect signin URI: + `http://localhost:3000/` ? Do you want to add another redirect signin URI `No` ? Enter your redirect signout URI: `myapp://` ? Do you want to add another redirect signout URI `No` +? Enter your redirect signout URI: + `http://localhost:3000/` +? Do you want to add another redirect signout URI + `No` ? Select the social providers you want to configure for your user pool: `` ``` -Once finished, run `amplify push` to publish your changes. +Note that when the CLI asks for the redirect URIs that you want to use, you may need to enter multiple: + +* Desktop apps require an http localhost (ex: http://localhost:3000) +* Web requires an http localhost or https URI (ex: http://localhost:3000 or https://www.yourapp.com) +* Mobile requires a custom app URI (ex: myapp://) diff --git a/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx b/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx index e431bfba9fc..8a9b656f5db 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx @@ -4,68 +4,44 @@ Sign-in with web UI will display the sign-in UI inside a webview. After the sign ## Platform Setup +

Web

+ +To use Hosted UI in your Flutter web application locally, you must run the app with the `--web-port=3000` argument (with the value being whichever port you assigned to localhost host when configuring your redirect URIs). +

Android

-Add the following `activity` and `queries` tag to the `AndroidManifest.xml` file in your app's `android/app/src/main` directory, -replacing `myapp` with your redirect URI prefix if necessary. Note that this differs from the amplify-flutter stable release, so you will need to make these changes even if you are transitioning an existing project. +Add the following `queries` element to the `AndroidManifest.xml` file in your app's `android/app/src/main` directory, as well as the following `intent-filter` to the `MainActivity` in the same file. + +Replace `myapp` with your redirect URI scheme as necessary: ```xml - - - + + + ... - - - - - - + android:name=".MainActivity" android:exported="true"> + + + + + + ... ``` -In order to use the `` element cited below, you may need to upgrade the Android gradle plugin version in your `build.gradle` file to one of the versions specified below: +

macOS

-| Your plugin version | Upgrade version | -| :--------------------- | ------------------: | -| 4.1.x + | N/A | -| 4.0.x | 4.0.1 | -| 3.6.x | 3.6.4 | -| 3.5.x | 3.5.4 | -| 3.4.x | 3.4.3 | -| 3.3.x | 3.3.3 | +Open XCode and enable the App Sandbox capability and then select "Incoming Connections (Server)" under "Network". +![Incoming Connections setting selected in the App Sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) -

iOS

+

iOS, Windows and Linux

-Add the following entry to the URL scheme in the `Info.plist` file in your app's `ios/Runner` directory. Replace `myapp` with the "redirect signin URI" you provided to the CLI: - -```xml - - - - - - - - CFBundleURLTypes - - - CFBundleURLSchemes - - myapp - - - - - - -``` +No specific platform configuration is required. diff --git a/src/fragments/lib-v1/auth/flutter/signin_web_ui/30_signin.mdx b/src/fragments/lib-v1/auth/flutter/signin_web_ui/30_signin.mdx index 52656fa5120..eaa6b732ccc 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_web_ui/30_signin.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_web_ui/30_signin.mdx @@ -4,9 +4,9 @@ Sweet! You're now ready to launch sign in with web UI. Future signInWithWebUI() async { try { final result = await Amplify.Auth.signInWithWebUI(); - print('Result: $result'); + safePrint('Sign in result: $result'); } on AuthException catch (e) { - print(e.message); + safePrint('Error signing in: ${e.message}'); } } ``` @@ -16,10 +16,12 @@ You can also specify a provider with the `provider` attribute: ```dart Future signInWithWebUIProvider() async { try { - final result = await Amplify.Auth.signInWithWebUI(provider: AuthProvider.google); - print('Result: $result'); + final result = await Amplify.Auth.signInWithWebUI( + provider: AuthProvider.google, + ); + safePrint('Result: $result'); } on AuthException catch (e) { - print(e.message); + safePrint('Error signing in: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_web_ui/40_private_session.mdx b/src/fragments/lib-v1/auth/flutter/signin_web_ui/40_private_session.mdx index cd58f358b47..a66d24945ee 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_web_ui/40_private_session.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_web_ui/40_private_session.mdx @@ -1,15 +1,17 @@ ### Prefer private session during signIn on iOS. -Amplify Flutter uses the amplify-ios library on the iOS platform to facilitate Web UI sign in and other Auth functionality. See the [amplify-ios Web UI documentation](/gen1/[platform]/prev/build-a-backend/auth/sign-in-with-web-ui/#prefer-private-session-during-signin) for details on how amplify-ios manages the interaction between the application and the Web UI. +Amplify.Auth.signInWithWebUI uses [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) internally for iOS. ASWebAuthenticationSession has a property, [prefersEphemeralWebBrowserSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3237231-prefersephemeralwebbrowsersessio) which can be used to indicate whether the session should ask the browser for a private authentication session. To set this flag to true, set `preferPrivateSession` to true using `CognitoSignInWithWebUIPluginOptions`. -As noted in the amplify-ios documentation, it is possible to use a private session when calling `Auth.signInWithWebUI`. This will bypass the permissions dialog that is displayed to the end user, although it will also prevent reuse of existing sessions from the user's browser. For example, if the user is logged into Google in their browser and try to sign in using Google in your app, they would now need to re-enter their credentials. +This will bypass the permissions dialog that is displayed to the end user during sign in and sign out. However, it will also prevent reuse of existing sessions from the user's browser. For example, if the user is logged into Google in their browser and try to sign in using Google in your app, they would now need to re-enter their credentials. ```dart Future signInWithWebUIAndPrivateSession() async { await Amplify.Auth.signInWithWebUI( - options: const CognitoSignInWithWebUIOptions( - isPreferPrivateSession: preferPrivateSession - ) + options: const SignInWithWebUIOptions( + pluginOptions: CognitoSignInWithWebUIPluginOptions( + isPreferPrivateSession: true, + ), + ), ); } ``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/20_signup.mdx b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/20_signup.mdx index 249e3ffd97c..34216c0abe2 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/20_signup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/20_signup.mdx @@ -1,28 +1,21 @@ Because authentication flows in Cognito can be switched via your configuration, it is still required that users register with a password. ```dart -// Create a boolean for checking the sign up status -bool isSignUpComplete = false; - -... - Future signUpCustomFlow() async { try { - final userAttributes = { - CognitoUserAttributeKey.email: 'email@domain.com', - CognitoUserAttributeKey.phoneNumber: '+15559101234', + final userAttributes = { + AuthUserAttributeKey.email: 'email@domain.com', + AuthUserAttributeKey.phoneNumber: '+15559101234', // additional attributes as needed }; final result = await Amplify.Auth.signUp( username: 'myusername', password: 'mysupersecurepassword', - options: CognitoSignUpOptions(userAttributes: userAttributes), + options: SignUpOptions(userAttributes: userAttributes), ); - setState(() { - isSignUpComplete = result.isSignUpComplete; - }); + safePrint('Sign up result: $result'); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error signing up: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/30_signin.mdx b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/30_signin.mdx index 3c578822af4..fa336a21b6d 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/30_signin.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/30_signin.mdx @@ -1,28 +1,26 @@ ```dart -// Create a boolean for checking the sign in status and keep the status -bool isSignedIn = false; +// Create state variables for the sign in status +var isSignedIn = false; String? challengeHint; -... - Future signInCustomFlow(String username) async { try { final result = await Amplify.Auth.signIn(username: username); setState(() { isSignedIn = result.isSignedIn; // Get the publicChallengeParameters from your Create Auth Challenge Lambda - challengeHint = result.nextStep?.additionalInfo?['hint']; + challengeHint = result.nextStep.additionalInfo['hint']; }); } on AuthException catch (e) { - safePrint(e.message); + safePrint('Error signing in: ${e.message}'); } } ``` - Please note that you will be prevented from successfully calling `signIn` if a - user has already signed in and a valid session is active. You must first call - `signOut` to remove the original session. When running on the iOS platform, - you will be able to call `signIn` if the session has expired, while on Android - you must first call `signOut` regardless. + +Please note that you will be prevented from successfully calling `signIn` if a +user has already signed in and a valid session is active. You must first call +`signOut` to remove the original session. + diff --git a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx index bc53bf7306f..25d5d2e5831 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx @@ -5,11 +5,20 @@ Future confirmSignIn(String generatedNumber) async { /// Enter the random number generated by your Create Auth Challenge trigger confirmationValue: generatedNumber, ); - print('Result: $result'); + safePrint('Sign in result: $result'); } on AuthException catch (e) { - print(e.message); + safePrint('Error signing in: ${e.message}'); } } ``` Once the user provides the correct response, they should be authenticated in your application. + + + +Special Handling on ConfirmSignIn + +During a `confirmSignIn` call, if `failAuthentication: true` is returned by the Lambda, the session of the request gets invalidated by Cognito, and a `NotAuthorizedException` is thrown. To recover, the user must initiate a new sign in by calling `Amplify.Auth.signIn`. + +Exception: `NotAuthorizedException` with a message `Invalid session for the user.` + diff --git a/src/fragments/lib-v1/auth/flutter/signout/10_local_signout.mdx b/src/fragments/lib-v1/auth/flutter/signout/10_local_signout.mdx index 7bddd5b2a70..1a3d7aef1ce 100644 --- a/src/fragments/lib-v1/auth/flutter/signout/10_local_signout.mdx +++ b/src/fragments/lib-v1/auth/flutter/signout/10_local_signout.mdx @@ -1,9 +1,10 @@ ```dart Future signOutCurrentUser() async { - try { - await Amplify.Auth.signOut(); - } on AuthException catch (e) { - print(e.message); + final result = await Amplify.Auth.signOut(); + if (result is CognitoCompleteSignOut) { + safePrint('Sign out completed successfully'); + } else if (result is CognitoFailedSignOut) { + safePrint('Error signing user out: ${result.exception.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/signout/20_global_signout.mdx b/src/fragments/lib-v1/auth/flutter/signout/20_global_signout.mdx index 77357b292bd..477b89dce01 100644 --- a/src/fragments/lib-v1/auth/flutter/signout/20_global_signout.mdx +++ b/src/fragments/lib-v1/auth/flutter/signout/20_global_signout.mdx @@ -1,9 +1,18 @@ ```dart -Future signOutCurrentUserGlobally() async { - try { - await Amplify.Auth.signOut(options: SignOutOptions(globalSignOut: true)); - } on AmplifyException catch (e) { - print(e.message); +Future signOutGlobally() async { + final result = await Amplify.Auth.signOut( + options: const SignOutOptions(globalSignOut: true), + ); + if (result is CognitoCompleteSignOut) { + safePrint('Sign out completed successfully'); + } else if (result is CognitoPartialSignOut) { + final globalSignOutException = result.globalSignOutException!; + final accessToken = globalSignOutException.accessToken; + // Retry the global sign out using the access token, if desired + // ... + safePrint('Error signing user out: ${globalSignOutException.message}'); + } else if (result is CognitoFailedSignOut) { + safePrint('Error signing user out: ${result.exception.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_in.mdx b/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_in.mdx index b5040e7062b..c62d34b2b00 100644 --- a/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_in.mdx +++ b/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_in.mdx @@ -1,7 +1,7 @@ ```dart Future confirmSignInPhoneVerification(String otpCode) async { await Amplify.Auth.confirmSignIn( - confirmationValue: otp, + confirmationValue: otpCode, ); } ``` diff --git a/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_up.mdx b/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_up.mdx index 87eecc30f69..f066c947b04 100644 --- a/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_up.mdx +++ b/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_up.mdx @@ -1,5 +1,8 @@ ```dart -Future confirmSignUpPhoneVerification(String username, String otpCode) async { +Future confirmSignUpPhoneVerification( + String username, + String otpCode, +) async { await Amplify.Auth.confirmSignUp( username: username, confirmationCode: otpCode, diff --git a/src/fragments/lib-v1/auth/flutter/sms/sign_up.mdx b/src/fragments/lib-v1/auth/flutter/sms/sign_up.mdx index 843e8915950..5b308e02076 100644 --- a/src/fragments/lib-v1/auth/flutter/sms/sign_up.mdx +++ b/src/fragments/lib-v1/auth/flutter/sms/sign_up.mdx @@ -6,11 +6,11 @@ Future signUpWithPhoneVerification( await Amplify.Auth.signUp( username: username, password: password, - options: CognitoSignUpOptions( - userAttributes: { + options: SignUpOptions( + userAttributes: { // ... if required - CognitoUserAttributeKey.email: 'test@example.com', - CognitoUserAttributeKey.phoneNumber: '+18885551234', + AuthUserAttributeKey.email: 'test@example.com', + AuthUserAttributeKey.phoneNumber: '+18885551234', }, ), ); diff --git a/src/fragments/lib-v1/auth/flutter/social_signin_web_ui/20_signin.mdx b/src/fragments/lib-v1/auth/flutter/social_signin_web_ui/20_signin.mdx index b09e78b4d07..5e3296a8a4a 100644 --- a/src/fragments/lib-v1/auth/flutter/social_signin_web_ui/20_signin.mdx +++ b/src/fragments/lib-v1/auth/flutter/social_signin_web_ui/20_signin.mdx @@ -1,10 +1,12 @@ ```dart -Future signInWithSocialWebUI() async { +Future socialSignIn() async { try { - final result = await Amplify.Auth.signInWithWebUI(provider: AuthProvider.facebook); - print('Result: $result'); - } on AmplifyException catch (e) { - print(e.message); + final result = await Amplify.Auth.signInWithWebUI( + provider: AuthProvider.google, + ); + safePrint('Sign in result: $result'); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/user_attributes/10_fetch_attributes.mdx b/src/fragments/lib-v1/auth/flutter/user_attributes/10_fetch_attributes.mdx index 57f40dfc1b8..a51c1de93c3 100644 --- a/src/fragments/lib-v1/auth/flutter/user_attributes/10_fetch_attributes.mdx +++ b/src/fragments/lib-v1/auth/flutter/user_attributes/10_fetch_attributes.mdx @@ -3,10 +3,10 @@ Future fetchCurrentUserAttributes() async { try { final result = await Amplify.Auth.fetchUserAttributes(); for (final element in result) { - print('key: ${element.userAttributeKey}; value: ${element.value}'); + safePrint('key: ${element.userAttributeKey}; value: ${element.value}'); } } on AuthException catch (e) { - print(e.message); + safePrint('Error fetching user attributes: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/user_attributes/20_update_user_attribute.mdx b/src/fragments/lib-v1/auth/flutter/user_attributes/20_update_user_attribute.mdx index f490dd150d7..84acdb8dbe4 100644 --- a/src/fragments/lib-v1/auth/flutter/user_attributes/20_update_user_attribute.mdx +++ b/src/fragments/lib-v1/auth/flutter/user_attributes/20_update_user_attribute.mdx @@ -1,51 +1,79 @@ To update a single user attribute, call `updateUserAttribute`: ```dart -Future updateUserAttribute() async { +Future updateUserEmail({ + required String newEmail, +}) async { try { final result = await Amplify.Auth.updateUserAttribute( - userAttributeKey: CognitoUserAttributeKey.email, - value: 'email@email.com', + userAttributeKey: AuthUserAttributeKey.email, + value: newEmail, ); - if (result.nextStep.updateAttributeStep == 'CONFIRM_ATTRIBUTE_WITH_CODE') { - var destination = result.nextStep.codeDeliveryDetails?.destination; - print('Confirmation code sent to $destination'); - } else { - print('Update completed'); - } - } on AmplifyException catch (e) { - print(e.message); + _handleUpdateUserAttributeResult(result); + } on AuthException catch (e) { + safePrint('Error updating user attribute: ${e.message}'); } } ``` +User attribute updates may require additional verification before they're complete. Check the +`UpdateUserAttributeResult` returned from `Amplify.Auth.updateUserAttribute` to see which next +step, if any, is required. When the update is complete, the next step will be `done`. + +```dart +void _handleUpdateUserAttributeResult( + UpdateUserAttributeResult result, +) { + switch (result.nextStep.updateAttributeStep) { + case AuthUpdateAttributeStep.confirmAttributeWithCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + break; + case AuthUpdateAttributeStep.done: + safePrint('Successfully updated attribute'); + break; + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + To update multiple user attributes at a time, call `updateUserAttributes`: ```dart -Future updateMultipleUserAttributes() async { +Future updateUserAttributes() async { const attributes = [ AuthUserAttribute( - userAttributeKey: CognitoUserAttributeKey.email, + userAttributeKey: AuthUserAttributeKey.email, value: 'email@email.com', ), AuthUserAttribute( - userAttributeKey: CognitoUserAttributeKey.familyName, + userAttributeKey: AuthUserAttributeKey.familyName, value: 'MyFamilyName', ), ]; - try { - final result = await Amplify.Auth.updateUserAttributes(attributes: attributes); + final result = await Amplify.Auth.updateUserAttributes( + attributes: attributes, + ); result.forEach((key, value) { - if (value.nextStep.updateAttributeStep == 'CONFIRM_ATTRIBUTE_WITH_CODE') { - final destination = value.nextStep.codeDeliveryDetails?.destination; - print('Confirmation code sent to $destination for $key'); - } else { - print('Update completed for $key'); + switch (value.nextStep.updateAttributeStep) { + case AuthUpdateAttributeStep.confirmAttributeWithCode: + final destination = value.nextStep.codeDeliveryDetails?.destination; + safePrint('Confirmation code sent to $destination for $key'); + break; + case AuthUpdateAttributeStep.done: + safePrint('Update completed for $key'); + break; } }); - } on AmplifyException catch (e) { - print(e.message); + } on AuthException catch (e) { + safePrint('Error updating user attributes: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/user_attributes/30_confirm_attribute.mdx b/src/fragments/lib-v1/auth/flutter/user_attributes/30_confirm_attribute.mdx index e04c052c92d..2427f9300ce 100644 --- a/src/fragments/lib-v1/auth/flutter/user_attributes/30_confirm_attribute.mdx +++ b/src/fragments/lib-v1/auth/flutter/user_attributes/30_confirm_attribute.mdx @@ -2,12 +2,11 @@ Future verifyAttributeUpdate() async { try { await Amplify.Auth.confirmUserAttribute( - userAttributeKey: CognitoUserAttributeKey.email, + userAttributeKey: AuthUserAttributeKey.email, confirmationCode: '390739', ); - print('Attribute verified'); - } on AmplifyException catch (e) { - print(e.message); + } on AuthException catch (e) { + safePrint('Error confirming attribute update: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx b/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx index 6575a74904c..160240595ae 100644 --- a/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx +++ b/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx @@ -2,12 +2,11 @@ Future resendVerificationCode() async { try { final result = await Amplify.Auth.resendUserAttributeConfirmationCode( - userAttributeKey: CognitoUserAttributeKey.email, + userAttributeKey: AuthUserAttributeKey.email, ); - final destination = result.codeDeliveryDetails.destination; - print('Confirmation code set to $destination'); - } on AmplifyException catch (e) { - print(e.message); + _handleCodeDelivery(result.codeDeliveryDetails); + } on AuthException catch (e) { + safePrint('Error resending code: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx b/src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx new file mode 100644 index 00000000000..db31a1933f5 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx @@ -0,0 +1,26 @@ +## Custom attributes +Amplify Flutter supports [standard OIDC user attributes](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) as well as custom attributes. Custom attributes can be instantiated via the custom attribute constructor: + +```dart +Future _signUp({ + required String username, + required String password, + required String email, + required String customValue, +}) async { + final userAttributes = { + AuthUserAttributeKey.email: email, + // Create and pass a custom attribute + const CognitoUserAttributeKey.custom('my-custom-attribute'): customValue + }; + await Amplify.Auth.signUp( + username: username, + password: password, + options: SignUpOptions( + userAttributes: userAttributes, + ), + ); +} +``` + +When working with a Cognito UserPool, you can set up [custom attributes](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-custom-attributes) via the Cognito console or AWS CLI. Although Cognito prepends a "custom:" prefix on the attribute name, there is no need for you to add this in Amplify Flutter's custom attribute constructor. \ No newline at end of file diff --git a/src/pages/[platform]/prev/build-a-backend/auth/admin_actions/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/admin_actions/index.mdx new file mode 100644 index 00000000000..f07d733d007 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/auth/admin_actions/index.mdx @@ -0,0 +1,369 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Set up admin actions', + description: 'Learn how to expose administrative actions for your Cognito User Pool to your end user applications.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/auth/admin-actions/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +Admin Actions allow you to execute queries and operations against users and groups in your Cognito user pool. + +For example, the ability to list all users in a Cognito User Pool may provide useful for the administrative panel of an app if the logged-in user is a member of a specific Group called "Admins". + +> This is an advanced feature that is not recommended without an understanding of the underlying architecture. The associated infrastructure which is created is a base designed for you to customize for your specific business needs. We recommend removing any functionality which your app does not require. + +The Amplify CLI can setup a REST endpoint with secure access to a Lambda function running with limited permissions to the User Pool if you wish to have these capabilities in your application, and you can choose to expose the actions to all users with a valid account or restrict to a specific User Pool Group. + +## Enable Admin Queries + + + + +```bash +amplify add auth +``` + +Select the option to go through Manual configuration. + +```console + Do you want to use the default authentication and security configuration? (Use arrow keys) + Default configuration + Default configuration with Social Provider (Federation) +❯ Manual configuration + I want to learn more. +``` + +Go through the rest of the configuration steps until you reach the following prompts: + +```console +? Do you want to add User Pool Groups? Yes +? Provide a name for your user pool group: Admins +? Do you want to add another User Pool Group No +✔ Sort the user pool groups in order of preference · Admins +? Do you want to add an admin queries API? Yes +? Do you want to restrict access to the admin queries API to a specific Group? Yes +? Select the group to restrict access with: (Use arrow keys) +❯ Admins + Enter a custom group +``` + +Continue with the rest of the prompts to finish the configuration. + + + + + +```bash +amplify update auth +``` + +Select the option to Create or update Admin queries API. + +```console +What do you want to do? Create or update Admin queries API +? Do you want to restrict access to the admin queries API to a specific Group Yes +? Select the group to restrict access with: (Use arrow keys) +❯ Admins + Enter a custom group +``` + + + + + + + +If you don't have any User Pool Groups, you will need to select `Enter a custom group`. + + + +When ready, run `amplify push` to deploy the changes. + +This will configure an API Gateway endpoint with a Cognito Authorizer that accepts an Access Token, which is used by a Lambda function to perform actions against the User Pool. The function is example code which you can use to remove, add, or alter functionality based on your business case by editing it in the `amplify/backend/function/AdminQueriesXXX/src` directory and running an `amplify push` to deploy your changes. If you choose to restrict actions to a specific Group, custom middleware in the function will prevent any actions unless the user is a member of that Group. + +## Admin Queries API + +The default routes and their functions, HTTP methods, and expected parameters are below + +- `addUserToGroup`: Adds a user to a specific Group. Expects `username` and `groupname` in the POST body. +- `removeUserFromGroup`: Removes a user from a specific Group. Expects `username` and `groupname` in the POST body. +- `confirmUserSignUp`: Confirms a users signup. Expects `username` in the POST body. +- `disableUser`: Disables a user. Expects `username` in the POST body. +- `enableUser`: Enables a user. Expects `username` in the POST body. +- `getUser`: Gets specific user details. Expects `username` as a GET query string. +- `listUsers`: Lists all users in the current Cognito User Pool. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. +- `listGroups`: Lists all groups in the current Cognito User Pool. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. +- `listGroupsForUser`: Lists groups to which current user belongs to. Expects `username` as a GET query string. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. +- `listUsersInGroup`: Lists users that belong to a specific group. Expects `groupname` as a GET query string. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. +- `signUserOut`: Signs a user out from User Pools, but only if the call is originating from that user. Expects `username` in the POST body. + +## Example + +To leverage this functionality in your app you would call the appropriate route from `Amplify.API` after signing in. The following example adds the user "richard" to the Editors Group and then list all members of the Editors Group with a pagination limit of 10: + + + + + +```js +import React from 'react' +import { Amplify } from 'aws-amplify'; +import { fetchAuthSession } from 'aws-amplify/auth'; +import { post } from 'aws-amplify/api' +import { withAuthenticator } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; + +import amplifyconfig from './amplifyconfiguration.json'; +Amplify.configure(amplifyconfig); + +const client = generateClient() + +async function addToGroup() { + let apiName = 'AdminQueries'; + let path = '/addUserToGroup'; + let options = { + body: { + "username" : "richard", + "groupname": "Editors" + }, + headers: { + 'Content-Type' : 'application/json', + Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` + } + } + return post({apiName, path, options}); +} + +async function listEditors(limit){ + let apiName = 'AdminQueries'; + let path = '/listUsersInGroup'; + let options = { + queryStringParameters: { + "groupname": "Editors", + "limit": limit, + }, + headers: { + 'Content-Type' : 'application/json', + Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` + } + } + const response = await get({apiName, path, options}); + return response; +} + +function App() { + return ( +
+ + +
+ ); +} + +export default withAuthenticator(App); +``` + +
+ + + +1. Initialize Amplify API. Refer to [Set up Amplify REST API](/[platform]/build-a-backend/restapi/set-up-rest-api/) for more details. + +You should have the initialization code including the imports: + +```swift +import Amplify +import AWSCognitoAuthPlugin +import AWSAPIPlugin +``` + +and code that adds `AWSCognitoAuthPlugin` and `AWSAPIPlugin` before configuring Amplify. + +```swift +try Amplify.add(plugin: AWSCognitoAuthPlugin()) +try Amplify.add(plugin: AWSAPIPlugin()) +try Amplify.configure() +``` + +2. Sign in using `Amplify.Auth`. See [Amplify.Auth](/[platform]/build-a-backend/auth/set-up-auth/) to learn more about signing up and signing in a user. + +3. Use the following in your app to add a user to the Group. + +```swift +func addToGroup(username: String, groupName: String) async { + let path = "/addUserToGroup" + let body = "{\"username\":\"\(username)\",\"groupname\":\"\(groupName)\"}".data(using: .utf8) + let request = RESTRequest(path: path, body: body) + do { + let data = try await Amplify.API.post(request: request) + print("Response Body: \(String(decoding: data, as: UTF8.self))") + } catch { + if case let .httpStatusError(statusCode, response) = error as? APIError, + let awsResponse = response as? AWSHTTPURLResponse, + let responseBody = awsResponse.body { + print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") + } + } +} + +await addToGroup(username: "richard", groupName: "Editors") +``` + +4. Use the following to list the users in the Group. + +```swift +func listEditors(groupName: String, limit: Int, nextToken: String? = nil) async { + let path = "/listUsersInGroup" + var query = [ + "groupname": groupName, + "limit": String(limit) + ] + if let nextToken = nextToken { + query["token"] = nextToken + } + + let request = RESTRequest(path: path, queryParameters: query, body: nil) + do { + let data = try await Amplify.API.get(request: request) + print("Response Body: \(String(decoding: data, as: UTF8.self))") + } catch { + if case let .httpStatusError(statusCode, response) = error as? APIError, + let awsResponse = response as? AWSHTTPURLResponse, + let responseBody = awsResponse.body { + print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") + } + } +} + +await listEditors(groupName: "Editors", limit: 10) +``` + +**Note: Cognito User Pool with HostedUI** + +The Admin Queries API configuration in **amplifyconfiguration.json** will have the endpoint's authorization type set to `AMAZON_COGNITO_USER_POOLS`. With this authorization type, `Amplify.API` will perform the request with the access token. However, when using HostedUI, the app may get unauthorized responses despite being signed in, and will require using the ID Token. Set the authorizationType to "NONE" and add a custom interceptor to return the ID Token. + +```json +{ + "awsAPIPlugin": { + "[YOUR-RESTENDPOINT-NAME]": { + "endpointType": "REST", + "endpoint": "[YOUR-REST-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "NONE" + } + } +} +``` + + +If you perform additional updates to your resources using Amplify CLI, the authorizationType will be reverted back to `AMAZON_COGNITO_USER_POOLS`. Make sure to update this back to `NONE`. + + + +Add a custom interceptor to the API +```swift +try Amplify.configure() +try Amplify.API.add(interceptor: MyCustomInterceptor(), for: "[YOUR-RESTENDPOINT-NAME]") +``` + +Set up the custom interceptor to return the ID token for the request. + +```swift +import Amplify +import AWSPluginsCore + +class MyCustomInterceptor: URLRequestInterceptor { + func latestAuthToken() async throws -> String { + guard let session = try await Amplify.Auth.fetchAuthSession() as? AuthCognitoTokensProvider else { + throw AuthError.unknown("Could not retrieve Cognito token") + } + + let tokens = try session.getCognitoTokens().get() + return tokens.idToken + } + + func intercept(_ request: URLRequest) async throws -> URLRequest { + var request = request + do { + let token = try await latestAuthToken() + request.setValue(token, forHTTPHeaderField: "authorization") + } catch { + throw APIError.operationError("Failed to retrieve Cognito UserPool token.", "", error) + } + return request + } +} +``` + +
+ +## Adding Admin Actions + +To add additional admin actions that are not included by default but are enabled by Amazon Cognito, you will need to update the Lambda function code that is generated for you. The change will include adding a route handler for the action and creating a route for it. You will then associate the route handler to the route within the [Express](https://expressjs.com/) app. + +Below is an example of how to add an admin action that will allow you to [update a user's attributes](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminUpdateUserAttributes.html). +```js +async function updateUserAttributes(username, attributes) { + + const params = { + Username: username, + UserAttributes: attributes, + UserPoolId: 'STRING_VALUE', + }; + + console.log(`Attempting to update ${username} attributes`); + + try { + await cognitoIdentityServiceProvider.adminUpdateUserAttributes(params).promise(); + console.log(`Success updating ${username} attributes`); + return { + message: `Success updating ${username} attributes`, + }; + } catch (err) { + console.log(err); + throw err; + } +} +``` +Once the route handler is defined, you will then add a route with the correct HTTP method to the Express app and associate the route handler to the route. Be sure to make the route unique. + +Below is an example of how you can add a `POST` route named `/updateUserAttributes` and associate the above route handler to it. +```js +app.post('/updateUserAttributes', async (req, res, next) => { + if (!req.body.username || !req.body.attributes) { + const err = new Error('username and attributes are required'); + err.statusCode = 400; + return next(err); + } + + try { + const response = await updateUserAttributes(req.body.username, req.body.attributes); + res.status(200).json(response); + } catch (err) { + next(err); + } +}); +``` diff --git a/src/pages/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx new file mode 100644 index 00000000000..c178748008e --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx @@ -0,0 +1,33 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Use existing resources without the CLI', + description: + 'Configure the Amplify Libraries to use existing Amazon Cognito resources by referencing them in your configuration.', + platforms: ['flutter', 'swift', 'android'] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import android0 from '/src/fragments/lib/auth/existing-resources.mdx'; + + + +import ios1 from '/src/fragments/lib/auth/existing-resources.mdx'; + + + +import flutter2 from '/src/fragments/lib/auth/existing-resources.mdx'; + + diff --git a/src/pages/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx new file mode 100644 index 00000000000..ecf4d878240 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx @@ -0,0 +1,111 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Use an existing Cognito User Pool and Identity Pool', + description: 'Configure the Amplify CLI to use existing Amazon Cognito User Pool and Identity Pool resources as an authentication and authorization mechanism for other Amplify categories (API, Storage, and more).', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/auth/import-existing-resources/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +Import existing Amazon Cognito resources into your Amplify project. Get started by running `amplify import auth` command to search for & import an existing Cognito User Pool & Identity Pool in your account. + +```bash +amplify import auth +``` + +The `amplify import auth` command will: + +- automatically populate your Amplify Library configuration files (aws-exports.js, amplifyconfiguration.json) with your chosen Amazon Cognito resource information +- provide your designated existing Cognito resource as the authentication & authorization mechanism for all auth-dependent categories (API, Storage and more) +- enable Lambda functions to access the chosen Cognito resource if you permit it + +Make sure to run `amplify push` to complete the import process and deploy this backend change to the cloud. + +This feature is particularly useful if you're trying to: + +- enable Amplify categories (such as API, Storage, and function) for your existing user base; +- incrementally adopt Amplify for your application stack; +- independently manage Cognito resources while working with Amplify. + +> Note: Amplify does not manage the lifecycle of an imported resource. + +## Import an existing Cognito User Pool + +Select the "Cognito User Pool only" option when you've run `amplify import auth`. In order to successfully import your User Pool, your User Pools require at least one app client with the following conditions: + +- *A "Web app client"*: an app client **without** a client secret + +Run `amplify push` to complete the import procedure. + +import attributesCallout from "/src/fragments/common/writable-vs-mutable-attributes.mdx"; + + + + + +Ensure that the hosted UI for an app client has a sign-out URL defined as omitting this may cause the Amplify CLI to not generate the OAuth `scopes`, `redirectSignIn`, `redirectSignOut` and `responseType` in the `aws-exports.js` file. + +If the Cognito user pool has native and web client defined ensure the clients have matching OAuth properties. + + + +## Import an existing Identity Pool + +Select the "Cognito User Pool and Identity Pool" option when you've run `amplify import auth`. In order to successfully import your Identity Pool, it must have both of the User Pool app clients fulfilling [these requirements](#import-an-existing-cognito-user-pool) associated as an authentication provider. + +Your Identity Pool needs: + +- an Authenticated Role with a trust relationship to your Identity Pool +- an Unauthenticated Role with a trust relationship to your Identity Pool + +These roles are usually automatically configured when you create a new Identity Pool enabling "Unauthenticated" access and have a Cognito User Pool as an authentication provider. + +Amplify CLI will update the policies attached to the roles to ensure Amplify categories function correctly. For example, enabling Storage for authenticated & guest users will add private, protected, public, read and upload permissions for the S3 bucket to the unauthenticated & authenticated role. + +Run `amplify push` to complete the import procedure. + +## Multi-environment support + +When you create a new environment through `amplify env add`, Amplify CLI will assume by default that you're managing your app's Cognito resources outside of an Amplify project. You'll be asked to either import a different Cognito resource or maintain the same Cognito resource for your app's auth category. + +If you want to have Amplify manage your auth resources in a new environment, run `amplify remove auth` to unlink the imported Cognito resource and `amplify add auth` to create new Amplify-managed auth resources in the new environment. + +## Unlink an existing Cognito User Pool or Identity Pool + +In order to unlink your existing Cognito resource run `amplify remove auth`. This will only unlink the Cognito resource referenced from the Amplify project. It will not delete the Cognito resource itself. + +Run `amplify push` to complete the unlink procedure. + +## Add Environmental Variables to Amplify Console Build + +In order to successfully build your application with Amplify Console add the following environmental variables to your build environment: + +|Environment Variable|Description| +|-|-| +|AMPLIFY_USERPOOL_ID|The ID for the Amazon Cognito user pool imported for auth| +|AMPLIFY_WEBCLIENT_ID|The ID for the app client to be used by web applications. The app client must be configured with access to the Amazon Cognito user pool specified by the AMPLIFY_USERPOOL_ID environment variable.| +|AMPLIFY_NATIVECLIENT_ID|The ID for the app client to be used by native applications. The app client must be configured with access to the Amazon Cognito user pool specified by the AMPLIFY_USERPOOL_ID environment variable.| +|AMPLIFY_IDENTITYPOOL_ID|The ID for the Amazon Cognito identity pool| diff --git a/src/pages/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx new file mode 100644 index 00000000000..6b6303d0afd --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx @@ -0,0 +1,185 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Override Amplify-generated Cognito resources', + description: "The 'amplify override auth' command generates a developer-configurable 'overrides' TypeScript file that provides Amplify-generated Cognito resources as CDK constructs. For example, developers can set auth settings that are not directly available in the Amplify CLI workflow, such as the number of valid days for a temporary password.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter' + ], + canonicalPath: '/javascript/build-a-backend/auth/override-cognito/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +```bash +amplify override auth +``` + +Run the command above to override Amplify-generated auth resources including Amazon Cognito user pool, identity pool, user pool groups, and more. + +The command creates a new `overrides.ts` file under `amplify/backend/auth//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). + +## Customize Amplify-generated Cognito auth resources + +Apply all the overrides in the `override(...)` function. For example, to update the temporary password validity days for your Cognito user pool: + +```ts +import { AmplifyAuthCognitoStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyAuthCognitoStackTemplate) { + resources.userPool.policies = { // Set the user pool policies + passwordPolicy: { + ...resources.userPool.policies["passwordPolicy"], // Carry over existing settings + temporaryPasswordValidityDays: 3 // Add new setting not provided Amplify's default + } + } +} +``` + +Or add a custom attribute to your Cognito user pool: + + + +Removing or adding an attribute on a Cognito userpool schema including default attributes (e.g. `email`) will cause errors such as +`Invalid AttributeDataType input, consider using the provided AttributeDataType enum` as CloudFormation interprets this as schema change. + + + + + +Custom attributes can not be renamed or deleted after you create them. + + + +```ts +import { AmplifyAuthCognitoStackTemplate } from '@aws-amplify/cli-extensibility-helper' + +export function override(resources: AmplifyAuthCognitoStackTemplate) { + const myCustomAttribute = { + attributeDataType: 'String', + developerOnlyAttribute: false, + mutable: true, + name: 'my_custom_attribute', + required: false, + } + resources.userPool.schema = [ + ...(resources.userPool.schema as any[]), // Carry over existing attributes (example: email) + myCustomAttribute, + ] +} +``` + +You can override the following auth resources that Amplify generates: + +|Amplify-generated resource|Description| +|-|-| +|[customMessageConfirmationBucket](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html)|S3 bucket used for custom message triggers| +|[snsRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|SNS role for sending authentication-related messages| +|[userPool](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html)|The Cognito user pool that enables user sign-up and sign-in| +|[userPoolClientWeb](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)|A Cognito user pool client for web apps| +|[userPoolClient](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)|A Cognito user pool client for mobile apps| +|[identityPool](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypool.html)|A Cognito identity pool to federate identities| +|[identityPoolRoleMap](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypoolroleattachment.html)|Role mapping for authenticated and unauthenticated user roles| +|[lambdaConfigPermissions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html)|Permissions for Lambda function to access Cognito user pool and identity pool | +|[lambdaTriggerPermissions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM policy attached to Cognito Lambda triggers| +|[userPoolClientLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to fetch app client secret from user pool client| +|[userPoolClientRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|IAM Role for Lambda function to fetch app client secret from user pool client| +|[userPoolClientLambdaPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to fetch app client secret from user pool client| +|[userPoolClientLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to fetch app client secret from user pool client| +|[userPoolClientInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to fetch app client secret from user pool client| +|[hostedUICustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable Cognito user pool Hosted UI login| +|[hostedUICustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to enable Cognito user pool Hosted UI login| +|[hostedUICustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to enable Cognito user pool Hosted UI login| +|[hostedUICustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable Cognito user pool Hosted UI login| +|[hostedUIProvidersCustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to configure Hosted UI with 3rd party identity providers| +|[hostedUIProvidersCustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to configure Hosted UI with 3rd party identity provider| +|[hostedUIProvidersCustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to configure Hosted UI with 3rd party identity provider| +|[hostedUIProvidersCustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to configure Hosted UI with 3rd party identity provider| +|[oAuthCustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable OAuth| +|[oAuthCustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for OAuth custom CloudFormation resource| +|[oAuthCustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for OAuth Lambda function| +|[oAuthCustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable OAuth| +|[mfaLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable multi-factor authentication function| +|[mfaLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for multi-factor authentication Lambda function| +|[mfaLambdaPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for multi-factor authentication Lambda function| +|[mfaLambdaInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable multi-factor authentication| +|[mfaLambdaRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|IAM Execution Role for multi-factor authentication Lambda function| +|[openIdLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable OpenID Connect| +|[openIdLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for OpenID Connect Lambda function| +|[openIdLambdaIAMPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable OpenID Connect Lambda function| +|[openIdLambdaInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable OpenID Connect| +|[openIdLambdaRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|Lambda Execution Role for OpenID Connect Lambda function| + +## Customize Amplify-generated Cognito user group resources + +Apply all the overrides in the `override(...)` function. For example to add a path to the lambda execution role that facilitates the user pool group to role mapping: +```ts +import { AmplifyUserPoolGroupStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyUserPoolGroupStackTemplate) { + resources.lambdaExecutionRole.path = "//" // Note: CFN does not allow you to modify the path after creation +} +``` + +You can override the following user pool group resources that Amplify generates: + +|Amplify-generated resource|Description| +|-|-| +|[userPoolGroup](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolgroup.html)|The map of user pool groups| +|[userPoolGroupRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|The map of user pool group roles| +|[roleMapCustomResource](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|A custom CloudFormation resource to map user pool groups to their roles| +|[lambdaExecutionRole](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html)|Lambda execution role for the "user pool group"-to-role mapping function| +|[roleMapLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|The Lambda function that facilitates the user pool group to role mapping| + + +## Customize Amplify-generated Cognito auth resources with social providers + +Apply all the overrides in the `override(...)` function. For example to add social providers to your Cognito user pool: + +```ts +import { AmplifyAuthCognitoStackTemplate } from "@aws-amplify/cli-extensibility-helper"; + +export function override(resources: AmplifyAuthCognitoStackTemplate) { + resources.addCfnResource( + { + type: "AWS::Cognito::UserPoolIdentityProvider", + properties: { + AttributeMapping: { + preferred_username: "email", + email: "email" + }, + ProviderDetails: { + client_id: "test", + client_secret: "test", + authorize_scopes: "test", + }, + ProviderName: "LoginWithAmazon", + ProviderType: "LoginWithAmazon", + UserPoolId: { + Ref: "UserPool", + }, + }, + }, + "amazon-social-provider" + ); +} +``` diff --git a/src/pages/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx new file mode 100644 index 00000000000..a8cfbbb90d4 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx @@ -0,0 +1,117 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Set up user group management', + description: 'Create logical groups in Cognito User Pools and assign permissions to access resources in Amplify categories with the Amplify CLI.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/auth/user-group-management/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +You can create logical groups in Cognito User Pools and assign permissions to access resources in Amplify categories with the CLI, as well as define the relative precedence of one group to another. This can be useful for defining which users should be part of "Admins" vs "Editors", and if the users in a Group should be able to just write or write & read to a resource (AppSync, API Gateway, S3 bucket, etc). [You can also use these with `@auth` Static Groups in the GraphQL Transformer](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules#user-group-based-data-access). Precedence helps remove any ambiguity on permissions if a user is in multiple Groups. + +## Create user groups + +```bash +amplify add auth +``` + +```console +❯ Manual configuration + +Do you want to add User Pool Groups? (Use arrow keys) +❯ Yes + +? Provide a name for your user pool group: Admins +? Do you want to add another User Pool Group Yes +? Provide a name for your user pool group: Editors +? Do you want to add another User Pool Group No +? Sort the user pool groups in order of preference … (Use + to change the order) + Admins + Editors +``` + +When asked as in the example above, you can press `Shift` on your keyboard along with the **LEFT** and **RIGHT** arrows to move a Group higher or lower in precedence. Once complete you can open `amplify/backend/auth/userPoolGroups/user-pool-group-precedence.json` to manually set the precedence. + +## Group access controls + +For certain Amplify categories you can restrict access with CRUD (Create, Read, Update, and Delete) permissions, setting different access controls for authenticated users vs Guests (e.g. Authenticated users can read & write to S3 buckets while Guests can only read). You can further restrict this to apply different permissions conditionally depending on if a logged-in user is part of a specific User Pool Group. + +```bash +amplify add storage # Select content +``` + +```console +? Restrict access by? (Use arrow keys) + Auth/Guest Users + Individual Groups +❯ Both + Learn more + +Who should have access? +❯ Auth and guest users + +What kind of access do you want for Authenticated users? +❯ create/update, read + +What kind of access do you want for Guest users? +❯ read + +Select groups: +❯ Admins + +What kind of access do you want for Admins users? +❯ create/update, read, delete +``` + +The above example uses a combination of permissions where users in the "Admins" Group have full access, "Guest" users can only read, and "Authenticated" users who are not a part of any group have create, update, and read access. Amplify will configure the corresponding IAM policy on your behalf. Advanced users can additionally set permissions by adding a `customPolicies` key to `amplify/backend/auth/userPoolGroups/user-pool-group-precedence.json` with custom IAM policy for a Group. This will attach an inline policy on the IAM role associated to this Group during deployment. **Note** this is an advanced feature and only suitable if you have an understanding of AWS resources. For instance perhaps you wanted users in the "Admins" group to have the ability to Create an S3 bucket: + +```json +[ + { + "groupName": "Admins", + "precedence": 1, + "customPolicies": [ + { + "PolicyName": "admin-group-policy", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "statement1", + "Effect": "Allow", + "Action": ["s3:CreateBucket"], + "Resource": ["arn:aws:s3:::*"] + } + ] + } + } + ] + }, + { + "groupName": "Editors", + "precedence": 2 + } +] +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/advanced-workflows/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/advanced-workflows/index.mdx index 350a21f999d..962b8c09604 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/advanced-workflows/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/advanced-workflows/index.mdx @@ -6,6 +6,7 @@ export const meta = { platforms: [ 'javascript', 'react-native', + 'flutter', 'angular', 'nextjs', 'react', @@ -582,3 +583,7 @@ Note: To work with Service Interface Objects, your Amazon Cognito users' [IAM ro + +import flutter from '/src/fragments/lib-v1/auth/flutter/advanced/advanced.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx index b7bf6aa37e0..b0848fa20c4 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx @@ -4,7 +4,7 @@ export const meta = { title: 'Use existing Amazon Cognito resources', description: 'Configure the Amplify Libraries to use existing Amazon Cognito resources by referencing them in your configuration.', - platforms: ['flutter', 'swift', 'android'] + platforms: ['swift', 'android'] }; export const getStaticPaths = async () => { diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/under-the-hood/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/under-the-hood/index.mdx index d5f55550538..70b76a13b90 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/under-the-hood/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/under-the-hood/index.mdx @@ -7,7 +7,6 @@ export const meta = { 'javascript', 'android', 'swift', - 'flutter', 'react-native', 'angular', 'nextjs', From caaca24d43966ff6a40ffd6f5914b206f86babee Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:14:36 -0700 Subject: [PATCH 03/88] chore: API transfer v1 from current to previous --- .../flutter/advanced-workflows/10_example.mdx | 2 +- .../flutter/advanced-workflows/20_custom.mdx | 12 +- .../flutter/advanced-workflows/30_nested.mdx | 18 +- .../advanced-workflows/40_multiple.mdx | 25 +- .../advanced-workflows/50_interceptor.mdx | 36 +- .../lib-v1/graphqlapi/flutter/authz.mdx | 6 +- .../graphqlapi/flutter/authz/10_userpool.mdx | 19 +- .../graphqlapi/flutter/authz/20_oidc.mdx | 2 +- .../graphqlapi/flutter/authz/21_oidc.mdx | 2 +- .../graphqlapi/flutter/authz/22_lambda.mdx | 2 +- .../flutter/authz/2X_add_plugin.mdx | 10 +- .../graphqlapi/flutter/authz/30_multi.mdx | 34 +- .../graphqlapi/flutter/authz/auth_mode.mdx | 1 - .../flutter/getting-started/10_preReq.mdx | 17 +- .../flutter/getting-started/20_installLib.mdx | 6 +- .../flutter/getting-started/30_initapi.mdx | 50 +- .../lib-v1/graphqlapi/flutter/mutate-data.mdx | 15 +- .../lib-v1/graphqlapi/flutter/query-data.mdx | 62 +- .../graphqlapi/flutter/subscribe-data.mdx | 139 +- .../batch-put-custom-resolver/index.mdx | 151 ++ .../graphqlapi/best-practice/index.mdx | 36 + .../query-with-sorting/index.mdx | 132 ++ .../warehouse-management/index.mdx | 552 +++++++ .../client-code-generation/index.mdx | 241 ++++ .../index.mdx | 1014 +++++++++++++ .../index.mdx | 262 ++++ .../custom-business-logic/index.mdx | 954 +++++++++++++ .../customize-authorization-modes/index.mdx | 49 + .../customize-authorization-rules/index.mdx | 933 ++++++++++++ .../graphqlapi/data-modeling/index.mdx | 1262 +++++++++++++++++ .../index.mdx | 500 +++++++ .../graphqlapi/schema-evolution/index.mdx | 141 ++ .../search-and-result-aggregations/index.mdx | 479 +++++++ .../graphqlapi/troubleshooting/index.mdx | 92 ++ .../graphqlapi/advanced-workflows/index.mdx | 2 +- .../customize-authz-modes/index.mdx | 1 - 36 files changed, 7142 insertions(+), 117 deletions(-) delete mode 100644 src/fragments/lib-v1/graphqlapi/flutter/authz/auth_mode.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx diff --git a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/10_example.mdx b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/10_example.mdx index 2b5cc5cefc8..f88e8cc39d4 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/10_example.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/10_example.mdx @@ -3,6 +3,6 @@ Future createAndMutateTodo() async { final todo = Todo(name: 'my first todo', description: 'todo description'); final request = ModelMutations.create(todo); final response = await Amplify.API.mutate(request: request).response; - print('Response: $response'); + safePrint('Response: $response'); } ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/20_custom.mdx b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/20_custom.mdx index bbdf664657e..0eaccb71b9e 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/20_custom.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/20_custom.mdx @@ -1,16 +1,16 @@ ```dart const getTodo = 'getTodo'; -String graphQLDocument = '''query GetTodo(\$id: ID!) { +const graphQLDocument = '''query GetTodo(\$id: ID!) { $getTodo(id: \$id) { id name } }'''; final getTodoRequest = GraphQLRequest( - document: graphQLDocument, - modelType: Todo.classType, - variables: {'id': someTodoId}, - decodePath: getTodo, + document: graphQLDocument, + modelType: Todo.classType, + variables: {'id': someTodoId}, + decodePath: getTodo, ); ``` The `decodePath` specifies which part of the response to deserialize to the `modelType`. You'll need to specify the operation name (as `decodePath`) and `modelType` to deserialize the object at "data.getTodo" successfully into a `Todo` model. @@ -19,6 +19,6 @@ Then, query for the Todo by a todo id: ```dart Future queryTodo(GraphQLRequest getTodoRequest) async { final response = await Amplify.API.query(request: getTodoRequest).response; - print('Response: $response'); + safePrint('Response: $response'); } ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/30_nested.mdx b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/30_nested.mdx index 528bcd8542e..7693e6d9fdc 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/30_nested.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/30_nested.mdx @@ -1,6 +1,6 @@ ```dart const getPost = 'getPost'; -String graphQLDocument = '''query GetPost(\$id: ID!) { +const graphQLDocument = '''query GetPost(\$id: ID!) { $getPost(id: \$id) { id title @@ -16,17 +16,19 @@ String graphQLDocument = '''query GetPost(\$id: ID!) { } }'''; final getPostRequest = GraphQLRequest( - document: graphQLDocument, - modelType: Post.classType, - variables: {'id': somePostId}, - decodePath: getPost, + document: graphQLDocument, + modelType: Post.classType, + variables: {'id': somePostId}, + decodePath: getPost, ); ``` Then, query for the `Post` with nested comments included in decoded response: ```dart -Future queryPostWithNestedComments(GraphQLRequest getPostRequest) async { - final response = await Amplify.API.query(request: getTodoRequest).response; - print('Response $response'); +Future queryPostWithNestedComments( + GraphQLRequest getPostRequest, +) async { + final response = await Amplify.API.query(request: getPostRequest).response; + safePrint('Response $response'); } ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/40_multiple.mdx b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/40_multiple.mdx index 5240683d683..8bd40c4b2bc 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/40_multiple.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/40_multiple.mdx @@ -1,7 +1,7 @@ ```dart const getTodo = 'getTodo'; const getPost = 'getPost'; -String graphQLDocument = ''' +const graphQLDocument = ''' query GetPostAndTodo(\$todoId: ID!, \$postId: ID!) { $getTodo(id: \$todoId) { id @@ -16,11 +16,8 @@ String graphQLDocument = ''' '''; final multiOperationRequest = GraphQLRequest( - document: graphQLDocument, - variables: { - 'todoId': someTodoId, - 'postId': somePostId - }, + document: graphQLDocument, + variables: {'todoId': someTodoId, 'postId': somePostId}, ); ``` Notice here that `modelType` and `decodePath` are omitted. When these decoding variables are omitted, the plugin simply returns the result as a raw `String` from the response. @@ -33,12 +30,18 @@ import 'dart:convert'; ... -Future queryMultiOperationRequest(GraphQLRequest operation) async { - final response = await Amplify.API.query(request: multiOperationRequest).response; +Future queryMultiOperationRequest( + GraphQLRequest operation, +) async { + final response = + await Amplify.API.query(request: multiOperationRequest).response; if (response.data != null) { - final jsonData = (json.decode(response.data) as Map).cast(); - final post = Post.fromJson((jsonData[getPost] as Map).cast); - final todo = Todo.fromJson((jsonData[getTodo] as Map).cast); + final jsonData = + (json.decode(response.data) as Map).cast(); + final post = + Post.fromJson((jsonData[getPost] as Map).cast); + final todo = + Todo.fromJson((jsonData[getTodo] as Map).cast); } } ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx index 60c1cc63943..af23e74869e 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx @@ -1 +1,35 @@ -Custom headers and interceptors are only supported in developer preview. Please follow the open [Github Issue](https://github.com/aws-amplify/amplify-flutter/issues/798). +The simplest option for GraphQL requests is to use the `headers` property of a `GraphQLRequest`. + +```dart +Future queryWithCustomHeaders() async { + final operation = Amplify.API.query( + request: GraphQLRequest( + document: graphQLDocumentString, + headers: {'customHeader': 'someValue'}, + ), + ); + final response = await operation.response; + final data = response.data; + safePrint('data: $data'); +} +``` + +Another option is to use the `baseHttpClient` property of the API plugin which can customize headers or otherwise alter HTTP functionality for all HTTP calls. + +```dart +// First create a custom HTTP client implementation to extend HTTP functionality. +class MyHttpRequestInterceptor extends AWSBaseHttpClient { + @override + Future transformRequest( + AWSBaseHttpRequest request, + ) async { + request.headers.putIfAbsent('customHeader', () => 'someValue'); + return request; + } +} + +// Then you can pass an instance of this client to `baseHttpClient` when you configure Amplify. +await Amplify.addPlugins([ + AmplifyAPI(baseHttpClient: MyHttpRequestInterceptor()), +]); +``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz.mdx index 05ba9072b60..f7e505dc2ba 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz.mdx @@ -38,7 +38,7 @@ and under the `awsAPIPlugin` ``` -import flutter0 from "/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx"; +import flutter0 from "/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx"; @@ -78,7 +78,7 @@ and under the `awsAPIPlugin` #### OIDC -import flutter1 from "/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx"; +import flutter1 from "/src/fragments/lib/graphqlapi/flutter/authz/20_oidc.mdx"; @@ -134,6 +134,6 @@ The `friendly_name` illustrated here is created from Amplify CLI prompt. There a The `GRAPHQL-ENDPOINT` from AWS AppSync will look similar to `https://xyz.appsync-api.us-west-2.amazonaws.com/graphql`. -import flutter2 from "/src/fragments/lib-v1/graphqlapi/flutter/authz/30_multi.mdx"; +import flutter2 from "/src/fragments/lib/graphqlapi/flutter/authz/30_multi.mdx"; diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx index 06a31f8a7d2..87463a8cf3f 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx @@ -2,20 +2,23 @@ In case you have not added the Cognito libraries to your application, be sure to ```yaml environment: - sdk: ">=2.15.0 <3.0.0" + sdk: ">=2.18.0 <4.0.0" - dependencies: - flutter: - sdk: flutter +dependencies: + flutter: + sdk: flutter - amplify_flutter: ^0.6.0 - amplify_api: ^0.6.0 + amplify_flutter: ^1.0.0 + amplify_api: ^1.0.0 # Be sure that this is added - amplify_auth_cognito: ^0.6.0 + amplify_auth_cognito: ^1.0.0 ``` Afterwards add the following code to your app before you configure Amplify: ```dart -await Amplify.addPlugins([AmplifyAuthCognito(), AmplifyAPI(modelProvider: ModelProvider.instance)]); +await Amplify.addPlugins([ + AmplifyAuthCognito(), + AmplifyAPI(modelProvider: ModelProvider.instance), +]); ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx index 27eeea6dd7f..3e8bd488f27 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx @@ -11,6 +11,6 @@ class CustomOIDCProvider extends OIDCAuthProvider { } ``` -import warning from "/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx"; +import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/21_oidc.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/21_oidc.mdx index 9576869702b..03dc4ca4b17 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/21_oidc.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/21_oidc.mdx @@ -15,6 +15,6 @@ class CustomOIDCProvider extends OIDCAuthProvider { } ``` -import warning from "/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx"; +import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/22_lambda.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/22_lambda.mdx index 6e5f0094abd..cedc5024efe 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/22_lambda.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/22_lambda.mdx @@ -9,6 +9,6 @@ class CustomFunctionProvider extends FunctionAuthProvider { } ``` -import warning from "/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx"; +import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx index 7c6f578e3f1..488197283df 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx @@ -1,12 +1,14 @@ Then, include it, along with any other auth providers, in the call to `addPlugin`. ```dart -await Amplify.addPlugin(AmplifyAPI( +await Amplify.addPlugin( + AmplifyAPI( authProviders: const [ - CustomOIDCProvider(), - CustomFunctionProvider(), + CustomOIDCProvider(), + CustomFunctionProvider(), ], -)); + ), +); ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/30_multi.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/30_multi.mdx index 9e22d8128ed..35f7c1ae3e5 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/30_multi.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/30_multi.mdx @@ -1,15 +1,39 @@ -When you have configured multiple APIs, you can specify the name of the API as a parameter as the target for an operation: +When you have multiple authorization modes, you can specify the mode with the `authorizationMode` parameter. You can also specify the API name with the `apiName` parameter. ```dart -Future main() async { +Future mutateWithApiKey() async { final operation = Amplify.API.mutate( request: GraphQLRequest( - document: graphQLDocumentString, - apiName: '[FRIENDLY-NAME-API-WITH-API-KEY]', + document: graphQLDocumentString, + authorizationMode: APIAuthorizationType.apiKey, ), ); final response = await operation.response; final data = response.data; - print('data: $data'); + safePrint('data: $data'); +} + +Future mutateWithIam() async { + final operation = Amplify.API.mutate( + request: GraphQLRequest( + document: graphQLDocumentString, + authorizationMode: APIAuthorizationType.iam, + ), + ); + final response = await operation.response; + final data = response.data; + safePrint('data: $data'); +} + +Future mutateByApiName() async { + final operation = Amplify.API.mutate( + request: GraphQLRequest( + document: graphQLDocumentString, + apiName: '[FRIENDLY-NAME-API-WITH-API-KEY]', + ), + ); + final response = await operation.response; + final data = response.data; + safePrint('data: $data'); } ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/auth_mode.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/auth_mode.mdx deleted file mode 100644 index dae403b57f2..00000000000 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/auth_mode.mdx +++ /dev/null @@ -1 +0,0 @@ -In developer preview, you have the option to use the `authorizationMode` parameter to avoid referencing `friendly_name_` from the `amplifyconfiguration.dart` file (see code snippet below). \ No newline at end of file diff --git a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx index 242473bd189..bced735fceb 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx @@ -1,5 +1,12 @@ -* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) -* A Flutter application targeting Flutter SDK >= 2.10.0 (stable version) with Amplify libraries integrated - * An iOS configuration targeting at least iOS 11.0 - * An Android configuration targeting at least Android API level 21 (Android 5.0) or above - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) +* [Install and configure Amplify CLI](/[platform]/tools/cli/start/set-up-cli/) +* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated + + The following are also required, depending on which platforms you are targeting: + + * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 + * An Android configuration targeting at least Android API level 24 (Android 7.0) or above + * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) + * Any Windows OS meeting Flutter minimums + * macOS version 10.15 or higher + * Any Ubuntu Linux distribution meeting Flutter minimums + * For a full example please follow the [project setup walkthrough](/[platform]/start/project-setup/create-application/) diff --git a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/20_installLib.mdx index 4a4f0fef357..bd8d918655c 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/20_installLib.mdx @@ -2,11 +2,11 @@ Add the following dependencies to your `pubspec.yaml` file and install dependenc ```yaml environment: - sdk: ">=2.15.0 <3.0.0" + sdk: ">=2.18.0 <4.0.0" dependencies: flutter: sdk: flutter - amplify_flutter: ^0.6.0 - amplify_api: ^0.6.0 + amplify_flutter: ^1.0.0 + amplify_api: ^1.0.0 ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/30_initapi.mdx b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/30_initapi.mdx index 4f0ece449de..28361a48898 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/30_initapi.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/30_initapi.mdx @@ -3,35 +3,47 @@ To initialize the Amplify API category you call `Amplify.addPlugin()` method. To Your code should look like this: ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_example_application/models/ModelProvider.dart'; - +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; import 'amplifyconfiguration.dart'; +import 'models/ModelProvider.dart'; class MyApp extends StatefulWidget { + const MyApp({super.key}); + @override - _MyAppState createState() => _MyAppState(); + State createState() => _MyAppState(); } class _MyAppState extends State { - @override - void initState() { - super.initState(); - _configureAmplify(); + @override + void initState() { + super.initState(); + _configureAmplify(); + } + + Future _configureAmplify() async { + final api = AmplifyAPI(modelProvider: ModelProvider.instance); + await Amplify.addPlugin(api); + + try { + await Amplify.configure(amplifyconfig); + } on Exception catch (e) { + safePrint('An error occurred configuring Amplify: $e'); } + } - Future _configureAmplify() async { - final api = AmplifyAPI(modelProvider: ModelProvider.instance); - await Amplify.addPlugin(api); - - try { - await Amplify.configure(amplifyconfig); - } on AmplifyAlreadyConfiguredException { - safePrint( - 'Tried to reconfigure Amplify; this can occur when your app restarts on Android.'); - } - } + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold( + body: Center( + child: Text('Home'), + ), + ), + ); + } } ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx b/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx index 7c3ea90991b..3472dce68f8 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx @@ -2,7 +2,7 @@ Now that the client is set up, you can run a GraphQL mutation with `Amplify.API.mutate` to create, update, and delete your data. -import createTodo from "/src/fragments/lib-v1/graphqlapi/flutter/getting-started/50_createtodo.mdx"; +import createTodo from "/src/fragments/lib/graphqlapi/flutter/getting-started/50_createtodo.mdx"; @@ -14,7 +14,7 @@ Future updateTodo(Todo originalTodo) async { final request = ModelMutations.update(todoWithNewName); final response = await Amplify.API.mutate(request: request).response; - print('Response: $response'); + safePrint('Response: $response'); } ``` @@ -24,15 +24,20 @@ To delete the `Todo`: Future deleteTodo(Todo todoToDelete) async { final request = ModelMutations.delete(todoToDelete); final response = await Amplify.API.mutate(request: request).response; - print('Response: $response'); + safePrint('Response: $response'); } ``` + ```dart // or delete by ID, ideal if you do not have the instance in memory, yet Future deleteTodoById(Todo todoToDelete) async { - final request = ModelMutations.deleteById(Todo.classType, '8e0dd2fc-2f4a-4dc4-b47f-2052eda10775'); + final request = ModelMutations.deleteById( + Todo.classType, + TodoModelIdentifier(id: '8e0dd2fc-2f4a-4dc4-b47f-2052eda10775'), + ); final response = await Amplify.API.mutate(request: request).response; - print('Response: $response'); + safePrint('Response: $response'); } ``` + diff --git a/src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx b/src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx index 8f55c9991e0..cb426723cc1 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx @@ -4,18 +4,21 @@ Now that you were able to make a mutation, take the `id` from the created `Todo` ```dart Future queryItem(Todo queriedTodo) async { - try { - final request = ModelQueries.get(Todo.classType, queriedTodo.id); - final response = await Amplify.API.query(request: request).response; - final todo = response.data; - if (todo == null) { - print('errors: ${response.errors}'); - } - return todo; - } on ApiException catch (e) { - print('Query failed: $e'); - return null; - } + try { + final request = ModelQueries.get( + Todo.classType, + queriedTodo.modelIdentifier, + ); + final response = await Amplify.API.query(request: request).response; + final todo = response.data; + if (todo == null) { + safePrint('errors: ${response.errors}'); + } + return todo; + } on ApiException catch (e) { + safePrint('Query failed: $e'); + return null; + } } ``` @@ -31,14 +34,14 @@ Future> queryListItems() async { final todos = response.data?.items; if (todos == null) { - print('errors: ${response.errors}'); - return []; + safePrint('errors: ${response.errors}'); + return const []; } return todos; } on ApiException catch (e) { - print('Query failed: $e'); + safePrint('Query failed: $e'); + return const []; } - return []; } ``` @@ -57,7 +60,8 @@ Future> queryPaginatedListItems() async { // Indicates there are > 100 todos and you can get the request for the next set. if (firstPageData?.hasNextResult ?? false) { final secondRequest = firstPageData!.requestForNextResult; - final secondResult = await Amplify.API.query(request: secondRequest!).response; + final secondResult = + await Amplify.API.query(request: secondRequest!).response; return secondResult.data?.items ?? []; } else { return firstPageData?.items ?? []; @@ -76,8 +80,8 @@ Supported operators: - `ge` - greater than or equal - `lt` - less than - `le` - less than or equal -- `between` - Matches models where the given field begins with the provided value. -- `beginsWith` - Matches models where the given field is between the provided start and end values. +- `beginsWith` - Matches models where the given field begins with the provided value. +- `between` - Matches models where the given field is between the provided start and end values. - `contains` - Matches models where the given field contains the provided value. ### Basic Equality Operator @@ -88,7 +92,10 @@ Query for equality on a model's attribute. const blogTitle = 'Test Blog 1'; final queryPredicate = Blog.NAME.eq(blogTitle); -final request = ModelQueries.list(Blog.classType, where: queryPredicate); +final request = ModelQueries.list( + Blog.classType, + where: queryPredicate, +); final response = await Amplify.API.query(request: request).response; final blogFromResponse = response.data?.items.first; ``` @@ -100,8 +107,10 @@ Get all Posts by parent ID ```dart final blogId = blog.id; -final request = - ModelQueries.list(Post.classType, where: Post.BLOG.eq(blogId)); +final request = ModelQueries.list( + Post.classType, + where: Post.BLOG.eq(blogId), +); final response = await Amplify.API.query(request: request).response; final data = response.data?.items ?? []; ``` @@ -112,9 +121,12 @@ Return Posts with a rating less than 5. ```dart const rating = 5; - -final request = ModelQueries.list(Post.classType, where: Post.RATING.lt(rating)); + +final request = ModelQueries.list( + Post.classType, + where: Post.RATING.lt(rating), +); final response = await Amplify.API.query(request: request).response; final data = response.data?.items ?? []; -``` \ No newline at end of file +``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/subscribe-data.mdx b/src/fragments/lib-v1/graphqlapi/flutter/subscribe-data.mdx index b920d68e3a2..11ced1c1351 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/subscribe-data.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/subscribe-data.mdx @@ -10,13 +10,13 @@ Stream> subscribe() { final Stream> operation = Amplify.API .subscribe( subscriptionRequest, - onEstablished: () => print('Subscription established'), + onEstablished: () => safePrint('Subscription established'), ) // Listens to only 5 elements .take(5) .handleError( - (error) { - print('Error in subscription stream: $error'); + (Object error) { + safePrint('Error in subscription stream: $error'); }, ); return operation; @@ -37,25 +37,150 @@ void subscribe() { final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); final Stream> operation = Amplify.API.subscribe( subscriptionRequest, - onEstablished: () => print('Subscription established'), + onEstablished: () => safePrint('Subscription established'), ); subscription = operation.listen( (event) { - print('Subscription event data received: ${event.data}'); + safePrint('Subscription event data received: ${event.data}'); }, - onError: (Object e) => print('Error in subscription stream: $e'), + onError: (Object e) => safePrint('Error in subscription stream: $e'), ); } void unsubscribe() { subscription?.cancel(); + subscription = null; } ``` -Note that in addition to an `onCreate` subscription, you can also call `.onUpdate()` or `.onDelete()`. +In addition to an `onCreate` subscription, you can also call `.onUpdate()` or `.onDelete()`. ```dart final onUpdateSubscriptionRequest = ModelSubscriptions.onUpdate(Todo.classType); // or final onDeleteSubscriptionRequest = ModelSubscriptions.onDelete(Todo.classType); ``` + +## Subscription connection status + +Now that you set up the application and are using subscriptions, you may want to know when the subscription is closed, or reflect to your users when the subscription isn’t healthy. You can monitor the subscription status for changes via `Amplify.Hub` + +```dart +Amplify.Hub.listen( + HubChannel.Api, + (ApiHubEvent event) { + if (event is SubscriptionHubEvent) { + safePrint(event.status); + } + }, +); +``` + +#### SubscriptionStatus + +- **`connected`** - Connected and working with no issues +- **`connecting`** - Attempting to connect (both initial connection and reconnection) +- **`pendingDisconnect`** - Connection has no active subscriptions and is shutting down +- **`disconnected`** - Connection has no active subscriptions and is disconnected +- **`failed`** - Connection had a failure and has been disconnected + +## Automated Reconnection + +Under the hood, we will attempt to maintain a healthy web socket connection through network changes. For example, if a device’s connection changes from Wi-Fi to 5g network, the plugin will attempt to reconnect using the new network. + +Likewise, when disconnected from the internet unexpectedly, the subscription will attempt to reconnect using an exponential retry/back off strategy. By default, we will make 8 recovery attempts over about 50 seconds. If we cannot make a successful connection, then the web socket will be closed. You can customize this strategy when configuring the API plugin through `RetryOptions`. + +```dart +Future _configureAmplify() async { + final apiPlugin = AmplifyAPI( + modelProvider: ModelProvider.instance, + // Optional config + subscriptionOptions: const GraphQLSubscriptionOptions( + retryOptions: RetryOptions(maxAttempts: 10), + ), + ); + await Amplify.addPlugin(apiPlugin); + + try { + await Amplify.configure(amplifyconfig); + } on AmplifyAlreadyConfiguredException { + safePrint( + "Tried to reconfigure Amplify; this can occur when your app restarts on Android."); + } +} +``` + + + +**Important**: While offline, your application will miss messages and will not automatically catch up when reconnection happens. Depending on your use case, you may want to take action to catch up when your app comes back online. The following example solves this problem by retrieving all data on reconnection. + + + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_api/amplify_api.dart'; +import './models/ModelProvider.dart'; // <--- Update import to reflect your project +import 'dart:async'; + +// ... + +List allTodos = []; +SubscriptionStatus prevSubscriptionStatus = SubscriptionStatus.disconnected; +StreamSubscription>? subscription; + +/// ... + +// Init listeners +Amplify.Hub.listen( + HubChannel.Api, + (ApiHubEvent event) { + if (event is SubscriptionHubEvent) { + if (prevSubscriptionStatus == SubscriptionStatus.connecting && + event.status == SubscriptionStatus.connected) { + getTodos(); // refetch todos + } + prevSubscriptionStatus = event.status; + } + }, +); + +subscribe(); + +/// ... + +Future getTodos() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items ?? []; + if (response.errors.isNotEmpty) { + safePrint('errors: ${response.errors}'); + } + + setState(() { + allTodos = todos; + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + return; + } +} + +void subscribe() { + final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); + final Stream> operation = Amplify.API.subscribe( + subscriptionRequest, + onEstablished: () => safePrint('Subscription established'), + ); + subscription = operation.listen( + (event) { + setState(() { + allTodos.add(event.data); + }); + }, + onError: (Object e) => safePrint('Error in subscription stream: $e'), + ); +} + +``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx new file mode 100644 index 00000000000..07a85a3f18d --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx @@ -0,0 +1,151 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Batch put custom resolver', + description: 'Leverage GraphQL mutations to efficiently create multiple objects in one request rather than making sequential requests to create each object individually.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +Sometimes you need to create objects in bulk, rather than creating individual objects sequentially and waiting for all the requests to complete. + +1. Define your schema with a custom mutation. The custom mutation should not be deployed to AppSync beforehand if following these steps, the CLI will attach its own resolver preventing you from attaching a custom resource this way. +```graphql +type Todo @model { + id: ID! + name: String! + description: String +} + +type Mutation { + batchCreateTodo(todos: [BatchCreateTodo]): [Todo] +} + +input BatchCreateTodo { + id: ID + name: String! + description: String +} +``` + +2. [Create a custom resource for your resolver](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) and use the following code snippets as a guide to get started + +2. Follow the steps for creating a custom resolver: +```bash +amplify add custom +``` +```console +? How do you want to define this custom resource? +❯ AWS CDK +? Provide a name for your custom resource +❯ MyCustomResolvers +``` + +Next, install the AppSync dependencies for your custom resource: +```bash +cd amplify/backend/custom/MyCustomResolvers +npm i @aws-cdk/aws-appsync@~1.124.0 +``` + +Use the following template as a starting point for your custom CDK stack, the resolvers must be templated with environment references + +```ts +import * as cdk from '@aws-cdk/core'; +import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; +import * as appsync from '@aws-cdk/aws-appsync'; +import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; + +export class cdkStack extends cdk.Stack { + constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) { + super(scope, id, props); + /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ + new cdk.CfnParameter(this, 'env', { + type: 'String', + description: 'Current Amplify CLI env name', + }); + + // Access other Amplify Resources + const retVal:AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this, + amplifyResourceProps.category, + amplifyResourceProps.resourceName, + [{ + category: "api", + resourceName: "" + }] + ); + + const requestVTL = ` + ## [Start] Initialization default values. ** + $util.qr($ctx.stash.put("defaultValues", $util.defaultIfNull($ctx.stash.defaultValues, {}))) + #set( $createdAt = $util.time.nowISO8601() ) + #set($todosArray = []) + #foreach($item in \${ctx.args.todos}) + $util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId()))) + $util.qr($item.put("createdAt", $util.defaultIfNull($item.createdAt, $createdAt))) + $util.qr($item.put("updatedAt", $util.defaultIfNull($item.updatedAt, $createdAt))) + $util.qr($item.put("__typename", "Todo")) + $util.qr($todosArray.add($util.dynamodb.toMapValues($item))) + #end + ## [End] Initialization default values. ** + $util.toJson( { + "version": "2018-05-29", + "operation": "BatchPutItem", + "tables": { + "-${apiIdRef}-${envRef}": $todosArray + } + } ) + ` + const responseVTL = ` + ## [Start] ResponseTemplate. ** + #if( $ctx.error ) + $util.error($ctx.error.message, $ctx.error.type) + #else + $util.toJson($ctx.result.data.-${apiIdRef}-${envRef}) + #end + ## [End] ResponseTemplate. ** + `; + + + const resolver = new appsync.CfnResolver(this, "custom-resolver", { + // apiId: retVal.api.new.GraphQLAPIIdOutput, + // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 + // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. + // Previously the ApiId is the variable Name which is wrong , it should be variable value as below + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + fieldName: "querySomething", + typeName: "Query", // Query | Mutation | Subscription + requestMappingTemplate: requestVTL, + responseMappingTemplate: responseVTL, + dataSourceName: "TodoTable" // DataSource name + }) + } +} +``` + +By using CloudFormation parameters, you contextualize your custom resolvers to the environment you're working with. + +3. Run `amplify push` and deploy your API + +The full documentation for custom resolvers [is available here](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx new file mode 100644 index 00000000000..b4a94f5cf67 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx @@ -0,0 +1,36 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; +import { getChildPageNodes } from '@/utils/getChildPageNodes'; + +export const meta = { + title: 'Best practice', + description: 'Best practices and examples for working with GraphQL.', + platforms: [ + 'flutter', + ], + route: '/[platform]/build-a-backend/graphqlapi/best-practice', + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + const childPageNodes = getChildPageNodes(meta.route); + return { + props: { + platform: context.params.platform, + meta, + childPageNodes + } + }; +} + + diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx new file mode 100644 index 00000000000..d65d4eee7fb --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx @@ -0,0 +1,132 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'GraphQL query with sorting by date', + description: 'How to implement sorting in a GraphQL query', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/query-with-sorting/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +In this guide you will learn how to implement sorting in a GraphQL API. In our example, you will implement sorting results by date in either an ascending or descending order by implementing an additional data access pattern leveraging a DynamoDB Global Secondary Index using the `@index` GraphQL Transformer directive. + +### Overview + +To get started, let's start with a basic GraphQL schema for a Todo app: + +```graphql +type Todo @model { + id: ID! + title: String! +} +``` + +When the API is created with an `@model` directive, the following queries will automatically be created for you: + +```graphql +type Query { + getTodo(id: ID!): Todo + listTodos( + filter: ModelTodoFilterInput + limit: Int + nextToken: String + ): ModelTodoConnection +} +``` + +Next, take a look at the `ModelTodoConnection` type to get an idea of the data that will be returned when the `listTodos` query is run: + +```graphql +type ModelTodoConnection { + items: [Todo] + nextToken: String +} +``` + +By default, the `listTodos` query will return the `items` array **unordered**. Many times you will need these items to be ordered by title, by creation date, or in some other way. + +To enable this, you can use the [@index](/[platform]/build-a-backend/graphqlapi/data-modeling/) directive. This directive will allow you to set a custom `sortKey` on any field in your API. + +### Implementation + +In this example, you will enable sorting by the `createdAt` field. By default, Amplify will populate this `createdAt` field with a timestamp if none is passed in. + +To enable this, update your schema with the following: + +```graphql +type Todo @model { + id: ID! + title: String! + type: String! + @index( + name: "todosByDate" + queryField: "todosByDate" + sortKeyFields: ["createdAt"] + ) + createdAt: String! +} +``` + + + +When created a Todo, you must now populate the `type` field for this to work properly. + + + +Next, create a few todos being sure to populate the `type` field: + +```graphql +mutation createTodo { + createTodo(input: { title: "Todo 1", type: "Todo" }) { + id + title + } +} +``` + +Now, you can query for todos by date in an ascending or descending order using the new `todosByDate` query: + +```graphql +query todosByDate { + todosByDate(type: "Todo", sortDirection: ASC) { + items { + id + title + createdAt + } + } +} + +query todosByDateDescending { + todosByDate(type: "Todo", sortDirection: DESC) { + items { + id + title + createdAt + } + } +} +``` + +To learn more about the `@index` directive, check out the documentation [here](/[platform]/build-a-backend/graphqlapi/data-modeling/) diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx new file mode 100644 index 00000000000..ee4b595b00c --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx @@ -0,0 +1,552 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Warehouse Management System', + description: 'Configure common access patters for your app following a warehouse management system example.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/warehouse-management/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +In this "Warehouse management system" example, you will learn how to configure common access patterns for your app. This example has the following types: + +- Warehouse +- Product +- Inventory +- Employee +- AccountRepresentative +- Customer + +These types have the following common access patterns: + +1. [Look up employee details by employee ID](#1-look-up-employee-details-by-employee-id) +2. [Query employee details by employee name](#2-query-employee-details-by-employee-name) +3. [Find an employee's phone number(s)](#3-find-an-employees-phone-number) +4. [Find a customer's phone number(s)](#4-find-a-customers-phone-number) +5. [Get orders for a given customer within a given date range](#5-get-orders-for-a-given-customer-within-a-given-date-range) +6. [Show all open orders within a given date range across all customers](#6-show-all-open-orders-within-a-given-date-range-across-all-customers) +7. [See all employees recently hired](#7-see-all-employees-hired-recently) +8. [Find all employees working in a given warehouse](#8-find-all-employees-working-in-a-given-warehouse) +9. [Get all items on order for a given product](#9-get-all-items-on-order-for-a-given-product) +10. [Get current inventories for a given product at all warehouses](#10-get-current-inventories-for-a-product-at-all-warehouses) +11. [Get customers by account representative](#11-get-customers-by-account-representative) +12. [Get orders by account representative and date](#12-get-orders-by-account-representative-and-date) +13. [Get all items on order for a given product](#13-get-all-items-on-order-for-a-given-product) +14. [Get all employees with a given job title](#14-get-all-employees-with-a-given-job-title) +15. [Get inventory by product and warehouse](#15-get-inventory-by-product-by-warehouse) +16. [Get total product inventory](#16-get-total-product-inventory) +17. [Get account representatives ranked by order total and sales period](#17-get-sales-representatives-ranked-by-order-total-and-sales-period) + +The following schema introduces the required indexes and relationships so that you can support these access patterns: + +```graphql +# This "input" configures a global authorization rule to enable public access to +# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/auth +input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY! + +type Order @model { + id: ID! + customerID: ID! @index(name: "byCustomerByStatusByDate", sortKeyFields: ["status", "date"]) @index(name: "byCustomerByDate", sortKeyFields: ["date"]) + accountRepresentativeID: ID! @index(name: "byRepresentativebyDate", sortKeyFields: ["date"]) + productID: ID! @index(name: "byProduct", sortKeyFields: ["id"]) + status: String! + amount: Int! + date: String! +} + +type Customer @model { + id: ID! + name: String! + phoneNumber: String + accountRepresentativeID: ID! @index(name: "byRepresentative", sortKeyFields: ["id"]) + ordersByDate: [Order] @hasMany(indexName: "byCustomerByDate", fields: ["id"]) + ordersByStatusDate: [Order] @hasMany(indexName: "byCustomerByStatusByDate", fields: ["id"]) +} + +type Employee @model { + id: ID! + name: String! @index(name: "byName", queryField: "employeeByName", sortKeyFields: ["id"]) + startDate: String! + phoneNumber: String! + warehouseID: ID! @index(name: "byWarehouse", sortKeyFields: ["id"]) + jobTitle: String! @index(name: "byTitle", queryField: "employeesByJobTitle", sortKeyFields: ["id"]) + newHire: String! @index(name: "newHire", queryField: "employeesNewHire", sortKeyFields: ["id"]) @index(name: "newHireByStartDate", queryField: "employeesNewHireByStartDate", sortKeyFields: ["startDate"]) +} + +type Warehouse @model { + id: ID! + employees: [Employee] @hasMany(indexName: "byWarehouse", fields: ["id"]) +} + +type AccountRepresentative @model { + id: ID! + customers: [Customer] @hasMany(indexName: "byRepresentative", fields: ["id"]) + orders: [Order] @hasMany(indexName: "byRepresentativebyDate", fields: ["id"]) + orderTotal: Int + salesPeriod: String @index(name: "bySalesPeriodByOrderTotal", queryField: "repsByPeriodAndTotal", sortKeyFields: ["orderTotal"]) +} + +type Inventory @model { + productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) + warehouseID: ID! @index(name: "byWarehouseID", queryField: "itemsByWarehouseID") + inventoryAmount: Int! +} + +type Product @model { + id: ID! + name: String! + orders: [Order] @hasMany(indexName: "byProduct", fields: ["id"]) + inventories: [Inventory] @hasMany(fields: ["id"]) +} +``` + +Now that you have the schema created, let's create the items in the database that you will be operating against: + +```graphql +# first +mutation createWarehouse { + createWarehouse(input: {id: "1"}) { + id + } +} + +# second +mutation createEmployee { + createEmployee(input: { + id: "amanda" + name: "Amanda", + startDate: "2018-05-22", + phoneNumber: "6015555555", + warehouseID: "1", + jobTitle: "Manager", + newHire: "true"} + ) { + id + jobTitle + name + newHire + phoneNumber + startDate + warehouseID + } +} + +# third +mutation createAccountRepresentative { + createAccountRepresentative(input: { + id: "dabit" + orderTotal: 400000 + salesPeriod: "January 2019" + }) { + id + orderTotal + salesPeriod + } +} + +# fourth +mutation createCustomer { + createCustomer(input: { + id: "jennifer_thomas" + accountRepresentativeID: "dabit" + name: "Jennifer Thomas" + phoneNumber: "+16015555555" + }) { + id + name + accountRepresentativeID + phoneNumber + } +} + +# fifth +mutation createProduct { + createProduct(input: { + id: "yeezyboost" + name: "Yeezy Boost" + }) { + id + name + } +} + +# sixth +mutation createInventory { + createInventory(input: { + productID: "yeezyboost" + warehouseID: "1" + inventoryAmount: 300 + }) { + productID + inventoryAmount + warehouseID + } +} + +# seventh +mutation createOrder { + createOrder(input: { + amount: 300 + date: "2018-07-12" + status: "pending" + accountRepresentativeID: "dabit" + customerID: "jennifer_thomas" + productID: "yeezyboost" + }) { + id + customerID + accountRepresentativeID + amount + date + customerID + productID + } +} +``` + +### 1. Look up employee details by employee ID + +This can simply be done by querying the employee model with an employee ID, no `@primaryKey` or `@index` need to be explicitly specified to make this work. + +```graphql +query getEmployee($id: ID!) { + getEmployee(id: $id) { + id + name + phoneNumber + startDate + jobTitle + } +} +``` + +### 2. Query employee details by employee name + +The `@index` `byName` on the `Employee` type makes this access-pattern feasible because under the hood an index is created and a query is used to match against the name field. You can use this query: + +```graphql +query employeeByName($name: String!) { + employeeByName(name: $name) { + items { + id + name + phoneNumber + startDate + jobTitle + } + } +} +``` + +### 3. Find an Employee’s phone number + +Either one of the previous queries would work to find an employee’s phone number as long as one has their ID or name. + +### 4. Find a customer’s phone number + +A similar query to those given above but on the Customer model would give you a customer’s phone number. + +```graphql +query getCustomer($customerID: ID!) { + getCustomer(id: $customerID) { + phoneNumber + } +} +``` + +### 5. Get orders for a given customer within a given date range + +There is a one-to-many relation that lets all the orders of a customer be queried. + +This relationship is created by having the `@index` name `byCustomerByDate` on the Order model that is queried by the `@hasMany` relationship on the orders field of the Customer model. + +A sort key with the date is used. What this means is that the GraphQL resolver can use predicates like `Between` to efficiently search the date range rather than scanning all records in the database and then filtering them out. + +The query one would need to get the orders to a customer within a date range would be: + +```graphql +query getCustomerWithOrdersByDate($customerID: ID!) { + getCustomer(id: $customerID) { + ordersByDate(date: { + between: [ "2018-01-22", "2020-10-11" ] + }) { + items { + id + amount + productID + } + } + } +} +``` + +### 6. Show all open orders within a given date range across all customers + +The `@index` `byCustomerByStatusByDate` enables you to run a query that would work for this access pattern. + +In this example, a composite sort key (combination of two or more keys) with the `status` and `date` is used. What this means is that the unique identifier of a record in the database is created by concatenating these two fields (status and date) together, and then the GraphQL resolver can use predicates like `between` or `contains` to efficiently search the unique identifier for matches rather than scanning all records in the database and then filtering them out. + +```graphql +query listCustomersWithOrdersByStatusDate { + listCustomers { + items { + ordersByStatusDate(statusDate: { + between: [ + { status: "pending", date: "2018-01-22" }, + { status: "pending", date: "2020-10-11" } + ]}) { + items { + id + amount + date + } + } + } + } +} +``` + +### 7. See all employees hired recently + +Having `@index(name: "newHire", fields: ["newHire", "id"])` on the `Employee` model allows one to query by whether an employee has been hired recently. + +```graphql +query employeesNewHire { + employeesNewHire(newHire: "true") { + items { + id + name + phoneNumber + startDate + jobTitle + } + } +} +``` + +You can also query and have the results returned by start date by using the `employeesNewHireByStartDate` query: + +```graphql +query employeesNewHireByDate { + employeesNewHireByStartDate(newHire: "true") { + items { + id + name + phoneNumber + startDate + jobTitle + } + } +} +``` + +### 8. Find all employees working in a given warehouse + +This needs a one to many relationship from warehouses to employees. As can be seen from the `@hasMany` relationship in the `Warehouse` model, this relationship uses the `byWarehouse` index on the `Employee` model. The relevant query would look like this: + +```graphql +query getWarehouse($warehouseID: ID!) { + getWarehouse(id: $warehouseID) { + id + employees{ + items { + id + name + startDate + phoneNumber + jobTitle + } + } + } +} +``` + +### 9. Get all items on order for a given product + +This access-pattern would use a one-to-many relation from products to orders. With this query you can get all orders of a given product: + +```graphql +query getProductOrders($productID: ID!) { + getProduct(id: $productID) { + id + orders { + items { + id + status + amount + date + } + } + } +} +``` + +### 10. Get current inventories for a product at all warehouses + +The query needed to get the inventories of a product in all warehouses would be: + +```graphql +query getProductInventoryInfo($productID: ID!) { + getProduct(id: $productID) { + id + inventories { + items { + warehouseID + inventoryAmount + } + } + } +} +``` + +### 11. Get customers by account representative + +This uses a has-many relationship between account representatives and customers: + +The query needed would look like this: + +```graphql +query getCustomersForAccountRepresentative($representativeId: ID!) { + getAccountRepresentative(id: $representativeId) { + customers { + items { + id + name + phoneNumber + } + } + } +} +``` + +### 12. Get orders by account representative and date + +As can be seen in the AccountRepresentative model this relationship uses the `byRepresentativebyDate` field on the `Order` model to create the connection needed. The query needed would look like this: + +```graphql +query getOrdersForAccountRepresentative($representativeId: ID!) { + getAccountRepresentative(id: $representativeId) { + id + orders(date: { + between: [ + "2010-01-22", "2020-10-11" + ] + }) { + items { + id + status + amount + date + } + } + } +} +``` + +### 13. Get all items on order for a given product + +This is the same as number 9. + +### 14. Get all employees with a given job title + +Using the `byTitle` `@index` makes this access pattern quite easy. + +```graphql +query employeesByJobTitle { + employeesByJobTitle(jobTitle: "Manager") { + items { + id + name + phoneNumber + jobTitle + } + } +} +``` + +### 15. Get inventory by product by warehouse + +Here having the inventories be held in a separate model is particularly useful since this model can have its own partition key and sort key such that the inventories themselves can be queried as is needed for this access-pattern. + +A query on this model would look like this: + +```graphql +query inventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { + getInventory(productID: $productID, warehouseID: $warehouseID) { + productID + warehouseID + inventoryAmount + } +} + +``` + +You can also get all inventory from an individual warehouse by using the `itemsByWarehouseID` query created by the `byWarehouseID` key: + +```graphql +query byWarehouseId($warehouseID: ID!) { + itemsByWarehouseID(warehouseID: $warehouseID) { + items { + inventoryAmount + productID + } + } +} +``` + +### 16. Get total product inventory + +How this would be done depends on the use case. If one just wants a list of all inventories in all warehouses, one could just run a list inventories on the Inventory model: + +```graphql +query listInventorys { + listInventorys { + items { + productID + warehouseID + inventoryAmount + } + } +} +``` + +### 17. Get sales representatives ranked by order total and sales period + +The sales period is either a date range or maybe even a month or week. Therefore you can set the sales period as a string and query using the combination of `salesPeriod` and `orderTotal`. You can also set the `sortDirection` in order to get the return values from largest to smallest: + +```graphql +query repsByPeriodAndTotal { + repsByPeriodAndTotal( + sortDirection: DESC, + salesPeriod: "January 2019", + orderTotal: { + ge: 1000 + }) { + items { + id + orderTotal + } + } +} +``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx new file mode 100644 index 00000000000..f50ed4399b4 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx @@ -0,0 +1,241 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'JavaScript, Android, Swift, and Flutter client code generation', + description: "Amplify's codegen capabilities generate native code for iOS and Android, as well as types for Flow and TypeScript. Codegen can also generate GraphQL statements (queries, mutations, and subscriptions).", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/client-code-generation/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +"Codegen" generates native code for Swift (iOS), Java (Android), and JavaScript that represent your GraphQL API's data models. It can also generate GraphQL statements (queries, mutations, and subscriptions) so that you don't have to hand code them. + +The design of codegen functionality provides mechanisms to run at different points in your app development lifecycle, including when you create or update an API as well as independently when you want to just update the data fetching requirements of your app but leave your API alone. It additionally allows you to work in a team where the schema is updated or managed by another person. Finally, you can also include the codegen in your build process so that it runs automatically (such as from in Xcode). + +## Generate GraphQL client helper code for GraphQL APIs deployed with Amplify GraphQL CDK construct + +The necessary GraphQL client helper code differ from platform to platform. For JavaScript GraphQL client code, you need to reference the API ID that you receive after you deploy your application. For Android, iOS, and Flutter you can reference the local GraphQL schema to generate models for your API client. + +### JavaScript / TypeScript GraphQL API client helper code + +Go to your frontend app's root directory and run the following command in the Terminal: + +```bash +npx @aws-amplify/cli codegen add --apiId <...> --region <...> +``` + +This will download your API's schema and by default generate client helper code into the `src/graphql` folder. After every API deployment, you can rerun the following command to generate updated GraphQL statement and types: + +``` +npx @aws-amplify/cli codegen +``` + +### Generate "models" for Android, Swift, Flutter, and JavaScript DataStore + +The Android, Swift, Flutter, and DataStore on JavaScript use the "modelgen" pattern to interact with the client library. To generate models, run the following command from your frontend application's root directory: + +```bash +npx @aws-amplify/cli codegen models \ + --model-schema \ + --target [android|ios|flutter|javascript|typescript] \ + --output-dir ./ +``` + +## Generate GraphQL Client code with Amplify CLI-deployed GraphQL API + +### Create API then automatically generate code + +```bash +amplify init +amplify add api (select GraphQL) +amplify push +``` + +You’ll see questions as before, but now it will also automatically ask you if you want to generate GraphQL statements and do codegen. It will also respect the `./app/src/main` directory for Android projects. After the AppSync deployment finishes the Swift file will be automatically generated (Android you’ll need to kick off a [Gradle Build step](#androiduse)) and you can begin using in your app immediately. + +When you deploy your GraphQL API to the cloud, you are prompted to configure codegen. When a project is configured to generate code with codegen, it stores all the configuration `.graphqlconfig.yml` file in the root folder of your project. To make changes to the configuration, use `amplify configure codegen`. + +### Modify GraphQL schema, push, then automatically generate code + +During development, you might wish to update your GraphQL schema and generated code as part of an iterative dev/test cycle. Modify & save your schema in `amplify/backend/api//schema.graphql` then run: + +```bash +amplify push +``` + +Each time you will be prompted to update the code in your API and also ask you if you want to run codegen again as well, including regeneration of the GraphQL statements from the new schema. + +### No API changes, just update GraphQL statements & generate code + +One of the benefits of GraphQL is the client can define it's data fetching requirements independently of the API. Amplify codegen supports this by allowing you to modify the selection set (e.g. add/remove fields inside the curly braces) for the GraphQL statements and running type generation again. This gives you fine-grained control over the network requests that your application is making. Modify your GraphQL statements (default in the `./graphql` folder unless you changed it) then save the files and run: + +```bash +amplify codegen types +``` + +A new updated Swift file will be created (or run Gradle Build on Android for the same). You can then use the updates in your application code. + +## Shared schema, modified elsewhere (e.g. console or team workflows) + +Suppose you are working in a team and the schema is updated either from the AWS AppSync console or on another system. Your types are now out of date because your GraphQL statement was generated off an outdated schema. The easiest way to resolve this is to regenerate your GraphQL statements, update them if necessary, and then generate your types again. Modify the schema in the console or on a separate system, then run: + +```bash +amplify codegen statements +amplify codegen types +``` + +You should have newly generated GraphQL statements and Swift code that matches the schema updates. If you ran the second command your types will be updated as well. Alternatively, if you run `amplify codegen` alone it will perform both of these actions. + +## Introspection Schema outside of an initialized project + +If you would like to generate statements and types without initializing an amplify project, you can do so by providing your introspection schema named `schema.json` in your project directory and adding codegen from the same directory. To download your introspection schema from an AppSync api, in the AppSync console go to the schema editor and under "Export schema" choose `schema.json`. + +```bash +amplify add codegen +``` + +Once codegen has been added you can update your introspection schema, then generate statements and types again without re-entering your project information. + +```bash +amplify codegen +``` + +You can update your project and codegen configuration if required. + +```bash +amplify configure codegen +amplify codegen +``` + +When generating types, codegen uses GraphQL statements as input. It will generate only the types that are being used in the GraphQL statements. + +## Codegen commands + +### amplify add codegen + +```bash +amplify add codegen +``` + +The `amplify add codegen` allows you to add AppSync API created using the AWS console. If you have your API is in a different region then that of your current region, the command asks you to choose the region. If you are adding codegen outside of an initialized amplify project, provide your introspection schema named `schema.json` in the same directory that you make the add codegen call from. **Note**: If you use the --apiId flag to add an externally created AppSync API, such as one created in the AWS console, you will not be able to manage this API from the Amplify CLI with commands such as amplify api update when performing schema updates. You cannot add an external AppSync API when outside of an initialized project. + +### amplify configure codegen + +```bash +amplify configure codegen +``` + +The `amplify configure codegen` command allows you to update the codegen configuration after it is added to your project. When outside of an initialized project, you can use this to update your project configuration as well as the codegen configuration. + +### amplify codegen statements + +```bash +amplify codegen statements [--nodownload] [--maxDepth ] +``` + +The `amplify codegen statements` command generates GraphQL statements(queries, mutation and subscription) based on your GraphQL schema. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. + +### amplify codegen types + +```bash +amplify codegen types +``` + +The `amplify codegen types [--nodownload]` command generates GraphQL `types` for Flow and typescript and Swift class in an iOS project. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. + +### amplify codegen + +```bash +amplify codegen [--maxDepth ] +``` + +The `amplify codegen [--nodownload]` generates GraphQL `statements` and `types`. This command downloads introspection schema every time it is run but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. If you are running codegen outside of an initialized amplify project, the introspection schema named `schema.json` must be in the same directory that you run amplify codegen from. This command will not download the introspection schema when outside of an amplify project - it will only use the introspection schema provided. + +## Statement depth + +In the below schema there are connections between `Comment` -> `Post` -> `Blog` -> `Post` -> `Comments`. When generating statements codegen has a default limit of 2 for depth traversal. But if you need to go deeper than 2 levels you can change the `maxDepth` parameter either when setting up your codegen or by passing `--maxDepth` parameter to `codegen` + +```graphql +type Blog @model { + id: ID! + name: String! + posts: [Post] @hasMany +} +type Post @model { + id: ID! + title: String! + blog: Blog @belongsTo + comments: [Comment] @hasMany +} +type Comment @model { + id: ID! + content: String + post: Post @belongsTo +} +``` + +```graphql +query GetComment($id: ID!) { + getComment(id: $id) { + # depth level 1 + id + content + post { + # depth level 2 + id + title + blog { + # depth level 3 + id + name + posts { + # depth level 4 + items { + # depth level 5 + id + title + } + nextToken + } + } + comments { + # depth level 3 + items { + # depth level 4 + id + content + post { + # depth level 5 + id + title + } + } + nextToken + } + } + } +} +``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx new file mode 100644 index 00000000000..5299f7d9142 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx @@ -0,0 +1,1014 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Connect API to existing MySQL or PostgreSQL database', + description: 'Learn how to connect your API to an existing MySQL or PostgreSQL database.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/connect-api-to-existing-database/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +The following content requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + +In this section, you'll learn how to: + +- Connect Amplify GraphQL API to an existing MySQL or PostgreSQL database +- Execute SQL statements with custom GraphQL queries and mutations using the new `@sql` directive +- Generate create, read, update, and delete API operations based on your SQL database schema + +## Connect your API with an existing MySQL or PostgreSQL database + + + + +Pre-requisites: + +- Have an existing [MySQL database](https://aws.amazon.com/getting-started/hands-on/create-mysql-db/) or [PostgreSQL database](https://aws.amazon.com/getting-started/hands-on/create-connect-postgresql-db/) deployed +- The [AWS CDK CLI is installed](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install) +- Have an [AWS CDK application initialized](https://docs.aws.amazon.com/cdk/v2/guide/hello_world.html) + + + +This feature is not yet available in the Asia Pacific (Hong Kong, ap-east-1) or Europe (Milan, eu-south-1) regions. + + + + + +First, place your database connection information (hostname, username, password, port, and database name) into Systems Manager, each as a `SecureString`. + +Go to the Systems Manager console, navigate to Parameter Store, and click "Create Parameter". Create five different SecureStrings: one each for the hostname of your database server, the username and password to connect, the database port, and the database name. + +Your Systems Manager configuration should look something like this: + +![A screenshot of an AWS Systems Manager console page titled "Parameter Store". The page shows a list of parameters with names like "/amplify-cdk-app/username", "/amplify-cdk-app/password", and "/amplify-cdk-app/hostname" indicating database connection details. Each parameter is of Tier "Standard" and typed as "SecureString". The last modified date is displayed for each parameter.](/images/storing-db-creds-in-ssm.png) + + +First, place your database connection information (hostname, username, password, port, and database name) into Secrets Manager. + +Go to the Secrets Manager console, navigate to Secrets, and click "Store a new secret". You may create the secret in any manner as long as there are `username` and `password` keys defined. + +![A screenshot of a page in the Secrets Manager console titled "Secret value info". The screenshot shows an example of a secret's keys and values in a table including "username", "password", "engine", "host", "port", and "dbClusterIdentifier".](/images/storing-db-creds-in-secrets-manager.png) + +Optionally, you can decide whether to encrypt the secret using the KMS key that Secrets Manager creates or a customer managed KMS key that you create. + +You can also configure a rotation schedule and create a Lambda function or choose an existing Lambda function from your account to rotate the database credentials automatically. + + + +Install the following package to add the Amplify GraphQL API construct to your dependencies: + +```sh +npm install @aws-amplify/graphql-api-construct +``` + +Create a new `schema.sql.graphql` file within your CDK app’s `lib/` folder that includes the APIs you want to expose. Define your GraphQL object types, queries, and mutations to match the APIs you wish to expose. For example, define object types for database tables, queries to fetch data from those tables, and mutations to modify those tables. + +```graphql +type Post { + id: Int! + title: String! + content: String! + published: Boolean + publishedDate: AWSDate @refersTo(name: "published_date") +} + +type Query { + searchPosts(contains: String!): [Post] + @sql( + statement: "SELECT * FROM posts WHERE title LIKE CONCAT('%', :contains, '%');" + ) + @auth(rules: [{ allow: public }]) +} + +type Mutation { + createPost(title: String! content: String!): AWSJSON + @sql(statement: "INSERT INTO posts (title, content) VALUES (:title, :content);") + @auth(rules: [{ allow: public }]) +} +``` + +You can use the `:variable` notation to reference input variables from the query request. + + +Amplify’s GraphQL API operates on a deny-by-default basis. The `{ allow: public }` auth rule in the example schema above designates that anyone using an API Key is authorized to execute the query. + +Review [Authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) to limit access to these queries and mutations based on API Key, Amazon Cognito User Pool, OpenID Connect, AWS Identity and Access Management (IAM), or a custom Lambda function. + + + +Next, open the main stack file in your CDK project (usually located in `lib/-stack.ts`). Import the necessary constructs at the top of the file: + +```ts +import { + AmplifyGraphqlApi, + AmplifyGraphqlDefinition +} from '@aws-amplify/graphql-api-construct'; + +import path from 'path'; +``` + +In the main stack class, add the following code to define a new GraphQL API. Replace `stack` with the name of your stack instance (often referenced via `this`): + + + +```ts +new AmplifyGraphqlApi(stack, 'SqlBoundApi', { + apiName: 'MySqlBoundApi', + definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( + [path.join(__dirname, 'schema.sql.graphql')], + { + name: 'MySQLSchemaDefinition', + dbType: 'MYSQL', + vpcConfiguration: { + vpcId: 'vpc-123456', + securityGroupIds: ['sg-123', 'sg-456'], + subnetAvailabilityZoneConfig: [ + { subnetId: 'sn-123456', availabilityZone: 'us-east-1a' }, + { subnetId: 'sn-987654', availabilityZone: 'us-east-1b' } + ] + }, + dbConnectionConfig: { + hostnameSsmPath: + '/path/to/ssm/SecureString/containing/value/of/hostname', + portSsmPath: '/path/to/ssm/SecureString/containing/value/of/port', + usernameSsmPath: + '/path/to/ssm/SecureString/containing/value/of/username', + passwordSsmPath: + '/path/to/ssm/SecureString/containing/value/of/password', + databaseNameSsmPath: + '/path/to/ssm/SecureString/containing/value/of/databaseName' + } + } + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); +``` + + + + +```ts +new AmplifyGraphqlApi(this, 'SqlBoundApi', { + apiName: 'MySqlBoundApi', + definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( + [path.join(__dirname, 'schema.sql.graphql')], + { + name: 'MySQLSchemaDefinition', + dbType: 'MYSQL', + vpcConfiguration: { + vpcId: 'vpc-123456', + securityGroupIds: ['sg-123', 'sg-456'], + subnetAvailabilityZoneConfig: [ + { subnetId: 'sn-123456', availabilityZone: 'us-east-1a' }, + { subnetId: 'sn-987654', availabilityZone: 'us-east-1b' }, + ], + }, + dbConnectionConfig: { + databaseName: 'database', + port: 3306, + hostname: 'database-1-instance-1.id.region.rds.amazonaws.com', + secretArn: + 'arn:aws:secretsmanager:Region1:123456789012:secret:MySecret-a1b2c3', + }, + } + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30), + }, + }, +}); +``` + + + + +The API will have an API key enabled for authorization. + +Before deploying, make sure to: + +- Set a value for `name`. This will be used to name the AppSync DataSource itself, plus any associated resources like resolver Lambdas. This name must be unique across all schema definitions in a GraphQL API. + +- Change the `dbType` to match your database engine. This is the type of the SQL database used to process model operations for this definition. Supported engines are `"MYSQL"` or `"POSTGRES"`. + +- Update the SSM parameter paths within `dbConnectionConfig` to point to those existing in your AWS account. These are the parameters the SQL Lambda will use to connect to the database. + +- If your database instance exists within a VPC, update the `vpcConfiguration` properties - `vpcId`, `securityGroupIds`, and `subnetAvailabilityZoneConfig` with your vpc details. This is the configuration of the VPC into which to install the SQL Lambda. + + + +If your database exists within a VPC, the RDS instance must be configured to be `Publicly accessible`. This does not mean the instance needs to accessible from the internet. + +The target security group(s) must have two inbound rules set up: + +- A rule allowing traffic on port 443 from the security group. + +- An inbound rule allowing traffic on the database port from the security group. (Default: 3306 for MySQL. 5432 for PostgreSQL.) + +In addition, the target security group(s) must have two outbound rules set up: + +- An outbound rule allowing traffic on port 443 to the security group. + +- An outbound rule allowing traffic on the database port to the security group. (Default: 3306 for MySQL. 5432 for PostgreSQL.) + + + **NOTE:** Make sure to limit the type of inbound traffic your security group + allows according to your security needs and/or use cases. For information on + security group rules, please refer to the [Amazon EC2 Security Group Rules reference](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html?icmpid=docs_ec2_console). + + + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + + + +Consider adding an RDS Proxy in front of the cluster to manage database connections. + +When using Amplify GraphQL API with a relational database like Amazon RDS, each query from your application needs to open a separate connection to the database. + +If there are a large number of queries occurring concurrently, it can exceed the connection limit on the database and result in errors like "Too many connections". To avoid this, Amplify can use an RDS Proxy when connecting your GraphQL API to a database. + +The RDS Proxy acts as an intermediary sitting in front of your database. Instead of each application query opening a direct connection to the database, they will connect through the Proxy. The Proxy helps manage and pool these connections to avoid overwhelming your database cluster. This improves the availability of your API, allowing more queries to execute concurrently without hitting connection limits. + +However, there is a tradeoff of increased latency - queries may take slightly longer as they wait for an available connection from the Proxy pool. There are also additional costs associated with using RDS Proxy. Please refer to the [pricing page for RDS Proxy](https://aws.amazon.com/rds/proxy/pricing/) to learn more. + + + +## Create custom queries and mutations + +Amplify GraphQL API for SQL databases introduces the `@sql` directive, which allows you to define SQL statements in custom GraphQL queries and mutations. This provides more flexibility when the default, auto-generated GraphQL queries and mutations are not sufficient. + +There are two ways to specify the SQL statement - inline or by referencing a `.sql` file. + +### Inline SQL Statement + +For getting started, you can embed the SQL statement directly in the schema using the `statement` argument. + +The SQL statement can use parameters in the format `:variable`, which will be bound to the input variables passed when executing a custom GraphQL query or mutation. + +In the example below, a SQL statement is defined, accepting a `searchTerm` input variable. + +```graphql +type Query { + searchPosts(searchTerm: String): [Post] + @sql(statement: "SELECT * FROM posts WHERE title LIKE :searchTerm;") + @auth(rules: [{ allow: public }]) +} +``` + +{/* TODO: Add a NOTE: about proxy/connection pinning here. */} + +### SQL File Reference + +For longer, more complex SQL queries, you can specify the statement in separate `.sql` files rather than inline. Referencing a file keeps your schema clean and allows reuse of SQL statements across fields. + + + + +First, update your GraphQL schema file to reference a SQL file name without the `.sql` extension: + +```graphql +type Query { + getPublishedPosts(start: AWSDate, end: AWSDate): [Post] + @sql(reference: "getPublishedPostsByDateRange") + @auth(rules: [{ allow: public }]) +} +``` + +Next, create a new `lib/sql-statements` folder and add any custom queries or mutations as SQL files. For example, you could create different `.sql` files for different queries: + +```sql +-- lib/sql-statements/getPublishedPostsByDateRange.sql +SELECT p.id, p.title, p.content, p.published_date +FROM posts p +WHERE p.published = 1 + AND p.published_date > :startDate + AND p.published_date < :endDate +ORDER BY p.published_date DESC +LIMIT 10 +``` + +```sql +-- lib/sql-statements/getPostById.sql +SELECT * FROM posts WHERE id = :id; +``` + +Then, you can import the `SQLLambdaModelDataSourceStrategyFactory` which helps define the datasource strategy from the custom `.sql` files you've created. + +```js +import { SQLLambdaModelDataSourceStrategyFactory } from '@aws-amplify/graphql-api-construct'; +import path from 'path'; +import fs from 'fs'; +``` + +In your `lib/-stack.ts` file, read from the `sql-statements/` folder and add them as custom SQL statements to your Amplify GraphQL API: + +```js + +// Define custom SQL statements folder path +const sqlStatementsPath = path.join(__dirname, 'sql-statements'); + +// Use the Factory to define the SQL data source strategy +const sqlStrategy = SQLLambdaModelDataSourceStrategyFactory.fromCustomSqlFiles( + // File paths to all SQL statements + fs + .readdirSync(sqlStatementsPath) + .map((file) => path.join(sqlStatementsPath, file)), + // Move your connection information and VPC config into here + { + dbType: 'MYSQL', + name: 'MySQLSchemaDefinition', + dbConnectionConfig: { + //... + }, + vpcConfiguration: { + //... + } + } +); + + +const amplifyApi = new AmplifyGraphqlApi(this, 'SqlBoundApi', { + definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( + [path.join(__dirname, 'schema.sql.graphql')], + sqlStrategy + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); +``` + +The SQL statements defined in the `.sql` files will be executed as if they were defined inline in the schema. The same rules apply in terms of using parameters, ensuring valid SQL syntax, and matching the return type to row data. + + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + + +### Custom Query + +For reference, you define a GraphQL query by adding a new field under a `type Query` object: + +```graphql +type Query { + searchPostsByTitle(title: String): [Post] + @sql( + statement: "SELECT * FROM posts WHERE title LIKE CONCAT('%', :title, '%');" + ) + @auth(rules: [{ allow: public }]) +} +``` + +### Custom Mutation + +For reference, you define a GraphQL mutation by adding a new field under a `type Mutation` object: + +```graphql +type Mutation { + publishPostById(id: ID!): AWSJSON + @sql(statement: "UPDATE posts SET published = :published WHERE id = :id;") + @auth(rules: [{ allow: public }]) +} +``` + +### Returning row data from custom mutations + +SQL statements such as `INSERT`, `UPDATE` and `DELETE` return the number of rows affected. + +If you want to return the result of the SQL statement, you can use `AWSJSON` as the return type. + +```graphql +type Mutation { + publishPosts: AWSJSON @sql(statement: "UPDATE posts SET published = 1;") + @auth(rules: [{ allow: public }]) +} +``` + +This will return a JSON response similar to this: + +```json +{ + "data": { + "publishPosts": "{\"fieldCount\":0,\"affectedRows\":7,\"insertId\":0,\"info\":\"Rows matched: 7 Changed: 7 Warnings: 0\",\"serverStatus\":34,\"warningStatus\":0,\"changedRows\":7}" + } +} +``` + +However, you might want to return the actual row data instead. + + + + +In MySQL, you can create and call a stored procedure that performs both an UPDATE statement and SELECT query to return a single post. + +Create a stored procedure by running the following SQL statement in your MySQL database: + +```sql +CREATE PROCEDURE publish_post (IN postId VARCHAR(255)) + +BEGIN +UPDATE posts SET published = 1 WHERE id = postId; + +SELECT * FROM posts WHERE id = postId LIMIT 1; +END +``` + +Call the stored procedure from the custom mutation: + +```graphql +type Mutation { + publishPostById(id: String!): [Post] + @sql(statement: "CALL publish_post(:id);") + @auth(rules: [{ allow: public }]) +} +``` + + + + +In PostgreSQL, you can add a `RETURNING` clause to an `INSERT`, `UPDATE`, or `DELETE` statement and get the actual modified row data. + +Example: + +```graphql +type Mutation { + publishPostById(id: String!): [Post] + @sql(statement: "UPDATE posts SET price = :id RETURNING *;") + @auth(rules: [{ allow: public }]) +} +``` + + + + + + The return type for custom queries and mutations expecting row data must + be an array of the corresponding model. + + + +## Apply authorization rules + +### Model level authorization rules + +The `@auth` directive can be used to restrict access to data and operations by specifying authorization rules. It allows granular access control over the GraphQL API based on the user's identity and attributes. You can for example, limit a query or mutation to only logged-in users via an `@auth(rules: [{ allow: private }])` rule or limit access to only users of the "Admin" group via an `@auth(rules: [{ allow: groups, groups: ["Admin"] }])` rule. + +All model-level authorization rules are supported for Amplify GraphQL schemas generated from MySQL and PostgreSQL databases. + +In the example below, public users authorized via API Key are granted unrestricted access to all posts. + +Add the following auth rule to the `Post` model within the `schema.sql.graphql` file: + +```graphql +type Post @model @refersTo(name: "posts") @auth(rules: [{ allow: public }]) { + id: String! @primaryKey + title: String! + content: String! +} +``` + +For more information on each rule please refer to our documentation on [Authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules). + +### Field-level authorization rules + +Field level auth rules are also supported for Amplify GraphQL schemas generated from MySQL and PostgreSQL databases. + +In the example below, unauthenticated users can read post data but only the owner of the post can perform operations on the `published` field. + +```graphql +type Post + @model + @refersTo(name: "posts") + @auth(rules: [ + { allow: public, operations: [read] }, + { allow: owner } + ]) { + id: String! @primaryKey + title: String! + content: String! + published: Boolean + // highlight-start + @auth(rules: [{ allow: owner }]) + // highlight-end +} +``` + +For more information on field-level auth rules please refer to our documentation on [Field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules). + +## Deploy your API + + + +To deploy the API, you can use the `cdk deploy` command: + +```sh +cdk deploy +``` + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + +Now the API has been deployed and you can start using it! + +You can start querying from the AWS AppSync console or integrate it into your application using the AWS Amplify libraries! + +## Auto-generate CRUDL operations for existing tables + +You can generate common CRUDL operations for your database tables based on your database schema. This saves time from hand-authoring the GraphQL types, queries, and mutations and SQL statements for common CRUDL use cases. After you generate the operations, you can annotate the `@model` types with authorization rules. + +Create a `Ingredients` table in your database: + +```sql +CREATE TABLE Ingredients ( + id varchar(255) NOT NULL PRIMARY KEY, + name varchar(255) NOT NULL, + unit_of_measurement varchar(255) NOT NULL, + price decimal(10, 2) NOT NULL, + supplier_id int, +); +``` + +### Step 1 - Export database schema as CSV + +Execute the following SQL statement on your database using a MySQL, PostgreSQL Client, or CLI tool similar to `psql` and export the output to a CSV file: + + + You must include column headers when exporting the database schema output to a CSV file. + + +Replace `` with the name of your database/schema. + + + +```sql +SELECT DISTINCT + INFORMATION_SCHEMA.COLUMNS.TABLE_NAME, + INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME, + INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT, + INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION, + INFORMATION_SCHEMA.COLUMNS.DATA_TYPE, + INFORMATION_SCHEMA.COLUMNS.COLUMN_TYPE, + INFORMATION_SCHEMA.COLUMNS.IS_NULLABLE, + INFORMATION_SCHEMA.COLUMNS.CHARACTER_MAXIMUM_LENGTH, + INFORMATION_SCHEMA.STATISTICS.INDEX_NAME, + INFORMATION_SCHEMA.STATISTICS.NON_UNIQUE, + INFORMATION_SCHEMA.STATISTICS.SEQ_IN_INDEX, + INFORMATION_SCHEMA.STATISTICS.NULLABLE +FROM INFORMATION_SCHEMA.COLUMNS +LEFT JOIN INFORMATION_SCHEMA.STATISTICS ON INFORMATION_SCHEMA.COLUMNS.TABLE_NAME=INFORMATION_SCHEMA.STATISTICS.TABLE_NAME AND INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME=INFORMATION_SCHEMA.STATISTICS.COLUMN_NAME +WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA = ''; +-- Replace database name here ^^^^^^^^^^^^^^^ +``` + +Your exported SQL schema should look something like this: + +```csv +TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,ORDINAL_POSITION,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,CHARACTER_MAXIMUM_LENGTH,INDEX_NAME,NON_UNIQUE,SEQ_IN_INDEX,NULLABLE +Ingredients,id,,1,int,int,NO,,PRIMARY,0,1,"" +Ingredients,name,,2,varchar,varchar(100),NO,100,,,, +Ingredients,unit_of_measurement,,3,varchar,varchar(50),NO,50,,,, +Ingredients,price,,4,decimal,"decimal(10,2)",NO,,,,, +Ingredients,supplier_id,,6,int,int,YES,,,,, +Meals,id,,1,int,int,NO,,PRIMARY,0,1,"" +``` + + + +```sql +SELECT DISTINCT + INFORMATION_SCHEMA.COLUMNS.table_name, + enum_name,enum_values,column_name,column_default,ordinal_position,data_type,udt_name,is_nullable,character_maximum_length,indexname,constraint_type, + REPLACE(SUBSTRING(indexdef from '\((.*)\)'), '"', '') as index_columns +FROM INFORMATION_SCHEMA.COLUMNS +LEFT JOIN pg_indexes +ON + INFORMATION_SCHEMA.COLUMNS.table_name = pg_indexes.tablename + AND INFORMATION_SCHEMA.COLUMNS.column_name = ANY(STRING_TO_ARRAY(REPLACE(SUBSTRING(indexdef from '\((.*)\)'), '"', ''), ', ')) + LEFT JOIN ( + SELECT + t.typname AS enum_name, + ARRAY_AGG(e.enumlabel) as enum_values + FROM pg_type t JOIN + pg_enum e ON t.oid = e.enumtypid JOIN + pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE n.nspname = 'public' + GROUP BY enum_name + ) enums + ON enums.enum_name = INFORMATION_SCHEMA.COLUMNS.udt_name + LEFT JOIN information_schema.table_constraints + ON INFORMATION_SCHEMA.table_constraints.constraint_name = indexname + AND INFORMATION_SCHEMA.COLUMNS.table_name = INFORMATION_SCHEMA.table_constraints.table_name +WHERE INFORMATION_SCHEMA.COLUMNS.table_schema = 'public' + AND INFORMATION_SCHEMA.COLUMNS.TABLE_CATALOG = ''; +-- Replace database name here ^^^^^^^^^^^^^^^ +``` + +Your exported SQL schema should look something like this: + +```csv +"table_name","enum_name","enum_values","column_name","column_default","ordinal_position","data_type","udt_name","is_nullable","character_maximum_length","indexname","constraint_type","index_columns" +"Ingredients","","","id","","1","bigint","int8","NO","","Ingredients_pkey","PRIMARY KEY","id" +"Ingredients","","","name","","2","text","text","NO","","","","" +"Ingredients","","","unit_of_measurement","","3","text","text","NO","","","","" +"Ingredients","","","price","","4","text","text","NO","","","","" +"Ingredients","","","supplier_id","","5","bigint","int8","NO","","","","" +``` + + + + +### Step 2 - Generate GraphQL schema from database schema + +Next, generate an Amplify GraphQL API schema by running the following command, replacing the `--engine-type` value with your database engine of `mysql` or `postgres`, and the `--sql-schema` value with the path to the CSV file created in the previous step: + +```bash +npx @aws-amplify/cli api generate-schema --engine-type mysql --sql-schema schema.csv --out schema.sql.graphql +``` + + +Next, update the first argument of `AmplifyGraphqlDefinition.fromFilesAndStrategy` to include the `schema.sql.graphql` file generated in the previous step: + +```ts +new AmplifyGraphqlApi(stack, 'SqlBoundApi', { + apiName: 'MySqlBoundApi', + definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( + [path.join(__dirname, 'schema.sql.graphql')], // file path + { + // ...strategy options + } + ) +}); +``` + +### Step 3 - Apply authorization rules for your generated GraphQL API + +Open your **schema.sql.graphql** file, you should see something like this. The auto-generated schema automatically changes the casing to better match common GraphQL conventions. Amplify's GraphQL API's operate on a **deny-by-default principle**, this means you must explicitly add `@auth` authorization rules in order to make this API accessible to your users. Currently only model-level authorization is supported. + +```graphql +input AMPLIFY { + engine: String = "mysql" +} + + +type Ingredient @refersTo(name: "Ingredients") @model { + id: Int! @refersTo(name: "ingredient_id") @primaryKey + name: String! + unitOfMeasurement: String! @refersTo(name: "unit_of_measurement") + price: Float! + supplierId: Int @refersTo(name: "supplier_id") +} +``` + +In our example, we'll add a public authorization rule, meaning anyone with an API key can create, read, update, and delete records from the database. Review [Customize authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) to see the full scope of model-level authorization capabilities. + +```diff +input AMPLIFY { + engine: String = "mysql" +} + + +- type Ingredient @refersTo(name: "Ingredients") @model { ++ type Ingredient ++ @refersTo(name: "Ingredients") ++ @model ++ @auth(rules: [{ allow: public }]) { + id: Int! @refersTo(name: "ingredient_id") @primaryKey + name: String! + unitOfMeasurement: String! @refersTo(name: "unit_of_measurement") + price: Float! + supplierId: Int @refersTo(name: "supplier_id") +} +``` + +Finally, remember to deploy your API to the cloud: + + + +To deploy the API, you can use the `cdk deploy` command: + +```sh +cdk deploy +``` + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + +Now the API has been deployed and you can start using it! + +### Rename & map models to tables + +To rename models and fields, you can use the `@refersTo` directive to map the models in the GraphQL schema to the corresponding table or field by name. + +By default, the Amplify CLI singularizes each model name using PascalCase and field names that are either snake_case or kebab-case will be converted to camelCase. + +In the example below, the Post model in the GraphQL schema is now mapped to the posts table in the database schema. Also, the `isPublished` is now mapped to the `published` column on the posts table. + +```graphql +type Post @refersTo(name: "posts") @model { + id: String! @primaryKey + title: String! + content: String! + isPublished: Boolean @refersTo(name: "published") + publishedDate: AWSDate @refersTo(name: "published_date") +} +``` + +### Create relationships between models + +You can use the `@hasOne`, `@hasMany`, and `@belongsTo` relational directives to create relationships between models. The field named in the `references` parameter of the relational directives must exist on the child model. + + + +Relationships that query across DynamoDB and SQL data sources are currently not supported. However, you can create relationships across SQL data sources. + + + +Assume that you have `users`, `blogs`, and `posts` tables in your database schema. The following examples demonstrate how you might create different types of relationships between them. Use them as references for creating relationships between the models in your own schema. + +#### Has One relationship + +Create a one-directional one-to-one relationship between two models using the `@hasOne` directive. + +In the example below, a User has a single Blog. + +```graphql +type User + @refersTo(name: "users") + @model + @auth(rules: [{ allow: owner }, { allow: groups, groups: ["Admin"] }]) { + id: String! @primaryKey + name: String! + owner: String + blog: Blog @hasOne(references: ["userId"]) +} +``` + +#### Has Many relationship + +Create a one-directional one-to-many relationship between two models using the `@hasMany` directive. + +In the example below, a Blog has many Posts. + +```graphql +type Blog @model { + id: String! @primaryKey + title: String! + posts: [Post] @hasMany(references: ["blogId"]) +} + +type Post @model { + id: String! @primaryKey + title: String! + content: String! + blogId: String! @refersTo(name: "blog_id") +} +``` + +#### Belongs To relationship + +Make a "has one" or "has many" relationship bi-directional with the `@belongsTo` directive. + +In the example below, a Post belongs to a Blog. + +```graphql +type Post @model { + id: String! @primaryKey + title: String! + content: String! + blogId: String! @refersTo(name: "blog_id") + blog: Blog @belongsTo(references: ["blogId"]) +} +``` + +### Apply iterative changes from the database definition + + + + 1. Make any adjustments to your SQL statements such as: + +```sql +CREATE TABLE posts ( + id varchar(255) NOT NULL PRIMARY KEY, + title varchar(255) NOT NULL, + content varchar(255) NOT NULL, + published tinyint(1) DEFAULT 0 NOT NULL + published_date date NULL +); +``` + +2. Regenerate the database schema as a CSV file by following the instructions in [Generate GraphQL schema from database schema](#step-2---generate-graphql-schema-from-database-schema). + +3. Generate an updated schema by running the following command, replacing the `--engine-type` value with your database engine of `mysql` or `postgres`, and the `--sql-schema` value with the path to the CSV file created in the previous step: + +```sh +npx @aws-amplify/cli api generate-schema --engine-type mysql --sql-schema schema.csv --out schema.sql.graphql +``` + +4. Deploy your changes to the cloud: + +```sh +cdk deploy +``` + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + +## How does it work? + +The Amplify uses AWS Lambda functions to enable features like querying data from your database. To work properly, these Lambda functions need access to common logic and dependencies. + +Amplify provides this shared code in the form of Lambda Layers. You can think of Lambda Layers as a package of reusable runtime code that Lambda functions can reference. + +When you deploy an Amplify API, it will create two Lambda functions: + +### SQL Lambda + +This allows you to query and write data to your database from your API. + + + **NOTE:** If the database is in a VPC, this Lambda function will be deployed + in the same VPC as the database. The usage of Amazon Virtual Private Cloud + (VPC) or VPC peering, with AWS Lambda functions will incur additional charges + as explained, this comes with an additional cost as explained on the [Amazon + Elastic Compute Cloud (EC2) on-demand pricing + page](https://aws.amazon.com/ec2/pricing/on-demand/). + + +### Updater Lambda + +This automatically keeps the SQL Lambda up-to-date by managing its Lambda Layers. + +A Lambda layer that includes all the core SQL connection logic lives within the AWS Amplify service account but is executed within your AWS account, when invoked by the SQL Lambda. This allows the Amplify service team to own the ongoing maintenance and security enhancements of the SQL connection logic. + +This allows the Amplify team to maintain and enhance the SQL Layer without needing direct access to your Lambdas. If updates to the Layer are needed, the Updater Lambda will receive a signal from Amplify and automatically update the SQL Lambda with the latest Layer. + +### Mapping of SQL data types to GraphQL types when auto-generating GraphQL schema + + + +**Note:** MySQL does not support time zone offsets in date time or timestamp fields. Instead, we will convert these values to `datetime`, without the offset. + +Unlike MySQL, PostgreSQL does support date time or timestamp values with an offset. + + + +| SQL | GraphQL | +|--------------------|--------------| +| **String** | | +| char | String | +| varchar | String | +| tinytext | String | +| text | String | +| mediumtext | String | +| longtext | String | +| **Geometry** | | +| geometry | String | +| point | String | +| linestring | String | +| geometryCollection | String | +| **Numeric** | | +| smallint | Int | +| mediumint | Int | +| int | Int | +| integer | Int | +| bigint | Int | +| tinyint | Int | +| float | Float | +| double | Float | +| decimal | Float | +| dec | Float | +| numeric | Float | +| **Date and Time** | | +| date | AWSDate | +| datetime | AWSDateTime | +| timestamp | AWSDateTime | +| time | AWSTime | +| year | Int | +| **Binary** | | +| binary | String | +| varbinary | String | +| tinyblob | String | +| blob | String | +| mediumblob | String | +| longblob | String | +| **Others** | | +| bool | Boolean | +| boolean | Boolean | +| bit | Int | +| json | AWSJSON | +| enum | ENUM | + +### Supported Amplify directives for auto-generated GraphQL schema + +| Name | Supported | Model Level | Field Level | Description | +|--------------|:---------:|:-----------:|:-----------:|-------------| +| `@model` | ✅ | ✅ | ❌ | Creates a datasource and resolver for a table. | +| `@auth` | ✅ | ✅ | ✅ | Allows access to data based on a set of authorization methods and operations. | +| `@primaryKey`| ✅ | ❌ | ✅ | Sets a field to be the primary key. | +| `@index` | ✅ | ❌ | ✅ | Defines an index on a table. | +| `@default` | ✅ | ❌ | ✅ | Sets the default value for a column. | +| `@hasOne` | ✅ | ❌ | ✅ | Defines a one-way 1:1 relationship from a parent to child model. | +| `@hasMany` | ✅ | ❌ | ✅ | Defines a one-way 1:M relationship between two models, the reference being on the child. | +| `@belongsTo` | ✅ | ❌ | ✅ | Defines bi-directional relationship with the parent model. | +| `@manyToMany`| ❌ | ❌ | ❌ | Defines a M:N relationship between two models. | +| `@refersTo` | ✅ | ✅ | ✅ | Maps a model to a table, or a field to a column, by name. | +| `@mapsTo` | ❌ | ❌ | ❌ | Maps a model to a DynamoDB table. | +| `@sql` | ✅ | ❌ | ✅ | Accepts an inline SQL statement or reference to a .sql file to be executed to resolve a Custom Query or Mutation. | + + +## Troubleshooting + +### Debug Mode + +To return the actual SQL error instead of a generic error from GraphQL responses, an environment variable `DEBUG_MODE` can be set to `true` on the Amplify-generated SQL Lambda function. You can find this Lambda function in the AWS Lambda console with the naming convention of: `--SQLLambdaFunction`. + +## Next steps + +Our recommended next steps include using the GraphQL API to mutate and query data on app clients or how to customize the authorization rules for your custom queries and mutations. Some resources that will help with this work include: + +- [Create, update, and delete application data](/[platform]/build-a-backend/graphqlapi/mutate-data/) +- [Read application data](/[platform]/build-a-backend/graphqlapi/query-data/) +- [Customize Authorization Rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx new file mode 100644 index 00000000000..b17dcd5204e --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx @@ -0,0 +1,262 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Incorporate machine learning', + description: 'Add AI/ML capabilities such as text recognition, image labeling, text-to-speech, and translation to your GraphQL API.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/connect-machine-learning-services/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +Amplify allows you to identify text on an image, identify labels on an image, translate text, and synthesize speech from text with the `@predictions` directive. + +> Note: The `@predictions` directive requires a S3 storage bucket configured via `amplify add storage` or set the `predictionsBucket` property when using CDK. + +## Identify text on an image + +To configure text recognition on an image use the `identifyText` action in the `@predictions` directive. + +```graphql +type Query { + recognizeTextFromImage: String @predictions(actions: [identifyText]) +} +``` + +In your GraphQL query, can pass in a S3 `key` for the image. At the moment, this directive works only with objects located within the `public/` folder of your S3 bucket. The `public/` prefix is automatically added to the `key` input. For instance, in the example below, `public/myimage.jpg` will be used as the input. + +```graphql +query RecognizeTextFromImage($input: RecognizeTextFromImageInput!) { + recognizeTextFromImage(input: { identifyText: { key: "myimage.jpg" } }) +} +``` + +## Identify labels on an image + +To configure label recognition on an image use the `identifyLabels` action in the `@predictions` directive. + +```graphql +type Query { + recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) +} +``` + +In your GraphQL query, you can pass in a S3 `key` for the image. At the moment, this directive works only with objects located within `public/` folder in your S3 bucket. The `public/` prefix is automatically added to the `key` input. For instance, in the example below, `public/myimage.jpg` will be used as the input. + +The query below will return a list of identified labels. Review [Detecting Labels](https://docs.aws.amazon.com/rekognition/latest/dg/labels.html) in the Amazon Rekognition documentation for the full list of supported labels. + +```graphql +query RecognizeLabelsFromImage($input: RecognizeLabelsFromImageInput!) { + recognizeLabelsFromImage(input: { identifyLabels: { key: "myimage.jpg" } }) +} +``` + +## Translate text + +To configure text translation use the `identifyLabels` action in the `@predictions` directive. + +```graphql +type Query { + translate: String @predictions(actions: [translateText]) +} +``` + +The query below will return the translated string. Populate the `sourceLanguage` and `targetLanguage` parameters with one of the [Supported Language Codes](https://docs.aws.amazon.com/translate/latest/dg/what-is.html#what-is-languages). Pass in the text to translate via the `text` parameter. + +```graphql +query TranslateText($input: TranslateTextInput!) { + translate( + input: { + translateText: { + sourceLanguage: "en" + targetLanguage: "de" + text: "Translate me" + } + } + ) +} +``` + +## Synthesize speech from text + +To configure Text-to-Speech synthesis use the `convertTextToSpeech` action in the `@predictions` directive. + +```graphql +type Query { + textToSpeech: String @predictions(actions: [convertTextToSpeech]) +} +``` + +The query below will return a presigned URL with the synthesized speech. Populate the `voiceID` parameter with one of the [Supported Voice IDs](https://docs.aws.amazon.com/polly/latest/dg/voicelist.htm). Pass in the text to synthesize via the `text` parameter. + +```graphql +query ConvertTextToSpeech($input: ConvertTextToSpeechInput!) { + textToSpeech( + input: { + convertTextToSpeech: { + voiceID: "Nicole" + text: "Hello from AWS Amplify!" + } + } + ) +} +``` + +## Combining Predictions actions + +You can also combine multiple Predictions actions together into a sequence. The following action sequences are supported: + +- `identifyText -> translateText -> convertTextToSpeech` +- `identifyLabels -> translateText -> convertTextToSpeech` +- `translateText -> convertTextToSpeech` + +In the example below, `speakTranslatedImageText` identifies text from an image, then translates it into another language, and finally converts the translated text to speech. + +```graphql +type Query { + speakTranslatedImageText: String + @predictions(actions: [identifyText, translateText, convertTextToSpeech]) +} +``` + +An example of that query will look like: + +```graphql +query SpeakTranslatedImageText($input: SpeakTranslatedImageTextInput!) { + speakTranslatedImageText( + input: { + identifyText: { key: "myimage.jpg" } + translateText: { sourceLanguage: "en", targetLanguage: "es" } + convertTextToSpeech: { voiceID: "Conchita" } + } + ) +} +``` + +A code example of this using the JS Library is shown below: + +```js +import React, { useState } from 'react'; +import { Amplify } from 'aws-amplify'; +import { uploadData, getUrl } from 'aws-amplify/storage'; +import { generateClient } from 'aws-amplify/api'; +import config from './amplifyconfiguration.json'; +import { speakTranslatedImageText } from './graphql/queries'; + +/* Configure Exports */ +Amplify.configure(config); + +const client = generateClient(); + +function SpeakTranslatedImage() { + const [src, setSrc] = useState(''); + const [img, setImg] = useState(''); + + function putS3Image(event) { + const file = event.target.files[0]; + uploadData({ + key: file.name, + data: file + }) + .result.then(async (result) => { + setSrc(await speakTranslatedImageTextOP(result.key)); + setImg((await getUrl({ key: result.key })).url.toString()); + }) + .catch((err) => console.log(err)); + } + + return ( +
+
+

Upload Image

+ { + putS3Image(event); + }} + /> +
+ {img && } + {src && ( +
+ +
+ )} +
+
+ ); +} + +async function speakTranslatedImageTextOP(key) { + const inputObj = { + translateText: { + sourceLanguage: 'en', + targetLanguage: 'es' + }, + identifyText: { key }, + convertTextToSpeech: { voiceID: 'Conchita' } + }; + const response = await client.graphql({ + query: speakTranslatedImageText, + variables: { input: inputObj } + }); + return response.data.speakTranslatedImageText; +} + +function App() { + return ( +
+

Speak Translated Image

+ +
+ ); +} +export default App; +``` + +## How it works + +Definition of the `@predictions` directive: + +```graphql +directive @predictions(actions: [PredictionsActions!]!) on FIELD_DEFINITION +enum PredictionsActions { + identifyText # uses Amazon Rekognition to detect text + identifyLabels # uses Amazon Rekognition to detect labels + convertTextToSpeech # uses Amazon Polly in a lambda to output a presigned url to synthesized speech + translateText # uses Amazon Translate to translate text from source to target language +} +``` + +`@predictions` creates resources to communicate with Amazon Rekognition, Translate, and Polly. For each action the following is created: + +- IAM Policy for each service (e.g. Amazon Rekognition `detectText` Policy) +- An AppSync VTL function +- An AppSync DataSource + +Finally, a pipeline resolver is created for the query or field. The pipeline resolver is composed of AppSync functions which are defined by the action list provided in the directive. diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx new file mode 100644 index 00000000000..0921819b749 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx @@ -0,0 +1,954 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Set up custom queries and mutations', + description: 'Add authorization rules to your GraphQL schema to control access to your data.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/custom-business-logic/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +Define your custom business logic in a Lambda function resolver, HTTP resolver, or an AppSync JavaScript or VTL resolver and expose them in a GraphQL query or mutation. Extend or override Amplify-generated GraphQL resolvers to optimize for your specific use cases. + +## Create a custom query or mutation + +While `@model` automatically generates dedicated "create", "read", "update", "delete", and "subscription" queries or mutations for you, there are some cases where you want to define a stand-alone query or mutation. + +1. Define your custom query or mutation + +```graphql +type Mutation { + myCustomMutation(args: String): String # your custom mutations here +} + +type Query { + myCustomQuery(args: String): String # your custom queries here +} +``` + +2. Use one of these resolver choices to handle the query or mutation request: + + - [Lambda function resolver](#lambda-function-resolver): use a custom Lambda function to handle query or mutation + - [HTTP resolver](#http-resolver): call an HTTP endpoint upon a query or mutation + - [AppSync JavaScript or VTL resolver](#appsync-javascript-or-vtl-resolver) (most advanced): use AppSync's JavaScript resolver or AppSync's VTL mapping templates to customize the query and mutation logic + +3. Secure your custom query or mutation with [field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) + - Note: Dynamic authorization rules are not supported on a custom query or mutation. + +## Lambda function resolver + +The `@function` directive allows you to quickly & easily configure a AWS Lambda resolvers with your GraphQL API. You can use any AWS Lambda functions that was created with the Amplify CLI, AWS CDK or reference an existing AWS Lambda function created with any other means. + + + + + +For example, use `amplify add function` to add a Lambda function called "echofunction" with the following handler: + +```js +exports.handler = async function (event, context) { + return event.arguments.msg; +}; +``` + +To connect an AWS Lambda resolver to the GraphQL API, add the `@function` directive to a field in your schema. + +```graphql +type Query { + echo(msg: String): String @function(name: "echofunction-${env}") +} +``` + +The Amplify CLI provides support for maintaining multiple environments. When you deploy a function via `amplify add function`, it will automatically add the environment suffix to your Lambda function name. For example, if you create a function named `echofunction` using `amplify add function` in the `dev` environment, the deployed function will be named `echofunction-dev`. The `@function` directive allows you to use `${env}` to reference the current Amplify CLI environment. + + + + + +First, create your Lambda function in CDK with your logic and set the `functionName` parameter. + +```ts +const echoLambda = new lambda.Function(this, 'EchoLambda', { + functionName: 'echofunction', // MAKE SURE THIS MATCHES THE @function's "name" PARAMETER + code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_18_X +}); + +const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); +``` + +To connect an AWS Lambda resolver to the GraphQL API, add the `@function` directive to a field in your GraphQL schema. + +```graphql +type Query { + echo(msg: String): String @function(name: "echofunction") +} +``` + +Optionally, if you don't want to hard-code the function name into the GraphQL schema, you can also set an arbitrary name in the GraphQL schema and then map a function within CDK to the function name. + +```ts +const coolLambdaFunction = new lambda.Function(this, 'MyCoolLambdaFunction', { + code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_18_X +}); + +const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + }, + functionNameMap: { + echofunction: coolLambdaFunction // Remap the function name to any function you define or reference within CDK. + } +}); +``` + + + + + +If you deployed your Lambda function without Amplify CLI then you must provide the full Lambda function name in the `name` parameter. If you deployed the same function with the name echofunction then you would have: + +```graphql +type Query { + echo(msg: String): String @function(name: "echofunction") +} +``` + + + + + +### Structure of the function event + +When writing Lambda functions that are connected via the `@function` directive, you can expect the following structure for the AWS Lambda `event` object. + +| Key | Description | +| --- | --- | +| typeName | The name of the parent object type of the field being resolved. | +| fieldName | The name of the field being resolved. | +| arguments | A map containing the arguments passed to the field being resolved. | +| identity | A map containing identity information for the request. Contains a nested key 'claims' that will contains the JWT claims if they exist. | +| source | When resolving a nested field in a query, the source contains parent value at runtime. For example when resolving `Post.comments`, the source will be the Post object. | +| request | The AppSync request object. Contains header information. | +| prev | When using pipeline resolvers, this contains the object returned by the previous function. You can return the previous value for auditing use cases. | + +Your function should follow the Lambda handler guidelines for your specific language. See the developer guides from the [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) documentation for your chosen language. If you choose to use structured types, your type should serialize the AWS Lambda event object outlined above. For example, if using Golang, you should define a struct with the above fields. + +### Calling functions in different regions + +By default, you expect the function to be in the same region as the Amplify project. If you need to call a function in a different or a specific region, you can provide the **region** argument. + +```graphql +type Query { + echo(msg: String): String @function(name: "echofunction", region: "us-east-1") +} +``` + +Calling functions in different AWS accounts is not supported via the `@function` directive but is supported by AWS AppSync. + +### Chaining functions + +You can chain together multiple `@function` resolvers such that they are invoked in series when your field's resolver is invoked. To create a pipeline resolver that calls to multiple AWS Lambda functions in series, use multiple `@function` directives on the field. Similarly, `@function` can be combined with field-level `@auth`. When combining these field directives, the ordering in the schema matches the ordering in the pipeline resolver. You can choose to have functions before and/or after field level authorization is applied. + +> **Note:** Be careful when using @auth directives on a field in a root type. @auth directives on field definitions use the source object to perform authorization logic and the source will be an empty object for fields on root types. Static group authorization should perform as expected. + +```graphql +type Mutation { + doSomeWork(msg: String): String + @function(name: "worker-function") + @function(name: "audit-function") +} +``` + +In the example above when you run a mutation that calls the `Mutation.doSomeWork` field, the **worker-function** will be invoked first then the **audit-function** will be invoked with an event that contains the results of the **worker-function** under the **event.prev.result** key. The **audit-function** would need to return **event.prev.result** if you want the result of **worker-function** to be returned for the field. + +### How it works + +Definition of `@function` directive: + +```graphql +directive @function(name: String!, region: String) on FIELD_DEFINITION +``` + +Under the hood, Amplify creates an `AppSync::FunctionConfiguration` for each unique instance of `@function` in a document and a pipeline resolver containing a pointer to a function for each `@function` on a given field. + +The `@function` directive generates these resources as necessary: + +1. An AWS IAM role that has permission to invoke the function as well as a trust policy with AWS AppSync. +2. An AWS AppSync data source that registers the new role and existing function with your AppSync API. +3. An AWS AppSync pipeline function that prepares the lambda event and invokes the new data source. +4. An AWS AppSync resolver that attaches to the GraphQL field and invokes the new pipeline functions. + +## HTTP resolver + +The `@http` directive allows you to quickly configure HTTP resolvers within your GraphQL API. + +To connect to an endpoint, add the @http directive to a field in your GraphQL schema. The directive allows you to define URL path parameters, and specify a query string and/or specify a request body. For example, given the definition of a Post type: + +```graphql +type Post { + id: ID! + title: String + description: String + views: Int +} + +type Query { + listPosts: [Post] @http(url: "https://www.example.com/posts") +} +``` + +Amplify generates the definition below that sends a request to the url when the listPosts query is used. + +```graphql +type Query { + listPosts: [Post] +} +``` + +### Request headers + +The `@http` directive generates resolvers that can handle XML and JSON responses. If an HTTP method is not defined, `GET` is used. You can specify a list of static headers to be passed with the HTTP requests to your backend in your directive definition. + +```graphql +type Query { + listPosts: [Post] + @http( + url: "https://www.example.com/posts" + headers: [{ key: "X-Header", value: "X-Header-Value" }] + ) +} +``` + +### Path parameters + +You can create dynamic paths by specifying parameters in the directive URL by using the special `:` notation. Your set of parameters can then be specified in the params input object of the query. Note that path parameters are not added to the request body or query string. You can define multiple parameters. + +```graphql +type Query { + getPost: Post @http(url: "https://www.example.com/posts/:id") +} +``` + +In the example above, the `:id` parameter will generate the appropriate query input as shown below: + +```graphql +type Query { + getPost(params: QueryGetPostParamsInput!): Post +} + +input QueryGetPostParamsInput { + id: String! +} +``` + +You can fetch a specific post by enclosing the id in the params input object. + +```graphql +query post { + getPost(params: { id: "POST_ID" }) { + id + title + } +} +``` + +This executes the following request: + +```console +GET /posts/POST_ID +Host: www.example.com +``` + +### Query String + +You can send a query string with your request by specifying variables for your query. The query string is supported with all request methods. + +Given the definition + +```graphql +type Query { + listPosts(sort: String!, from: String!, limit: Int!): Post + @http(url: "https://www.example.com/posts") +} +``` + +Amplify generates + +```graphql +type Query { + listPosts(query: QueryListPostsQueryInput!): [Post] +} + +input QueryListPostsQueryInput { + sort: String! + from: String! + limit: Int! +} +``` + +You can query for posts using the `query` input object + +```graphql +query posts { + listPosts(query: { sort: "DESC", from: "last-week", limit: 5 }) { + id + title + description + } +} +``` + +which sends the following request: + +```text +GET /posts?sort=DESC&from=last-week&limit=5 +Host: www.example.com +``` + +### Request Body + +The `@http` directive also allows you to specify the body of a request, which is used for `POST`, `PUT`, and `PATCH` requests. To create a new post, you can define the following. + +```graphql +type Mutation { + addPost(title: String!, description: String!, views: Int): Post + @http(method: POST, url: "https://www.example.com/post") +} +``` + +Amplify generates the `addPost` query field with the `query` and `body` input objects since this type of request also supports a query string. The generated resolver verifies that non-null arguments (e.g.: the `title` and `description`) are passed in at least one of the input objects; if not, an error is returned. + +```graphql +type Mutation { + addPost(query: QueryAddPostQueryInput, body: QueryAddPostBodyInput): Post +} + +input QueryAddPostQueryInput { + title: String + description: String + views: Int +} + +input QueryAddPostBodyInput { + title: String + description: String + views: Int +} +``` + +You can add a post by using the `body` input object: + +```graphql +mutation add { + addPost(body: { title: "new post", description: "fresh content" }) { + id + } +} +``` + +which will send + +```text +POST /post +Host: www.example.com +{ + title: "new post" + description: "fresh content" +} +``` + +### Reference Amplify environment name + +The `@http` directive allows you to use `${env}` to reference the current Amplify CLI environment. + +```graphql +type Query { + listPosts: Post @http(url: "https://www.example.com/${env}/posts") +} +``` + +which, in the `DEV` environment, will send + +```text +GET /DEV/posts +Host: www.example.com +``` + +**Combining the different components** + +You can use a combination of parameters, query, body, headers, and environments in your `@http` directive definition. + +Given the definition + +```graphql +type Post { + id: ID! + title: String + description: String + views: Int + comments: [Comment] +} + +type Comment { + id: ID! + content: String +} + +type Mutation { + updatePost( + title: String! + description: String! + views: Int + withComments: Boolean + ): Post + @http( + method: PUT + url: "https://www.example.com/${env}/posts/:id" + headers: [{ key: "X-Header", value: "X-Header-Value" }] + ) +} +``` + +you can update a post with + +```graphql +mutation update { + updatePost( + body: { title: "new title", description: "updated description", views: 100 } + params: { id: "EXISTING_ID" } + query: { withComments: true } + ) { + id + title + description + comments { + id + content + } + } +} +``` + +which, in the `DEV` environment, will send + +```text +PUT /DEV/posts/EXISTING_ID?withComments=true +Host: www.example.com +X-Header: X-Header-Value +{ + title: "new title" + description: "updated description" + views: 100 +} +``` + +### Reference existing field data + +In some cases, you may want to send a request based on existing field data. Take a scenario where you have a post and want to fetch comments associated with the post in a single query. Let's use the previous definition of `Post` and `Comment`. + +```graphql +type Post { + id: ID! + title: String + description: String + views: Int + comments: [Comment] +} + +type Comment { + id: ID! + content: String +} +``` + +A post can be fetched at `/posts/:id` and a post's comments at `/posts/:id/comments`. You can fetch the comments based on the post id with the following updated definition. `$ctx.source` is a map that contains the resolution of the parent field (`Post`) and gives access to `id`. + +```graphql +type Post { + id: ID! + title: String + description: String + views: Int + comments: [Comment] + @http(url: "https://www.example.com/posts/${ctx.source.id}/comments") +} + +type Comment { + id: ID! + content: String +} + +type Query { + getPost: Post @http(url: "https://www.example.com/posts/:id") +} +``` + +You can retrieve the comments of a specific post with the following query and selection set. + +```graphql +query post { + getPost(params: { id: "POST_ID" }) { + id + title + description + comments { + id + content + } + } +} +``` + +Assuming that `getPost` retrieves a post with the id `POST_ID`, the comments field is resolved by sending this request to the endpoint + +```text +GET /posts/POST_ID/comments +Host: www.example.com +``` + +Note that there is no check to ensure that the reference variable (here the post ID) exists. When using this technique, it is recommended to make sure the referenced field is non-null. + +### How it works + +Definition of `@http` directive: + +```graphql +directive @http( + method: HttpMethod + url: String! + headers: [HttpHeader] +) on FIELD_DEFINITION +enum HttpMethod { + PUT + POST + GET + DELETE + PATCH +} +input HttpHeader { + key: String + value: String +} +``` + +The `@http` transformer will create one HTTP datasource for each identified base URL. For example, if multiple HTTP resolvers are created that interact with the "https://www.example.com" endpoint, only a single datasource is created. Each directive generates one resolver. Depending on the definition, the appropriate `body`, `params`, and `query` input types are created. Note that `@http` transformer does not support calling other AWS services where Signature Version 4 signing process is required. + +## AppSync JavaScript or VTL resolver + +You can use AWS Cloud Development Kit (CDK) to define custom AppSync resolvers for your GraphQL API. `@auth` directives are not supported for custom queries or mutations that are connected to a JavaScript or VTL resolver. This is because you are replacing Amplify's auto-generated capabilities for that particular query or mutation with a custom-defined cloud resources. + +```bash +amplify add custom +``` + +```console +? How do you want to define this custom resource? +❯ AWS CDK +? Provide a name for your custom resource +❯ MyCustomResolvers +``` + +Next, install the AppSync dependencies for your custom resource: + +```bash +cd amplify/backend/custom/MyCustomResolvers +npm i @aws-cdk/aws-appsync@~1.172.0 +``` + +> **Note:** Installations using the '\~' character do not modify the package.json. To use '\~' for default npm configurations, make sure your package.json reflects the right dependency to avoid compatibility errors in CDK. + +Finally, add your custom resolvers into the `cdk-stack.ts` file. You can either add the JavaScript or VTL inline into your `cdk-stack.ts` file. + +#### Unit Resolver + + + + + +Review the [AppSync JavaScript resolver tutorial](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) for JavaScript resolver examples for different data sources. + +```ts +import * as cdk from 'aws-cdk-lib'; +import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; +import * as appsync from 'aws-cdk-lib/aws-appsync'; +import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; +import { Construct } from 'constructs'; + +const jsResolverTemplate = ` +export function request(ctx) { + return { + payload: null + } +} + +export function response(ctx) { + return ctx.arguments.message +} +` + +export class cdkStack extends cdk.Stack { + constructor( + scope: Construct, + id: string, + props?: cdk.StackProps, + amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps + ) { + super(scope, id, props); + /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ + new cdk.CfnParameter(this, 'env', { + type: 'String', + description: 'Current Amplify CLI env name' + }); + + // Access other Amplify Resources + const retVal: AmplifyDependentResourcesAttributes = + AmplifyHelpers.addResourceDependency( + this, + amplifyResourceProps.category, + amplifyResourceProps.resourceName, + [ + { + category: 'api', + resourceName: '' + } + ] + ); + + const resolver = new appsync.CfnResolver(this, 'CustomResolver', { + // apiId: retVal.api.new.GraphQLAPIIdOutput, + // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 + // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. + // Previously the ApiId is the variable Name which is wrong , it should be variable value as below + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + fieldName: 'echo', + typeName: 'Query', // Query | Mutation | Subscription + code: jsResolverTemplate, + dataSourceName: 'NONE_DS', // DataSource name + runtime: { + name: 'APPSYNC_JS', + runtimeVersion: '1.0.0' + } + }); + } +} +``` + + + + + +Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. + +```ts +import * as cdk from 'aws-cdk-lib'; +import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; +import * as appsync from 'aws-cdk-lib/aws-appsync'; +import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; +import { Construct } from 'constructs'; + +const requestVTL = ` + +`; +const responseVTL = ` + +`; + +export class cdkStack extends cdk.Stack { + constructor( + scope: Construct, + id: string, + props?: cdk.StackProps, + amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps + ) { + super(scope, id, props); + /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ + new cdk.CfnParameter(this, 'env', { + type: 'String', + description: 'Current Amplify CLI env name' + }); + + // Access other Amplify Resources + const retVal: AmplifyDependentResourcesAttributes = + AmplifyHelpers.addResourceDependency( + this, + amplifyResourceProps.category, + amplifyResourceProps.resourceName, + [ + { + category: 'api', + resourceName: '' + } + ] + ); + + const resolver = new appsync.CfnResolver(this, 'custom-resolver', { + // apiId: retVal.api.new.GraphQLAPIIdOutput, + // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 + // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. + // Previously the ApiId is the variable Name which is wrong , it should be variable value as below + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + fieldName: 'querySomething', + typeName: 'Query', // Query | Mutation | Subscription + requestMappingTemplate: requestVTL, + responseMappingTemplate: responseVTL, + dataSourceName: 'TodoTable' // DataSource name + }); + } +} +``` + +#### Pipeline Resolver + +```ts +import * as cdk from 'aws-cdk-lib'; +import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; +import * as appsync from 'aws-cdk-lib/aws-appsync'; +import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; +import { Construct } from 'constructs'; + +const beforeMappingVTL = ` + +`; +const afterMappingVTL = ` + +`; +const function1requestVTL = ` + +`; +const function1responseVTL = ` + +`; +const function2requestVTL = ` + +`; +const function2responseVTL = ` + +`; +export class cdkStack extends cdk.Stack { + constructor( + scope: Construct, + id: string, + props?: cdk.StackProps, + amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps + ) { + super(scope, id, props); + /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ + new cdk.CfnParameter(this, 'env', { + type: 'String', + description: 'Current Amplify CLI env name' + }); + + // Access other Amplify Resources + const retVal: AmplifyDependentResourcesAttributes = + AmplifyHelpers.addResourceDependency( + this, + amplifyResourceProps.category, + amplifyResourceProps.resourceName, + [ + { + category: 'api', + resourceName: '' + } + ] + ); + + const function1 = new appsync.CfnFunctionConfiguration(this, 'function1', { + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + dataSourceName: 'NONE_DS', // DataSource name + functionVersion: '2018-05-29', + name: 'function1', + requestMappingTemplate: function1requestVTL, + responseMappingTemplate: function1responseVTL + }); + + const function2 = new appsync.CfnFunctionConfiguration(this, 'function2', { + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + dataSourceName: 'TodoTable', // DataSource name + functionVersion: '2018-05-29', + name: 'function2', + requestMappingTemplate: function2requestVTL, + responseMappingTemplate: function2responseVTL + }); + + const resolver = new appsync.CfnResolver(this, 'pipeline-resolver', { + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + fieldName: 'querySomething', + typeName: 'Query', // Query | Mutation | Subscription + kind: 'PIPELINE', + pipelineConfig: { + functions: [function1.attrFunctionId, function2.attrFunctionId] + }, + requestMappingTemplate: beforeMappingVTL, + responseMappingTemplate: afterMappingVTL + }); + } +} +``` + +> **Note:** Users moving from ElasticSearch to OpenSearch will need to change the datasource name from `ElasticSearchDomain` to `OpenSearchDataSource` if the upgrade process changes the source name. For new @searchable models the datasource name will default to `OpenSearchDataSource`. + +You can alternatively define the VTL templates in another file such as `Query.querySomething.req.vtl` or `Query.querySomething.res.vtl` in `amplify/backend/custom/MyCustomResolvers/`. Then use the following code snippets to retrieve them: + +```ts +requestMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.req.vtl")).renderTemplate(), +responseMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.res.vtl")).renderTemplate(), +``` + +> **Note:** the `..` is added to the path because the path is always relative to the `build` folder of the custom resource. + + + + + +## Add authorization rules to custom queries and mutations + +Authorization rules can be applied with the `@auth` directive in the same way as field-level authorization rules. See [Field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules) for details. + +In the example below, `myCustomMutation` can only be executed by signed-in customers who are authenticated with IAM: + +```graphql +type Mutation { + myCustomMutation(args: String): String + @auth(rules: [{ allow: private, provider: iam }]) +} +``` + +> **Known limitation:** You can't combine the `@auth` directive with a custom query or mutation that is using a VTL resolver. + +## Override Amplify-generated resolvers + +Amplify generates [AWS AppSync pipeline resolver](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) for your queries and mutations. The resolvers are listed the following API resource's folder `amplify/backend/api//build/resolvers/`. + +To override an Amplify-generated resolver: + +1. Find the resolver file name you want to override under `build/resolvers` +2. Place a `.vtl` with the same file name the resource's `resolvers/` (not under `build/`) +3. Upon the next `amplify api gql-compile` or `amplify push` the Amplify-generated resolver file will be replaced with your overwritten resolver file + +```console +amplify/backend/api/ +├── build +│   ├── ... +│   ├── resolvers +│   │   ├── ... +│   │   ├── Query.searchTodos.req.vtl # Find resolver file name +│   │   └── ... +| ... +├── resolvers +│   └── Query.searchTodos.req.vtl # Place resolver overrides with the same file name here +``` + +The example above shows how the `Query.searchTodos.req.vtl` is overwritten with a custom resolver. Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. + +## Extend Amplify-generated resolvers + +Amplify generates [AWS AppSync pipeline resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) for your queries and mutations. You can "slot" in your custom business logic between Amplify-generated resolvers. You can find Amplify-generated resolvers under your API resources' `build/resolvers/` folder. The resolver functions file name determines its placement within the slot sequence. + +```console +File name convention: + [Query|Mutation|Subscription].[field name].[slot name].[slot placement].[req|res].vtl +Example: + Mutation.createTodo.postAuth.1.req.vtl +``` + +To extend an Amplify-generated resolver: + +1. Find the [resolver slot](#supported-resolver-slots) you want to add your custom business logic to +2. Place a `.vtl` file with the correct the file naming convention into `resolvers/` (not under `build/`) +3. Upon the next `amplify api gql-compile` or `amplify push` the Amplify-generated resolver file will be replaced within the desired slot within the resolver sequence. + +```console +amplify/backend/api/ +├── build +│   ├── ... +│   ├── resolvers +│   │   ├── ... +│   │   ├── Mutation.createTodo.postAuth.1.req.vtl # Amplify-generated resolvers +│   │   └── ... +| ... +├── resolvers +│   └── Mutation.createTodo.postAuth.2.req.vtl # Custom resolver slotted in after postAuth.1 resolver +``` + +For example, the a resolver function file named `Mutation.createTodo.postAuth.2.req.vtl` will be slotted in right after the `Mutation.createTodo.postAuth.1.req.vtl` resolver. Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. + +### Supported resolver slots + +#### Query + +| Sequence | Slot name | Description | +| --- | --- | --- | +| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | +| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | +| 3 | auth | Resolvers that implement authorization rule checks. | +| 4 | postAuth | Resolvers that are run after authorization rule checks. | +| 5 | preDataLoad | Resolvers to configure values to make a request to the data source. | +| 6 | postDataLoad | Resolvers for post-processing after request to data source. | +| 7 | finish | Final set of resolvers before response is returned to client. Typically used for clean-up. | + +#### Mutation + +| Sequence | Slot name | Description | +| --- | --- | --- | +| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | +| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | +| 3 | auth | Resolvers that implement authorization rule checks. | +| 4 | postAuth | Resolvers that are run after authorization rule checks. | +| 5 | preUpdate | Resolvers to configure values to make a request to the data source. | +| 6 | postUpdate | Resolvers for post-processing after request to data source. | +| 7 | finish | Final set of resolvers before response is returned to client. Typically used for clean-up. | + +#### Subscription + +| Sequence | Slot name | Description | +| --- | --- | --- | +| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | +| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | +| 3 | auth | Resolvers that implement authorization rule checks. | +| 4 | postAuth | Resolvers that are run after authorization rule checks. | +| 5 | preSubscribe | Resolver slot that executes after auth but before the subscription returns | diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx new file mode 100644 index 00000000000..c6dbad3c5f8 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx @@ -0,0 +1,49 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure authorization modes', + description: + "Learn more about how to configure authorization modes in Amplify's API category", + platforms: ['flutter'] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import ios0 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; + + + +import android1 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; + + + +import js2 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; + + + +import reactnative0 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; + + + +import flutter3 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; + + diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx new file mode 100644 index 00000000000..acd39039b98 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx @@ -0,0 +1,933 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Customize authorization rules', + description: 'Add authorization rules to your GraphQL schema to control access to your data.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/swift/build-a-backend/graphqlapi/customize-authorization-rules/' + }, + { + platforms: [ + 'angular', + 'nextjs', + 'javascript', + 'react', + 'vue', + 'react-native' + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/customize-authorization-rules/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +Use the `@auth` directive to configure authorization rules for public, sign-in user, per user, and per user group data access. **Authorization rules operate on the deny-by-default principle**. Meaning that if an authorization rule is not specifically configured, it is denied. + +```graphql +type Todo @model @auth(rules: [{ allow: owner }]) { + content: String +} +``` + +In the example above, each signed-in user, or also known as "owner", of a Todo can create, read, update, and delete their own Todos. + +Amplify also allows you to restrict the allowed operations, combine multiple authorization rules, and apply fine-grained field-level authorization. + +```graphql +type Todo + @model + @auth(rules: [{ allow: public, operations: [read] }, { allow: owner }]) { + content: String +} +``` + +In the example above, everyone (`public`) can read every Todo but owner (authenticated users) can create, read, update, and delete their own Todos. + +### Global authorization rule (only for getting started) + +To help you get started, there's a global authorization rule defined when you create a new GraphQL schema. For production environments, remove the global authorization rule and apply rules on each model instead. + + + + + +```graphql +input AMPLIFY { + globalAuthRule: AuthRule = { allow: public } +} +``` + + + + +In the CDK construct, we call this the "sandbox mode" that you need to explicitly enable via an input parameter. + +```ts +new AmplifyGraphqlApi(this, "MyNewApi", { + ..., + translationBehavior: { + sandboxModeEnabled: true + } +}); +``` + + + + + +The global authorization rule (in this case `{ allow: public }` - allows anyone to create, read, update, and delete) is applied to every data model in the GraphQL schema. + +**Note:** Amplify will always use the most specific authorization rule that's present. For example, a field-level authorization rule will be used in favor of a model-level authorization rule; similarly, a model-level authorization rule will be used in favor of a global authorization rule. + + + +Currently, only `{ allow: public }` is supported as a global authorization rule. + + + +## Authorization strategies + +Use the guide below to select the correct authorization strategy for your use case: + +| **Recommended use case** | **Strategy** | **Provider** | +| --- | --- | --- | +| Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access. | [`public`](#public-data-access) | `apiKey` | +| Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using AWS IAM controls. | [`public`](#public-data-access) | `iam` (or `identityPool` when using CDK construct) | +| Per user data access. Access is restricted to the "owner" of a record. Leverages `amplify add auth` Cognito user pool by default. | [`owner`](#per-user--owner-based-data-access) | `userPools` / `oidc` | +| Any signed-in data access. Unlike owner-based access, **any** signed-in user has access. | [`private`](#signed-in-user-data-access) | `userPools` / `oidc` / `iam` | +| Per user group data access. A specific or dynamically configured group of users have access | [`groups`](#user-group-based-data-access) | `userPools` / `oidc` | +| Define your own custom authorization rule within a Lambda function | [`custom`](#custom-authorization-rule) | `function` | + +### Public data access + +To grant everyone access, use the `public` authorization strategy. Behind the scenes, the API will be protected with an API Key. + +```graphql +type Todo @model @auth(rules: [{ allow: public }]) { + content: String +} +``` + +You can also override the authorization provider. In the example below, you can use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API Key. + + + + +When you run `amplify add auth`, the Amplify CLI generates scoped down IAM policies for the "Unauthenticated role" in Cognito identity pool automatically. + +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: public, provider: iam }]) { + id: ID! + title: String! +} +``` + + + + +Designate an Amazon Cognito identity pool's role for unauthenticated identities by setting the `identityPoolConfig` property: + +```ts +// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well +import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha'; + +const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', { + allowUnauthenticatedIdentities: true, + authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({ + userPool: , + userPoolClient: , + })] }, +}); + +new AmplifyGraphqlApi(this, "MyNewApi", { + definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + }, + identityPoolConfig: { + identityPoolId: identityPool.identityPoolId, + authenticatedUserRole: identityPool.authenticatedRole, + unauthenticatedUserRole: identityPool.unauthenticatedRole, + } + }, +}) +``` + +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: public, provider: identityPool }]) { + id: ID! + title: String! +} +``` + + + +In the Amplify Library's client configuration file (`amplifyconfiguration.json`) set `allowGuestAccess` to `true`. This lets the Amplify Library use the unauthenticated role from your Cognito identity pool when your user isn't logged in. + +```json +{ + "Auth": { + "Cognito": { + "userPoolId": "YOUR_USER_POOL_ID", + "userPoolClientId": "YOUR_USER_POOL_CLIENT_ID", + "identityPoolId": "YOUR_IDENTITY_POOL_ID", + "allowGuestAccess": true + }, + }, + "API": { + "GraphQL": { + "endpoint": "YOUR_API_ENDPOINT", + "region": "YOUR_API_REGION", + "defaultAuthMode": "YOUR_DEFAULT_AUTHORIZATION_MODE", + }, + }, +} +``` + + + + + + + + +### Per-user / owner-based data access + +To restrict a record's access to a specific user, use the `owner` authorization strategy. When `owner` authorization is configured, only the record's `owner` is allowed the specified operations. + +```graphql +# The "owner" of a Todo is allowed to create, read, update, and delete their own todos +type Todo @model @auth(rules: [{ allow: owner }]) { + content: String +} + +# The "owner" of a Todo record is only allowed to create, read, and update it. +# The "owner" of a Todo record is denied to delete it. +type Todo + @model + @auth(rules: [{ allow: owner, operations: [create, read, update] }]) { + content: String +} +``` + +Behind the scenes, Amplify will automatically add a `owner: String` field to each record which contains the record owner's identity information upon record creation. + +By default, the Cognito user pool's user information is populated into the `owner` field. The value saved includes `sub` and `username` in the format `::`. The API will authorize against the full value of `::` or `sub` / `username` separately and return `username`. You can alternatively configure [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). + +You can override the `owner` field to your own preferred field, by specifying a custom `ownerField` in the authorization rule. + + + +Do not set `ownerField` to your `@primaryKey` field or `id` field if no primary key is specified. If you want to query by the `ownerField`, use an `@index` on that `ownerField` to create a secondary index. + + + +```graphql +type Todo @model @auth(rules: [{ allow: owner, ownerField: "author" }]) { + content: String #^^^^^^^^^^^^^^^^^^^^ + author: String # record owner information now stored in "author" field +} +``` + + + +**By default, owners can reassign the owner of their existing record to another user.** + +To prevent an owner from reassigning their record to another user, protect the owner field (by default `owner: String`) with a [field-level authorization rule](#field-level-authorization-rules). For example, in a social media app, you would want to prevent Alice from being able to reassign Alice's Post to Bob. + +```graphql +type Todo @model @auth(rules: [{ allow: owner }]) { + id: ID! + description: String + owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }]) +} +``` + + + +### Multi-user data access + +If you want to grant a set of users access to a record, you can override the `ownerField` to a list of owners. Use this if you want a dynamic set of users to have access to a record. + +```graphql +type Todo @model @auth(rules: [{ allow: owner, ownerField: "authors" }]) { + content: String + authors: [String] +} +``` + +In the example above, upon record creation, the `authors` list is populated with the creator of the record. The creator can then update the `authors` field with additional users. Any user listed in the `authors` field can access the record. + +### Signed-in user data access + +To restrict a record's access to every signed-in user, use the `private` authorization strategy. + +> If you want to restrict a record's access to a specific user, see [Per-user / owner-based data access](#per-user--owner-based-data-access). `private` authorization applies the authorization rule to **every** signed-in user access. + +```graphql +type Todo @model @auth(rules: [{ allow: private }]) { + content: String +} +``` + +In the example above, anyone with a valid JWT token from Cognito user pool are allowed to access all Todos. + +You can also override the authorization provider. In the example below, you can use an "Authenticated Role" from the Cognito identity pool for granting access to signed-in users. + + + + +When you run `amplify add auth`, the Amplify CLI generates scoped down IAM policies for the "Authenticated role" in Cognito identity pool automatically. + +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: private, provider: iam }]) { + id: ID! + title: String! +} +``` + + + +Designate an Amazon Cognito identity pool role for authenticated identities by setting the `identityPoolConfig` property: + +```ts +// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well +import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha'; + +const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', { + allowUnauthenticatedIdentities: true, + authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({ + userPool: , + userPoolClient: , + })] }, +}); + +new AmplifyGraphqlApi(this, "MyNewApi", { + definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + }, + identityPoolConfig: { + identityPoolId: identityPool.identityPoolId, + authenticatedUserRole: identityPool.authenticatedRole, + unauthenticatedUserRole: identityPool.unauthenticatedRole, + } + }, +}) +``` + +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: private, provider: identityPool }]) { + id: ID! + title: String! +} +``` + + + + + +In addition, you can also use OpenID Connect with `private` authorization. See [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). + +**Note:** If you have a connected child model that allows `private` level access, any user authorized to fetch it from the parent model will be able to read the connected child model. For example, + +```graphql +type Todo @model @auth(rules: [{ allow: owner }]) { + id: ID! + name: String! + task: [Task] @hasMany +} + +type Task + @model + @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) { + id: ID! + description: String! +} +``` + +In the above relationship, the owner of a `Todo` record can query all the tasks connected to it, since the `Task` model allows `private` read access. + +### User group-based data access + +To restrict access based on user groups, use the `group` authorization strategy. + +**Static group authorization**: When you want to restrict access to a specific set of user groups, provide the group names in the `groups` parameter. + +```graphql +type Salary @model @auth(rules: [{ allow: groups, groups: ["Admin"] }]) { + id: ID! + wage: Int + currency: String +} +``` + +In the example above, only users that are part of the "Admin" user group are granted access to the Salary model. + + +**Dynamic group authorization**: When you want to restrict access to a set of user groups. + +```graphql +# Dynamic group authorization with multiple groups +type Post @model @auth(rules: [{ allow: groups, groupsField: "groups" }]) { + id: ID! + title: String + groups: [String] +} + +# Dynamic group authorization with a single group +type Post @model @auth(rules: [{ allow: groups, groupsField: "group" }]) { + id: ID! + title: String + group: String +} +``` + +With dynamic group authorization, each record contains an attribute specifying what Cognito groups should be able to access it. Use the `groupsField` argument to specify which attribute in the underlying data store holds this group information. To specify that a single group should have access, use a field of type `String`. To specify that multiple groups should have access, use a field of type `[String]`. + +By default, `group` authorization leverages Amazon Cognito user pool groups but you can also use OpenID Connect with `group` authorization. See [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). + +**Known limitations for real-time subscriptions when using dynamic group authorization**: + +1. If you authorize based on a single group per record, then subscriptions are only supported if the user is part of 5 or fewer user groups +2. If you authorize via an array of groups (`groups: [String]` example above), + +- subscriptions are only supported if the user is part of 20 or fewer groups +- you can only authorize 20 or fewer user groups per record + + +### Custom authorization rule + +You can define your own custom authorization rule with a Lambda function. + +```graphql +type Salary @model @auth(rules: [{ allow: custom }]) { + id: ID! + wage: Int + currency: String +} +``` + +The Lambda function of choice will receive an authorization token from the client and execute the desired authorization logic. The AppSync GraphQL API will receive a payload from Lambda after invocation to allow or deny the API call accordingly. + + + + +Configure the GraphQL API with the Lambda authorization mode, run the following command in your Terminal: + +```bash +amplify update api +``` + +``` +? Select a setting to edit: +> Authorization modes + +> Lambda + +? Choose a Lambda source: +> Create a new Lambda function +``` + + + + +To configure a Lambda function as the authorization mode, set the `lambdaConfig` in the CDK construct. Use the `ttl` to designate the toke expiry time. + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'AWS_LAMBDA', + lambdaConfig: { + function: new lambda.Function(this, 'MyAuthLambda', { + code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/auth')), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_18_X + }), + ttl: cdk.Duration.seconds(10) + } + } +}); +``` + +You can leverage this Lambda function code template as a starting point to author your authorization handler code: + +```js +// This is sample code. Please update this to suite your schema + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +exports.handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + const { + authorizationToken, + requestContext: { apiId, accountId } + } = event; + const response = { + isAuthorized: authorizationToken === 'custom-authorized', + resolverContext: { + // eslint-disable-next-line spellcheck/spell-checker + userid: 'user-id', + info: 'contextual information A', + more_info: 'contextual information B' + }, + deniedFields: [ + `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`, + `Mutation.createEvent` + ], + ttlOverride: 300 + }; + console.log(`response >`, JSON.stringify(response, null, 2)); + return response; +}; +``` + + + + +You can use the default Amplify provided template as a starting point for your custom authorization rule. The authorization Lambda function receives: + +```json +{ + "authorizationToken": "ExampleAuthToken123123123", # Authorization token specified by client + "requestContext": { + "apiId": "aaaaaa123123123example123", # AppSync API ID + "accountId": "111122223333", # AWS Account ID + "requestId": "f4081827-1111-4444-5555-5cf4695f339f", + "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", # GraphQL query + "operationName": "MyQuery", # GraphQL operation name + "variables": {} # any additional variables supplied to the operation + } +} +``` + +Your Lambda authorization function needs to return the following JSON: + +```json +{ + // required + "isAuthorized": true, // if "false" then an UnauthorizedException is raised, access is denied + "resolverContext": { "banana": "very yellow" }, // JSON object visible as $ctx.identity.resolverContext in VTL resolver templates + + // optional + "deniedFields": ["TypeName.FieldName"], // Forces the fields to "null" when returned to the client + "ttlOverride": 10 // The number of seconds that the response should be cached for. Overrides default specified in "amplify update api" +} +``` + +Review the Amplify Library documentation to set the custom authorization token for [GraphQL API](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules#aws-lambda) and [DataStore](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/#configure-custom-authorization-logic-with-aws-lambda). + +## Configure multiple authorization rules + +When combining multiple authorization rules, they are "logically OR"-ed. + +```graphql +type Post + @model + @auth( + rules: [ + { allow: public, operations: [read], provider: iam } + { allow: owner } + ] + ) { + title: String + content: String +} +``` + +```js +import { createPost } from './graphql/mutations'; +import { listPosts } from './graphql/queries'; + +// Creating a post is restricted to Cognito User Pools +const newPostResult = await client.graphql({ + query: queries.createPost, + variables: { input: { title: 'Hello World' } }, + authMode: 'userPool' +}); + +// Listing posts is available to all users (verified by IAM) +const listPostsResult = await client.graphql({ + query: queries.listPosts, + authMode: 'iam' +}); +``` + +In the example above: + +- any user (signed in or not, verified by IAM) is allowed to read all posts +- owners are allowed to create, read, update, and delete their own posts. + +If you are using DataStore and have multiple authorization rules, you can let DataStore automatically determine the best authorization mode client-side. Review how to [Configure Multiple Authorization Types](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/#configure-multiple-authorization-types) on DataStore for more details. + +## Field-level authorization rules + +When an authorization rule is added to a field, it'll strictly define the authorization rules applied on the field. Field-level authorization rules **do not** inherit model-level authorization rules. Meaning, only the specified field-level authorization rule is applied. + +```graphql +type Employee + @model + @auth(rules: [{ allow: private, operations: [read] }, { allow: owner }]) { + name: String + email: String + ssn: String @auth(rules: [{ allow: owner }]) +} +``` + +In the example above: + +- Owners are allowed to create, read, update, and delete Employee records they own +- Any signed in user has read access +- Any signed in user can read data with the exception of the `ssn` field. This field only has owner auth applied, the field-level auth rule means that model-level auth rules are not applied + + + +To prevent sensitive data from being sent over subscriptions, the GraphQL Transformer needs to alter the response of mutations for those fields by setting them to null. Therefore, to facilitate field-level authorization with subscriptions, you need to either apply field-level authorization rules to all **required** fields, make the other fields nullable, or disable subscriptions by setting it to public or off. + + + +In the example above: + +- **any signed in user** is allowed to read the list of employees' `name` and `email` fields +- **only the employee/owner themselves** have CRUD access to their `ssn` field + + + +To prevent unintended loss of data, the user or role that attempts to `delete` a record should have delete permissions on every field of the `@model` annotated GraphQL type. For example, in the schema below: + +```graphql +type Todo + @model + @auth( + rules: [ + { allow: private, provider: iam } + { allow: groups, groups: ["Admin"] } + ] + ) { + id: ID! + name: String! + @auth( + rules: [ + { allow: private, provider: iam } + { allow: groups, groups: ["Admin"] } + ] + ) + description: String @auth(rules: [{ allow: private, provider: iam }]) +} +``` + +Since the `description` field is not accessible by "Admin" Cognito group users, they cannot delete any `Todo` records. + + + +## Advanced + +### Review and print access control matrix + +Verify your API's access control matrix, by running the following command: + +```bash +amplify status api -acm Blog +``` + +```console +iam:public + ┌─────────┬────────┬──────┬────────┬────────┐ + │ (index) │ create │ read │ update │ delete │ + ├─────────┼────────┼──────┼────────┼────────┤ + │ title │ false │ true │ false │ false │ + │ content │ false │ true │ false │ false │ + └─────────┴────────┴──────┴────────┴────────┘ +userPools:owner:owner + ┌─────────┬────────┬──────┬────────┬────────┐ + │ (index) │ create │ read │ update │ delete │ + ├─────────┼────────┼──────┼────────┼────────┤ + │ title │ true │ true │ true │ true │ + │ content │ true │ true │ true │ true │ + └─────────┴────────┴──────┴────────┴────────┘ +``` + +### Use IAM authorization within the AppSync console + + + + + +IAM-based `@auth` rules are scoped down to only work with Amplify-generated IAM roles. To access the GraphQL API with IAM authorization within your AppSync console, you need to explicitly allow list the IAM user's name. Add the allow-listed IAM users by adding them to `amplify/backend/api//custom-roles.json`. (Create the `custom-roles.json` file if it doesn't exist). Append the `adminRoleNames` array with the IAM role or user names: + +```json +{ + "adminRoleNames": [""] +} +``` + + + + +To grant any IAM principal (AWS Resource, IAM role, IAM user, etc) access, **with the exception of Amazon Cognito identity pool roles**, to this GraphQL API in CDK, you need to enable IAM authorization mode via the `iamConfig` property of the CDK construct. + +```typescript +const userRole = Role.fromRoleName( + this, + 'MyUserRole', + '' +); + +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + }, + iamConfig: { + // Set this value to true. + enableIamAuthorizationMode: true + } + } +}); +``` + +These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. + + + +These "Admin Roles" have special access privileges that are scoped based on their IAM policy instead of any particular `@auth` rule. + +### Using OIDC authorization provider + +`private`, `owner`, and `group` authorization can be configured with an OpenID Connect (OIDC) authorization mode. Add `provider: oidc` to the authorization rule. + + + + +Upon the next `amplify push`, Amplify CLI prompts you for the _OpenID Connect provider domain_, _Client ID_, _Issued at TTL_, and _Auth Time TTL_. + + + + +Use the `oidcConfig` property to configure the _OpenID Connect provider domain_, _Client ID_, _Issued at TTL_, and _Auth Time TTL_. + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'OPENID_CONNECT', + oidcConfig: { + oidcIssuerUrl: '...', + oidcProviderName: '...', + tokenExpiryFromAuth: '...', + tokenExpiryFromIssue: '...', + clientId: '...' + } + } +}); +``` + + + + +```graphql +type Todo + @model + @auth( + rules: [ + { allow: owner, provider: oidc, identityClaim: "user_id" } + { allow: private, provider: oidc } + { allow: group, provider: oidc, groupClaim: "user_groups" } + ] + ) { + content: String +} +``` + +The example above highlights the supported authorization strategies with `oidc` authorization provider. For `owner` and `group` authorization, you also need to [specify a custom identity and group claim](#configure-custom-identity-and-group-claims). + +### Configure custom identity and group claims + +`@auth` supports using custom claims if you do not wish to use the default Amazon Cognito-provided "cognito:groups" or the double-colon-delimited claims, "sub::username", from your JWT token. This can be helpful if you are using tokens from a 3rd party OIDC system or if you wish to populate a claim with a list of groups from an external system, such as when using a [Pre Token Generation Lambda Trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html) which reads from a database. To use custom claims specify `identityClaim` or `groupClaim` as appropriate like in the example below: + +```graphql +type Post + @model + @auth( + rules: [ + { allow: owner, identityClaim: "user_id" } + { allow: groups, groups: ["Moderator"], groupClaim: "user_groups" } + ] + ) { + id: ID! + owner: String + postname: String + content: String +} +``` + +In this example the record owner will check against a `user_id` claim. Similarly, if the `user_groups` claim contains a "Moderator" string then access will be granted. + +### Grant Lambda function access to GraphQL API + +Lambda functions' IAM execution role do not immediately grant access to Amplify's GraphQL API because the API operates on a "deny-by-default"-basis. Access need to be explicitly granted. Depending on how your function is deployed, the workflow slightly differ + + + + + +If you grant a Lambda function in your Amplify project access to the GraphQL API via `amplify update function`, then the Lambda function's IAM execution role is allow-listed to honor the permissions granted on the `Query`, `Mutation`, and `Subscription` types. + +Therefore, these functions have special access privileges that are scoped based on their IAM policy instead of any particular `@auth` rule. + + + +Once you grant a function access to the GraphQL API, it is required to redeploy the API to apply the permissions. To do so, run the command `amplify api gql-compile --force` before deployment via `amplify push`. + + + + + + +To grant any IAM principal (AWS Resource, IAM role, IAM user, etc), **with the exception of Amazon Cognito identity pool roles**, to this GraphQL API in CDK, you need to enable IAM authorization mode on the CDK construct. + +```typescript +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + }, + iamConfig: { + // Must be set to `true`. Then grant your Lambda function's execution role access to the API + enableIamAuthorizationMode: true + } + } +}); +``` + +These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. + + + + + +To grant an external AWS Resource or an IAM role access to this GraphQL API, you need to explicitly list the IAM role's name or the AWS Resource's name by adding it to `amplify/backend/api//custom-roles.json`. (Create the `custom-roles.json` file if it doesn't exist). Append the `adminRoleNames` array with the IAM role name or AWS Resource name: + +```json +{ + "adminRoleNames": ["", ""] +} +``` + +You can use the symbol `${env}` to reference the current Amplify CLI environment. + +These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. + + + + + + + +Refer to the [sample code](/[platform]/build-a-backend/graphqlapi/connect-from-server-runtime/#iam-authorization) to learn how to sign the request to call the GraphQL API using IAM authorization. + + + +### How it works + +Definition of the `@auth` directive: + +```graphql +# When applied to a type, augments the application with +# owner and group-based authorization rules. +directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION +input AuthRule { + allow: AuthStrategy! + provider: AuthProvider + ownerField: String # defaults to "owner" when using owner auth + identityClaim: String # defaults to "sub::username" when using owner auth + groupClaim: String # defaults to "cognito:groups" when using Group auth + groups: [String] # Required when using Static Group auth + groupsField: String # defaults to "groups" when using Dynamic Group auth + operations: [ModelOperation] # Required for finer control +} + +enum AuthStrategy { + owner + groups + private + public + custom +} +enum AuthProvider { + apiKey + iam + oidc + userPools + function +} +enum ModelOperation { + create + update + delete + read # Short-hand to allow "get", "list", "sync", "listen", and "search" + get # Retrieves an individual item + list # Retrieves a list of items + sync # Enables ability to sync offline/online changes (including via DataStore) + listen # Subscribes to real-time changes + search # Enables ability to search using @searchable directive +} +``` + +Authorization rules consists of: + +- **authorization strategy** (`allow`): who the authorization rule applies to +- **authorization provider** (`provider`): which mechanism is used to apply the authorization rule (API Key, IAM, Amazon Cognito user pool, OIDC) +- **authorized operations** (`operations`): which operations are allowed for the given strategy and provider. If not specified, `create`, `read`, `update`, and `delete` operations are allowed. + - **`read` operation**: `read` operation can be replaced with `get`, `list`, `sync`, `listen`, and `search` for a more granular query access + + + +If you use DataStore instead of the API category to connect to your AppSync API, then you must allow `listen` and `sync` operations for your data model. + + + +**API Keys** are best used for public APIs (or parts of your schema which you wish to be public) or prototyping, and you must specify the expiration time before deploying. **IAM** authorization uses [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) to make request with policies attached to Roles. OIDC tokens provided by **Amazon Cognito user pool** or **3rd party OpenID Connect** providers can also be used for authorization, and enabling this provides a simple access control requiring users to authenticate to be granted top level access to API actions. diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx new file mode 100644 index 00000000000..68d7cbfbc92 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx @@ -0,0 +1,1262 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Customize your data model', + description: 'Customize your data model with primary keys, secondary indexes, and model relationships.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/data-modeling/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +Amplify automatically creates Amazon DynamoDB database tables for GraphQL types annotated with the `@model` directive in your GraphQL schema. You can create relations between the data models via the `@hasOne`, `@hasMany`, `@belongsTo`, and `@manyToMany` directives. + +## Setup database tables + +The following GraphQL schema automatically creates a database table for "Todo". `@model` will also automatically add an `id` field as a primary key to the database table. _See [Configure a primary key](#configure-a-primary-key) to learn how to customize the primary key._ + +```graphql +type Todo @model { + content: String +} +``` + +Upon `amplify push` or `cdk deploy`, Amplify deploys the Todo database table and a corresponding GraphQL API to perform create, read, update, delete, and list operations. + +In addition, `@model` also adds the helper fields `createdAt` and `updatedAt` to your type. The values for those fields are read-only by clients unless explicitly overwritten. See [Customize creation and update timestamps](#customize-creation-and-update-timestamps) to learn more. + +Try listing all the todos by executing the following query: + +```graphql +query QueryAllTodos { + listTodos() { + todos { + items { + id + content + createdAt + updatedAt + } + } + } +} +``` + + + +```js +import { Amplify } from 'aws-amplify'; +import { generateClient } from 'aws-amplify/api'; +import config from './amplifyconfiguration.json'; +import { listTodos } from './graphql/queries'; + +const client = generateClient(); + +Amplify.configure(config); + +try { + const result = await client.graphql({ query: listTodos }); + const todos = result.data.listTodos; +} catch (res) { + const { errors } = res; + console.error(errors); +} +``` + + + +### Configure a primary key + +Every GraphQL type with the `@model` directive will automatically have an `id` field set as the primary key. You can override this behavior by marking another required field with the `@primaryKey` directive. + +In the example below, `todoId` is the database's primary key and an `id` field will no longer be created automatically anymore by the `@model` directive. + +```graphql +type Todo @model { + todoId: ID! @primaryKey + content: String +} +``` + +Without any further configuration, you'll only be able to query for a Todo via an exact equality match of its primary key field. In the example above, this is the `todoId` field. + +> Note: After a primary key is configured and deployed, you can't change it without deleting and recreating your database table. + +You can also specify "sort keys" to use a combination of different fields as a primary key. This also allows you to apply more advanced sorting and filtering conditions on the specified "sort key fields". + +```graphql +type Inventory @model { + productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) + warehouseID: ID! + InventoryAmount: Int! +} +``` + +The schema above will allow you to pass different conditions to query the correct inventory item: + +```graphql +query QueryInventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { + getInventory(productID: $productID, warehouseID: $warehouseID) { + productID + warehouseID + inventoryAmount + } +} +``` + + + +```js +import { getInventory } from './graphql/queries'; + +const result = await client.graphql({ + query: getInventory, + variables: { + productID: 'product-id', + warehouseID: 'warehouse-id' + } +}); +const inventory = result.data.getInventory; +``` + + + +### Configure a secondary index + +Amplify uses Amazon DynamoDB tables as the underlying data source for @model types. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `@index` directive to configure a secondary index. + +> **Amazon DynamoDB** is a key-value and document database that delivers single-digit millisecond performance at any scale but making it work for your access patterns requires a bit of forethought. DynamoDB query operations may use at most two attributes to efficiently query data. The first query argument passed to a query (the hash key) must use strict equality and the second attribute (the sort key) may use gt, ge, lt, le, eq, beginsWith, and between. DynamoDB can effectively implement a wide variety of access patterns that are powerful enough for the majority of applications. + +A secondary index consists of a "hash key" and, optionally, a "sort key". Use the "hash key" to perform strict equality and the "sort key" for greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), equals (eq), begins with, and between operations. + +```graphql +type Customer @model { + id: ID! + name: String! + phoneNumber: String + accountRepresentativeID: ID! @index +} +``` + +The example client query below allows you to query for "Customer" records based on their `accountRepresentativeID`: + +```graphql +query QueryCustomersForAccountRepresentative($accountRepresentativeID: ID!) { + customersByAccountRepresentativeID( + accountRepresentativeID: $accountRepresentativeID + ) { + customers { + items { + id + name + phoneNumber + } + } + } +} +``` + + + + +```js +import { customersByAccountRepresentativeID } from './graphql/queries'; + +const result = await client.graphql({ + query: customersByAccountRepresentativeID, + variables: { + accountRepresentativeID: 'account-rep-id' + } +}); +const customers = result.data.customersByAccountRepresentativeID; +``` + + + +You can also overwrite the `queryField` or `name` to customize the GraphQL query name, or secondary index name respectively: + +```graphql +type Customer @model { + id: ID! + name: String! + phoneNumber: String + accountRepresentativeID: ID! + @index(name: "byRepresentative", queryField: "customerByRepresentative") +} +``` + +```graphql +query QueryCustomersForAccountRepresentative($representativeId: ID!) { + customerByRepresentative(accountRepresentativeID: $representativeId) { + customers { + items { + id + name + phoneNumber + } + } + } +} +``` + + + +```js +import { customerByRepresentative } from './graphql/queries'; + +const result = await client.graphql({ + query: customerByRepresentative, + variables: { + accountRepresentativeID: 'account-rep-id' + } +}); +const customer = result.data.customerByRepresentative; +``` + + + +To optionally configure sort keys, provide the additional fields in the `sortKeyFields` parameter: + +```graphql +type Customer @model @auth(rules: [{ allow: public }]) { + id: ID! + name: String! @index(name: "byNameAndPhoneNumber", sortKeyFields: ["phoneNumber"], queryField: "customerByNameAndPhone") + phoneNumber: String + accountRepresentativeID: ID! @index +``` + +The example client query below allows you to query for "Customer" based on their `name` and filter based on `phoneNumber`: + +```graphql +query MyQuery { + customerByNameAndPhone(phoneNumber: { beginsWith: "+1" }, name: "Rene") { + items { + id + name + phoneNumber + } + } +} +``` + + + +```js +import { customerByNameAndPhone } from './graphql/queries'; + +const result = await client.graphql({ + query: customerByNameAndPhone, + variables: { + phoneNumber: { beginsWith: '+1' }, + name: 'Rene' + } +}); + +const customer = result.data.customerByNameAndPhone; +``` + + + + + +## Setup relationships between models + +Create "has one", "has many", "belongs to", and "many to many" relationships between `@model` types. + +| Relationship | Description | +| --- | --- | +| `@hasOne` | Create a one-directional one-to-one relationship between two models. For example, a Project "has one" Team. This allows you to query the team from the project record. | +| `@hasMany` | Create a one-directional one-to-many relationship between two models. For example, a Post has many comments. This allows you to query all the comments from the post record. | +| `@belongsTo` | Use a "belongs to" relationship to make a "has one" or "has many" relationship bi-directional. For example, a Project has one Team and a Team belongs to a Project. This allows you to query the team from the project record and vice versa. | +| `@manyToMany` | Configures a "join table" between two models to facilitate a many-to-many relationship. For example, a Blog has many Tags and a Tag has many Blogs. | + +### Has One relationship + +import gqlv2callout from '/src/fragments/cli/gqlv2callout.mdx'; + + + +Create a one-directional one-to-one relationship between two models using the `@hasOne` directive. + +In the example below, a Project has a Team. + +```graphql +type Project @model { + id: ID! + name: String + team: Team @hasOne +} + +type Team @model { + id: ID! + name: String! +} +``` + +This generates queries and mutations that allow you to retrieve the related record from the source record: + +```graphql +mutation CreateProject { + createProject(input: { projectTeamId: "team-id", name: "Some Name" }) { + team { + name + id + } + name + id + } +} +``` + + +```js +import { createProject } from './graphql/mutations'; + +const result = await client.graphql({ + query: createProject, + variables: { + input: { projectTeamId: 'team-id', name: 'Some Name' } + } +}); + +const project = result.data.createProject; +``` + +To customize the field to be used for storing the relationship information, set the `fields` array argument and matching it to a field on the type: + +```graphql +type Project @model { + id: ID! + name: String + teamID: ID + team: Team @hasOne(fields: ["teamID"]) +} + +type Team @model { + id: ID! + name: String! +} +``` + +In this case, the Project type has a `teamID` field added as an identifier for the team. @hasOne can then get the connected Team object by querying the Team table with this `teamID`: + +```graphql +mutation CreateProject { + createProject(input: { name: "New Project", teamID: "a-team-id" }) { + id + name + team { + id + name + } + } +} +``` + + +```js +import { createProject } from './graphql/mutations'; + +const result = await client.graphql({ + query: createProject, + variables: { + input: { + teamID: 'team-id', + name: 'New Project' + } + } +}); +const project = result.data.createProject; +``` + +A `@hasOne` relationship always uses a reference to the primary key of the related model, by default `id` unless overridden with the [`@primaryKey` directive](#configure-a-primary-key). + +### Has Many relationship + + + +Create a one-directional one-to-many relationship between two models using the `@hasMany` directive. + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + content: String! +} +``` + +This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record: + +```graphql +mutation CreatePost { + createPost(input: { title: "Hello World!!" }) { + title + id + comments { + items { + id + content + } + } + } +} +``` + + +```js +import { createPost } from './graphql/mutations'; + +const result = await client.graphql({ + query: createPost, + variables { + input: { title: 'Hello World!!' }, + } +}); +const post = result.data.createPost; +const comments = post.comments.items; +``` + +Under the hood, `@hasMany` configures a default secondary index on the related table to enable you to query the related model from the source model. + +To customize the specific secondary index used for the "has many" relationship, create an `@index` directive on the field in the related table where you want to assign the secondary index. + +Next, provide the secondary index with a `name` attribute and a value. Optionally, you can configure a “sort key” on the secondary index by providing a `sortKeyFields` attribute and a designated field as its value. + +On the `@hasMany` configuration, pass in the name value from your secondary index as the value for the `indexName` parameter. Then, pass in the respective `fields` that match the connected index. + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) +} + +type Comment @model { + id: ID! + postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) + content: String! +} +``` + +In this case, the Comment type has a `postID` field added to store the reference of Post record. The `id` field referenced by `@hasMany` is the `id` on the `Post` type. `@hasMany` can then get the connected Comment object by querying the Comment table's secondary index "byPost" with this `postID`: + +```graphql +mutation CreatePost { + createPost(input: { title: "Hello world!" }) { + comments { + items { + postID + content + id + } + } + title + id + } +} +``` + +```js +import { createPost, createComment } from './graphql/mutations'; +import { getPost } from './graphql/mutations'; + +// create post +const result = await client.graphql({ + query: createPost, + variables: { + input: { title: 'Hello World!!' } + } +}); +const post = result.data.createPost; + +// create comment +await client.graphql({ + query: createComment, + variables: { + input: { content: 'Hi!', postID: post.id } + } +}); + +// get post +const result = await client.graphql({ + query: getPost, + variables: { id: post.id } +}); + +const postWithComments = result.data.createPost; +const postComments = postWithComments.comments.items; // access comments from post +``` + + +### Belongs To relationship + +Make a "has one" or "has many" relationship bi-directional with the `@belongsTo` directive. + + + + + +For 1:1 relationships, the @belongsTo directive solely facilitates the ability for you to query from both sides of the relationship. When updating a bi-directional hasOne relationship, you must update both sides of the relationship with corresponding IDs. + +```graphql +type Project @model { + id: ID! + name: String + team: Team @hasOne +} + +type Team @model { + id: ID! + name: String! + project: Project @belongsTo +} +``` + +This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: + +```graphql +mutation CreateProject { + createProject(input: { name: "New Project", teamID: "a-team-id" }) { + id + name + team { + # query team from project + id + name + project { + # bi-directional query: team to project + id + name + } + } + } +} +``` + +```js +import { createProject, createTeam, updateTeam } from './graphql/mutations'; + +// create team +const result = await client.graphql({ + query: createTeam, + variables: { + input: { name: 'New Team' } + } +}); +const team = result.data.createTeam; + +// create project +const result = await client.graphql({ + query: createProject, + variables: { + input: { name: 'New Project', projectTeamId: team.id } + } +}); +const project = result.data.createProject; +const projectTeam = project.team; // access team from project + +// update team +const updateTeamResult = await client.graphql({ + query: updateTeam, + variables: { + input: { id: team.id, teamProjectId: project.id } + } +}); + +const updatedTeam = updateTeamResult.data.updateTeam; +const teamProject = updatedTeam.project; // access project from team +``` + + + + + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + content: String! + post: Post @belongsTo +} +``` + +This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: + +```graphql +mutation CreatePost { + createPost(input: { title: "Hello World!!" }) { + title + id + comments { + # query comments from the post + items { + id + content + post { + # bi-directional query: comment to post + id + title + } + } + } + } +} +``` + +```js +import { createPost, createComment } from './graphql/mutations'; +import { getPost } from './graphql/mutations'; + +// create post +const result = await client.graphql({ + query: createPost, + variables: { + input: { title: 'Hello World!!' } + } +}); +const post = result.data.createPost; + +// create comment +await client.graphql({ + query: createComment, + variables: { + input: { content: 'Hi!', postID: post.id } + } +}); + +// get post +const result = await client.graphql({ + query: getPost, + variables: { id: post.id } +}); + +const postWithComments = result.data.createPost; +const postComments = postWithComments.comments.items; // access comments from post + +const commentPost = postComments[0].post; // access post from comment; +``` + + + + + +`@belongsTo` can be used without the `fields` argument. In those cases, a field is automatically generated to reference the parent’s primary key. + +Alternatively, you set up a custom field to store the reference of the parent object. An example bidirectional “has many” relationship is shown below. + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) +} + +type Comment @model { + id: ID! + postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) + content: String! + post: Post @belongsTo(fields: ["postID"]) +} +``` + +> Note: The `@belongsTo` directive requires that a `@hasOne` or `@hasMany` relationship already exists from parent to the related model. + +### Many-to-many relationship + +Create a many-to-many relationship between two models with the `@manyToMany` directive. Provide a common `relationName` on both models to join them into a many-to-many relationship. + +```graphql +type Post @model { + id: ID! + title: String! + content: String + tags: [Tag] @manyToMany(relationName: "PostTags") +} + +type Tag @model { + id: ID! + label: String! + posts: [Post] @manyToMany(relationName: "PostTags") +} +``` + +Under the hood, the `@manyToMany` directive will create a "join table" named after the `relationName` to facilitate the many-to-many relationship. This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: + +```graphql +mutation CreatePost { + createPost(input: { title: "Hello World!!" }) { + id + title + content + tags { + # queries the "join table" PostTags + items { + tag { + # related Tag records from Post + id + label + posts { + # queries the "join table" PostTags + items { + post { + # related Post records from Tag + id + title + content + } + } + } + } + } + } + } +} +``` + +```js +import { createPost, createTag, createPostTags } from './graphql/mutations'; +import { listPosts } from './graphql/queries'; + +// create post +const result = await client.graphql({ + query: createPost, + variables: { + input: { title: 'Hello World' } + } +}); +const post = result.data.createPost; + +// create tag +const tagResult = await client.graphql({ + query: createTag, + variables: { + input: { + label: 'My Tag' + } + } +}); +const tag = tagResult.data.createTag; + +// connect post and tag +await client.graphql({ + query: createPostTags, + variables: { + input: { + postId: post.id, + tagId: tag.id + } + } +}); + +// get posts +const listPostsResult = await client.graphql({ query: listPosts }); +const posts = listPostsResult.data.listPosts; + +const postTags = posts[0].tags; // access tags from post +``` + +## Assign default values for fields + +You can use the `@default` directive to specify a default value for optional [scalar type fields](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html) such as `Int`, `String`, and more. + +```graphql +type Todo @model { + content: String @default(value: "My new Todo") + # Note: all "value" parameters must be passed as a string value. + # Under the hood, Amplify will parse the string values into respective types. + # For example, to set a default value for an integer field, + # you must pass in `"0"` instead of `0` without the double-quotes. + likes: Int @default(value: "0") # +} +``` + +If you create a new Todo and don't supply a `content` input, Amplify will ensure that `My new Todo` is auto populated as a value. When `@default` is applied, non-null assertions using `!` are disregarded. For example, `String!` is treated the same as `String`. + +## Server-side filtering for subscriptions + +A server-side subscription filter expression is automatically generated for any `@model` type. + +```graphql +type Task @model { + title: String! + description: String + type: String + priority: Int +} +``` + +You can filter the subscriptions server-side by passing a filter expression. For example: If you want to subscribe to tasks of type `Security` and priority greater than `5`, you can set the `filter` argument accordingly. + +```graphql +subscription OnCreateTask { + onCreateTask( + filter: { and: [{ type: { eq: "Security" } }, { priority: { gt: 5 } }] } + ) { + title + description + type + priority + } +} +``` + +```js +import { onCreateTask } from './graphql/subscriptions'; + +const subscription = client.graphql({ + query: onCreateTask, + variables: { + filter: { + and: [ + { type: { eq: "Security" } } + { priority: { gt: 5 } } + ] + } + } +}).subscribe({ + next: ({ data }) => console.log(data), + error: (error) => console.warn(error) +}); +``` + +If you want to get all subscription events, don’t pass any `filter` parameters. + + + +**Important**: Passing an empty object `{}` as a filter is NOT recommended. Using `{}` as a filter might cause inconsistent behavior based on your data model's authorization rules. + + + + + +## Advanced + +### Rename generated queries, mutations, and subscriptions + +You can override the names of any `@model`-generated GraphQL queries, mutations, and subscriptions by supplying the desired name. + +```graphql +type Todo @model(queries: { get: "queryFor" }) { + name: String! + description: String +} +``` + +In the example above, you will be able to run a `queryForTodo` query to get a single Todo element. + +### Disable generated queries, mutations, and subscriptions + +You can disable specific operations by assigning their value to `null`. + +```graphql +type Todo @model(queries: { get: null }, mutations: null, subscriptions: null) { + name: String! + description: String +} +``` + +The example above disables the `getTodo` query, all mutations, and all subscriptions while allowing the generation of other queries such as `listTodo`. + +### Creating a custom query + +You can disable the `get` query and create a custom query that enables us to retrieve a single Todo model. + +```graphql +type Query { + getMyTodo(id: ID!): Todo @function(name: "getmytodofunction-${env}") +} +``` + +The example above creates a custom query that utilizes the `@function` directive to call a Lambda function for this query. + +For the type definitions of queries, mutations, and subscriptions, see [Type Definitions of the `@model` Directive](#type-definition-of-the-`@model`-directive). + +### Customize creation and update timestamps + +The `@model` directive automatically adds `createdAt` and `updatedAt` timestamps to each entity. The timestamp field names can be changed by passing timestamps attribute to the directive. + +```graphql +type Todo + @model(timestamps: { createdAt: "createdOn", updatedAt: "updatedOn" }) { + name: String! + description: String +} +``` + +For example, the schema above will allow you to query for the following contents: + +```graphql +type Todo { + id: ID! + name: String! + description: String + createdOn: AWSDateTime! + updatedOn: AWSDateTime! +} +``` + +### Modify subscriptions (real-time updates) access level + +By default, real-time updates are on for all `@model` types, which means customers receive real-time updates and authorization rules are applied during initial connection time. You can also turn off subscriptions for that model or make the real-time updates public, receivable by all subscribers. + +```graphql +type Todo + @model(subscriptions: { level: off }) { # or level: public + name: String! + description: String +} +``` + +### Create multiple relationships between two models + +You need to explicitly specify the connection field names if relational directives are used to create two connections of the same type between the two models. + +```graphql +type Individual @model { + id: ID! + homeAddress: Address @hasOne + shippingAddress: Address @hasOne +} + +type Address @model { + id: ID! + homeIndividualID: ID + shippingIndividualID: ID + homeIndividual: Individual @belongsTo(fields: ["homeIndividualID"]) + shipIndividual: Individual @belongsTo(fields: ["shippingIndividualID"]) +} +``` + +### Relationships to a model with a composite primary key + +When a primary key is defined by a _sort key_ in addition to the _hash key_, then it's called a **composite primary key**. + +If you explicitly define the `fields` argument on the `@hasOne`, `@hasMany`, or `@belongsTo` directives and reference a model that has a composite primary key, then you must set the values in the `fields` argument in a specific order: + +- The first value should always be the primary key of the related model. +- Remaining values should match the `sortKeyFields` specified in the `@primaryKey` directive of the related model. + + + + + +```graphql +type Project @model { + projectId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! + team: Team @hasOne(fields: ["teamId", "teamName"]) + teamId: ID # customized foreign key for child primary key + teamName: String # customized foreign key for child sort key +} + +type Team @model { + teamId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! +} +``` + + + + + +```graphql +type Project @model { + projectId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! + team: Team @hasOne(fields: ["teamId", "teamName"]) + teamId: ID # customized foreign key for child primary key + teamName: String # customized foreign key for child sort key +} + +type Team @model { + teamId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! + project: Project @belongsTo(fields: ["projectId", "projectName"]) + projectId: ID # customized foreign key for parent primary key + projectName: String # customized foreign key for parent sort key +} +``` + + + + + +```graphql +type Post @model { + postId: ID! @primaryKey(sortKeyFields: ["title"]) + title: String! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"]) +} + +type Comment @model { + commentId: ID! @primaryKey(sortKeyFields: ["content"]) + content: String! + postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key + postTitle: String # customized foreign key for parent sort key +} +``` + + + + + +```graphql +type Post @model { + postId: ID! @primaryKey(sortKeyFields: ["title"]) + title: String! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"]) +} + +type Comment @model { + commentId: ID! @primaryKey(sortKeyFields: ["content"]) + content: String! + post: Post @belongsTo(fields: ["postId", "postTitle"]) + postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key + postTitle: String # customized foreign key for parent sort key +} +``` + + + + + +### Generate a secondary index without a GraphQL query + +Because query creation against a secondary index is automatic, if you wish to define a secondary index that does not have a corresponding query in your API, set the `queryField` parameter to `null`. + +```graphql +type Customer @model { + id: ID! + name: String! + phoneNumber: String + accountRepresentativeID: ID! @index(queryField: null) +} +``` + +### Split GraphQL files + + +Amplify Studio does not support splitting GraphQL schemas. + +If using Amplify Studio, please follow the [Limitations](https://docs.amplify.aws/javascript/tools/console/data/data-model/#split-graphql-files) section of the Data Modeling documentation for Amplify Studio. + + +AWS Amplify supports splitting your GraphQL schema into separate `.graphql` files. + +You can start by creating a `amplify/backend/api//schema/` directory. As an example, you might split up the schema for a blog site by creating `Blog.graphql`, `Post.graphql`, and `Comment.graphql` files. + +You can then run `amplify api gql-compile` and the output build schema will include all the types declared across your schema files. + +As your project grows, you may want to organize your custom queries, mutations, and subscriptions depending on the size and maintenance requirements of your project. You can either consolidate all of them into one file or colocate them with their corresponding models. + +**Using a Single `Query.graphql` File** + +This method involves consolidating all queries into a single `Query.graphql` file. It is useful for smaller projects or when you want to keep all queries in one place. + +1. In the `amplify/backend/api//schema/` directory, create a file named `Query.graphql`. + +2. Copy all query type definitions from your multiple schema files into the `Query.graphql` file. + +3. Make sure all your queries are properly formatted and enclosed within a single `type Query { ... }` block. + +**Using the `extend` Keyword** + +Declaring a `Query` type in separate schema files will result in schema validation errors similar to the following when running `amplify api gql-compile`: + +```sh +🛑 Schema validation failed. + +There can be only one type named "Query". +``` + +Amplify GraphQL schemas support the `extend` keyword, which allows you to extend types with additional fields. In this case, it also allows you to split your custom queries, mutations, and subscriptions into multiple files. This may be more ideal for larger, more complex projects. + +1. Organize your GraphQL schema into multiple files as per your project's architecture. + +2. In one of the files (e.g., `schema1.graphql`), declare your type normally: + +```graphql +type Query { + # initial custom queries +} +``` + +3. In other schema files (e.g., `schema2.graphql`), use the `extend` keyword to add to the type: + +```graphql +extend type Query { + # additional custom queries +} +``` + +The order in which the Query types are extended does not affect the compilation of separate schema files. + + + +Declaring custom Query, Mutation, and/or Subscription with the same field names in another schema file will result in schema validation errors similar to the following: + +`🛑 Object type extension 'Query' cannot redeclare field getBlogById` + + + +## How it works + +### Model directive + +The `@model` directive will generate: + +- An Amazon DynamoDB table with PAY_PER_REQUEST billing mode enabled by default. +- An AWS AppSync DataSource configured to access the table above. +- An AWS IAM role attached to the DataSource that allows AWS AppSync to call the above table on your behalf. +- Up to 8 resolvers (create, update, delete, get, list, onCreate, onUpdate, onDelete) but this is configurable via the queries, mutations, and subscriptions arguments on the @model directive. +- Input objects for create, update, and delete mutations. +- Filter input objects that allow you to filter objects in list queries and relationship fields. +- For list queries the default number of objects returned is 100. You can override this behavior by setting the limit argument. + +**Type definition of the `@model` directive** + +```graphql +directive @model( + queries: ModelQueryMap + mutations: ModelMutationMap + subscriptions: ModelSubscriptionMap + timestamps: TimestampConfiguration +) on OBJECT + +input ModelMutationMap { + create: String + update: String + delete: String +} + +input ModelQueryMap { + get: String + list: String +} + +input ModelSubscriptionMap { + onCreate: [String] + onUpdate: [String] + onDelete: [String] + level: ModelSubscriptionLevel +} + +enum ModelSubscriptionLevel { + off + public + on +} + +input TimestampConfiguration { + createdAt: String + updatedAt: String +} +``` + +### Relational directives + +The relational directives are `@hasOne`, `@hasMany`, `@belongsTo` and `@manyToMany`. + + + + +The `@hasOne` will generate: + +- Foreign key fields in parent type that refer to the primary key and sort key fields of the child model. +- Foreign key fields in parent input object of `create` and `update` mutations. + +**Type definition of the `@hasOne` directive** + +```graphql +directive @hasOne(fields: [String!]) on FIELD_DEFINITION +``` + + + + +The `@hasMany` will generate: + +- Foreign key fields in child type that refer to the primary key and sort key fields of the parent model. +- Foreign key fields in child input object of `create` and `update` mutations. +- A global secondary index (GSI) in the child type Amazon DynamoDB table. + +**Type definition of the `@hasMany` directive** + +```graphql +directive @hasMany( + indexName: String + fields: [String!] + limit: Int = 100 +) on FIELD_DEFINITION +``` + +- The default number of nested objects returned is 100. You can override this behavior by setting the limit argument. + + + + +The `@belongsTo` will generate: + +- Foreign key fields that refer to the primary key and sort key fields of the related model. +- Foreign key fields in the input object of `create` and `update` mutations. + +**Type definition of the `@belongsTo` directive** + +```graphql +directive @belongsTo(fields: [String!]) on FIELD_DEFINITION +``` + + + + +The `@manyToMany` will generate: + +- A joint table defining the intermediate model type with the name of `relationName`. +- Foreign key fields in the joint table that refer to the primary key and sort key fields of both models. +- Foreign key fields in the intermediate model input object of `create` and `update` mutations. + +**Type definition of the `@manyToMany` directive** + +```graphql +directive @manyToMany( + relationName: String! + limit: Int = 100 +) on FIELD_DEFINITION +``` + +- The default number of nested objects returned is 100. You can override this behavior by setting the limit argument. + + + diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx new file mode 100644 index 00000000000..4385045cc55 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx @@ -0,0 +1,500 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Modify Amplify-generated resources', + description: 'Learn more about how to modify Amplify-generated resources for Amplify GraphQL APIs. This allows you to modify underlying AppSync, DynamoDB, Lambda, and OpenSearch resources.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/modify-amplify-generated-resources/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +Amplify GraphQL API uses a variety of auto-generated, underlying AWS services and resources. You can customize these underlying resources to optimize the deployed stack for your specific use case. + + + + + +```bash +amplify override api +``` + +Run the command above to override Amplify-generated GraphQL API resources including AWS AppSync API, Amazon DynamoDB table, Amazon OpenSearch domain, and more. + + + +If you need to customize a specific Amplify-generated VTL resolver, review [Override Amplify-generated resolvers](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#override-amplify-generated-resolvers) first. + + + +The command creates a new `overrides.ts` file under `amplify/backend/api//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). + + + + +In your CDK project, you can access every underlying resource as an ["L2"](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_using) or ["L1" construct](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_l1_using). Access the generated resources as L2 constructs via the `.resources` property on the returned stack or access the generated resources as L1 constructs using the `.resources.cfnResources` property. + +```ts +const api = new AmplifyGraphQlApi(this, 'api', { }); + +// Access L2 resources under `.resources` +api.resources.tables["Todo"].tableArn; + +// Access L1 resources under `.resources.cfnResources` +api.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true; +Object.values(api.resources.cfnResources.cfnTables).forEach(table => { + table.pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: false }; +}); +``` + + + + + +## Customize Amplify-generated AppSync GraphQL API resources + + + + + +Apply all the overrides in the `override(...)` function. For example to enable X-Ray tracing for the AppSync GraphQL API: + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.api.GraphQLAPI.xrayEnabled = true; +} +``` + +You can override the following GraphQL API resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [GraphQLAPI](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlapi.html) | AWS AppSync GraphQL API resource | +| [GraphQLAPIDefaultApiKey](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-apikey.html) | API Key resource for the AppSync GraphQL API | +| [GraphQLAPITransformerSchema](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlschema.html) | The GraphQL schema that's being deployed. (The output of the GraphQL Transformer) | +| [GraphQLAPINONEDS](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | A "none" data source that is used for requests that don't exit the AppSync API | +| [AmplifyDataStore](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The delta sync table used for Amplify DataStore's conflict resolution | +| [AmplifyDataStoreIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role used to access the delta sync table for DataStore | +| [DynamoDBAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access the DynamoDB resources from AppSync | + + + + +Apply all the customizations on `.resources.graphqlApi` or `.resources.cfnResources.cfnGraphqlApi`. For example to enable X-Ray tracing for the AppSync GraphQL API: + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); + +amplifyApi.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true; +``` + + + + + +## Customize Amplify-generated resources for @model directive + + + + + +Apply all the overrides in the `override(...)` function. Pass in the @model type name into `resources.models[...]` to modify the resources generated for that particular @model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Todo'].modelDDBTable.timeToLiveSpecification = { + attributeName: 'ttl', + enabled: true + }; +} +``` + +You can override the following @model directive resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [modelStack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html) | The nested stack containing all resources for the @model type | +| [modelDDBTable](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The DynamoDB table containing the data for this @model type | +| [modelIamRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access the DynamoDB table for this @model type | +| [modelIamRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access the delta sync table for this @model type in case DataStore is enabled | +| [dynamoDBAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | Default policy associated with the IAM role to access the DynamoDB table for this @model type | +| [modelDatasource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | The AppSync DataSource to representing the DynamoDB table | +| [invokeLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for Lambda-based conflict resolution function | + +For example, you can override a model generated DynamoDB table configuration. + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Todo'].modelDatasource.dynamoDbConfig['deltaSyncConfig'][ + 'baseTableTtl' + ] = '3600'; +} +``` + + + + +Apply all the customizations on `.resources.tables["MODEL_NAME"]` or `.resources.cfnResources.cfnTables["MODEL_NAME"]`. Pass in the @model type name into `.resources.cfnResources.cfnTables["MODEL_NAME"]` to modify the resources generated for that particular @model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); + +amplifyApi.resources.cfnResources.cfnTables['Todo'].timeToLiveSpecification = { + attributeName: 'ttl', + enabled: true +}; +``` + + + + + +### Example - Configure DynamoDB table's billing mode + +Set the [DynamoDB billing mode](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-billingmode) for the DynamoDB table. Either "PROVISIONED" or "PAY_PER_REQUEST". + + + + + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Post'].modelDDBTable.billingMode = 'PAY_PER_REQUEST'; +} +``` + + + + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); + +amplifyApi.resources.cfnResources.cfnTables['Todo'].billingMode = + 'PAY_PER_REQUEST'; +``` + + + + + +### Example - Configure provisioned throughput for DynamoDB table or Global Secondary Index + +Override the default [ProvisionedThroughput](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-provisionedthroughput) provisioned for each `@model` table and its Global Secondary Indexes (GSI). + +Only valid if the "DynamoDBBillingMode" is set to "PROVISIONED" + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Post'].modelDDBTable.provisionedThroughput = { + readCapacityUnits: 5, + writeCapacityUnits: 5 + }; + + /** + * When billing mode is set to "PROVISIONED", it is necessary to specify `provisionedThroughput` for every Global Secondary Index (GSI) that exists in the table. + */ + + resources.models[ + 'Post' + ].modelDDBTable.globalSecondaryIndexes[0].provisionedThroughput = { + readCapacityUnits: 5, + writeCapacityUnits: 5 + }; +} +``` + +### Example - Enable point-in-time recovery for DynamoDB table + +Enable/disable [DynamoDB point-in-time recovery](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-pointintimerecoveryspecification.html) for each `@model` table. + + + + + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Post'].modelDDBTable.pointInTimeRecoverySpecification = { + pointInTimeRecoveryEnabled: true + }; +} +``` + + + + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); + +amplifyApi.resources.cfnResources.cfnTables[ + 'Todo' +].pointInTimeRecoverySpecification = { + pointInTimeRecoveryEnabled: true +}; +``` + + + + + +## Customize Amplify-generated resources for @searchable (OpenSearch) directive + +Apply all the overrides in the `override(...)` function. For example, to modify the OpenSearch instance count: + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { + ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, + instanceCount: 6 + }; +} +``` + +You can override the following @searchable directive resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [OpenSearchDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | The AppSync data source representing the OpenSearch integration | +| [OpenSearchAccessIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access OpenSearch domain | +| [OpenSearchAccessIAMRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access OpenSearch domain | +| [OpenSearchDomain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticsearch-domain.html) | OpenSearch domain containing the @searchable data | +| [OpenSearchStreamingLambdaIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to stream DynamoDB data to OpenSearch domain | +| [OpenSearchStreamingLambdaIAMRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to stream DynamoDB data to OpenSearch domain | +| [CloudwatchLogsAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for granting CloudWatch logs access | +| [OpenSearchStreamingLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | Lambda function to stream DynamoDB data to OpenSearch domain | +| [OpenSearchModelLambdaMapping](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html) | Event source mapping for DynamoDB table stream to Lambda function | + +### Example - Configure Runtime for Streaming Lambda + +You can define the runtime for the `@searchable` by setting the runtime value on the function object itself. This can be done to resolve a deprecated runtime in the event that you cannot upgrade your version of the Amplify CLI. + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchStreamingLambdaFunction.runtime = 'python3.9'; +} +``` + +### Example - Configure OpenSearch Streaming function name + +Override the name of the AWS Lambda searchable streaming function + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchStreamingLambdaFunction.FunctionName = + 'CustomFunctionName'; +} +``` + +### Example - Configure OpenSearch instance version + +Override the `elasticsearchVersion` in the OpenSearch domain created by `@searchable` + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.elasticsearchVersion = 'OpenSearch_1.3'; +} +``` + +### Example - Configure OpenSearch instance type + +Override the type of instance launched into the OpenSearch domain created by `@searchable` + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { + ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, + instanceType: 'm3.medium.elasticsearch' + }; +} +``` + +### Example - Configure OpenSearch instance count + +Override the number of instances launched into the OpenSearch domain created by `@searchable` + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { + ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, + instanceCount: 2 + }; +} +``` + +### Example - Configure OpenSearch EBS volume size + +Override the amount of disk space allocated to each instance in the OpenSearch domain created by `@searchable` + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.ebsOptions = { + ...resources.opensearch.OpenSearchDomain.ebsOptions, + volumeSize: 10 + }; +} +``` + +## Customize Amplify-generated resources for @predictions directive + +Apply all the overrides in the `override(...)` function. For example, to add a Path to IAM role that facilitates text translation: + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.predictions.TranslateDataSourceServiceRole.path = + '/my/organization/'; +} +``` + +You can override the following @predictions directive resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [RekognitionDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync HTTP data source to connect to Amazon Rekognition service | +| [RekognitionDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Amazon Rekognition | +| [TranslateDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync HTTP data source to connect to Amazon Translate service | +| [translateTextAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to connect to Amazon Translate | +| [LambdaDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync Lambda data source to connect to Amazon Polly | +| [LambdaDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Lambda function calling Amazon Polly | +| [LambdaDataSourceServiceRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for AppSync to connect to Lambda function calling Amazon Polly | +| [TranslateDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Amazon Translate | +| [predictionsLambdaIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role for Lambda function calling Amazon Polly | +| [predictionsLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | Lambda function calling Amazon Polly | +| [PredictionsLambdaAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for Lambda function to access Amazon Polly | +| [predictionsIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access s3 bucket used by @predictions | +| [PredictionsStorageAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access S3 bucket used by @predictions | +| [identifyTextAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to enable Identify Text | +| [identifyLabelsAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to enable Identify Text | + +## Place AppSync Resolvers in Custom-named Stacks + +If you have a particularly large GraphQL schema, you may run into issues with too many resources defined in a stack. The most common case where this happens is in the ConnectionStack which contains the resolvers for all of the relational directives in the schema. + +Creating a stack mapping does not create an additional root stack for the Amplify environment. All mapped stacks will still be placed under the existing Amplify environment root stack. To map a resolver to a different stack, update `/amplify/api//transform.conf.json` with a "StackMapping" block. The StackMapping defines a map from resolver logical ID to stack name. + +```json +{ + "Version": 5, + "ElasticsearchWarning": true, + "StackMapping": { + "": "Custom stack name" + } +} +``` + +The easiest way to determine a resolver logical ID is to run `amplify api gql-compile` and note the resolver logical ID in the list of Resources in the generated CloudFormation stack. Resolvers for model operations will be of the form `Resolver`. Resolvers for relational directives are of the form `Resolver`. + +### Example + +Given the following schema: + +```graphql +type Blog @model { + id: ID! + name: String! + posts: [Post] @hasMany +} + +type Post @model { + id: ID! + title: String! + content: String + blog: Blog @belongsTo +} +``` + +To map the CreatePostResolver and the relational resolvers to a stack named 'MyCustomStack', add the following in `transform.conf.json`: + +```json +"StackMapping": { + "CreatePostResolver": "MyCustomStack", + "BlogpostsResolver": "MyCustomStack", + "PostblogResolver": "MyCustomStack", +} +``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx new file mode 100644 index 00000000000..4fbca0d68f6 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx @@ -0,0 +1,141 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Evolving GraphQL schemas', + description: 'Evolve your GraphQL schema over time using the @mapsTo directive to retain tables while renaming models', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/schema-evolution/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +GraphQL schemas change over the lifecycle of a project. Sometimes these changes include breaking API changes. One such change is renaming a model in the schema, which Amplify offers a way to do while retaining the underlying records for that model. + +## Renaming models while retaining data + +Amplify supports renaming models in a GraphQL schema by using the `@mapsTo` directive. +Normally when renaming a model, Amplify will remove the underlying table for the model and create a new table with the new name. Once a table contains production data that cannot be deleted, `@mapsTo` can be used to specify the original name. Amplify will use the original name to ensure the underlying DynamoDB tables and other resources point to the existing data. +Other GraphQL API references to the model will use the new name. + +For example, a schema such as: +```graphql +type Todo @model { + id: ID! + title: String! +} +``` +becomes: +```graphql +type Task @model @mapsTo(name: "Todo") { + id: ID! + title: String! +} +``` +Amplify will update all of the GraphQL operations and types to use the name Task, but the Task model will point to the table that Todo was originally using. + + + +- `@mapsTo` cannot be used to point a model to an arbitrarily named table. It can only be used to point a renamed model to it's original name. +- `@mapsTo` can only be used on @model GraphQL types that are backed by a DynamoDB table. + + + +When renaming a model that has relationships with other models, Amplify will automatically map auto-generated foreign key fields to their original name. For example, given: + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + message: String! + # postCommentsId: String is an autogenerated field containing the foreign key +} +``` +Amplify will automatically add a field named `postCommentsId` to the Comment model that contains the foreign key of the Post. If the Post type is renamed to Article: + +```graphql +type Article @model @mapsTo(name: "Post") { + id: ID! + title: String! + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + message: String! + # articleCommentsId: String is the new autogenerated field containing the foreign key +} +``` +The underlying table still contains records with `postCommentsId` as the foreign key field in the Comment table. In the new schema the foreign key field is now `articleCommentsId`. +Amplify is aware of this and will automatically map incoming requests with `articleCommentsId` to `postCommentsId` and do the reverse mapping for results. + +## Limitations + +### Constraint on relationship field names with @mapsTo + +In the above example if you renamed Comment to Reaction: +```graphql +type Post @model { + id: ID! + title: String! + comments: [Reaction] @hasMany # this field cannot be renamed and still access existing relationship data +} + +type Reaction @model @mapsTo(name: "Comment") { + id: ID! + message: String! + # autogenerated field postCommentsId: String contains the foreign key +} +``` +The `@hasMany` field `comments` cannot be renamed to `reactions`. This is because the foreign key field in Reaction uses the parent field name as part of the name. Amplify cannot determine the original name if this is changed. + +If a model is renamed multiple times, the value specified in `@mapsTo` must be the _original_ name, not the previous name. + +### Constraints to prevent naming conflicts + +A model in the schema cannot have the same name as the name another type maps to. For example, the following schema is invalid: +```graphql +type Article @model @mapsTo(name: "Post") { + id: ID! +} + +type Post @model { + id: ID! +} +``` +This schema would create a conflict on the Post table. + +Furthermore, even if the Post model is mapped to a different name, it is still not allowed. While this scenario technically does not pose a conflict, it is disallowed to prevent confusion. + +If you are accessing the table of a renamed model directly (ie. without going through AppSync), your access patterns will need to be aware that foreign key fields of records in the database are not renamed. See "How it works" below. + +## How it works +`@mapsTo` does not modify any existing tables or records. Instead, it points AppSync resolvers for the new name to the existing DynamoDB table for the original name. + +To handle renamed autogenerated foreign key fields when using relational directives, Amplify adds additional AppSync pipeline resolvers before and after fetching data from the database. +The resolvers before the fetch map any occurrence of the renamed foreign keys in the request to the original name. Then the resolvers after the fetch map any occurrence of the original name to the current name before returning the result. diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx new file mode 100644 index 00000000000..dc62cf0d07f --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx @@ -0,0 +1,479 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Build search and aggregate queries', + description: 'Add authorization rules to your GraphQL schema to control access to your data.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/search-and-result-aggregations/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +Add the `@searchable` directive to an `@model` type to enable OpenSearch-based data search and result aggregations. This gives you the ability to: + +- search for data using advanced filters, such as substring matching, wildcards, regex, `and`/`or`/`not` conditions +- get aggregation values, such as sum, average, min, max, terms +- retrieve total search result count +- sort the search results across one or multiple fields + +```graphql +type Student @model @searchable { + name: String + dateOfBirth: AWSDate + email: AWSEmail + examsCompleted: Int +} +``` + +> Once the `@searchable` directive is added, all new records added to the model are streamed to OpenSearch. To backfill existing data, see [Backfill OpenSearch index from DynamoDB table](/[platform]/build-a-backend/graphqlapi/troubleshooting/#backfill-opensearch-index-from-dynamodb-table). + +## Search and filter data + +Every model with a `@searchable` directive attached generates a new "search" GraphQL query to search and filter for records. The example above provides you the ability to search for "Student" records using a "searchStudents" query. + +The `filter` parameter allows you to filter for records based on their field values. + +```graphql +query SearchStudentsByEmail { + searchStudents(filter: { name: { eq: "Rene Brandel" } }) { + items { + id + name + email + } + } +} +``` + +In the example above, the search result consists of students with the name "Rene Brandel" + +### Supported search operations + +| Field type | Supported search operations | +| --- | --- | +| String | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp | +| Int | ne, gt, lt, gte, lte, eq, range | +| Float | ne, gt, lt, gte, lte, eq, range | +| Boolean | eq, ne | +| Enum | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp | + +### Nested search conditions (and, or, not) + +Use the filter parameter to pass a nested `and`/`or`/`not` condition. + +```graphql +query MyQuery { + searchStudents( + filter: { + name: { wildcard: "*Brandel" } + or: [{ dateOfBirth: { lt: "2000-01-01" } }, { email: { exists: true } }] + } + ) { + items { + id + name + email + dateOfBirth + } + } +} +``` + +By default, every operation in the filter properties is `and`ed. Use the `or` or `not` properties in the search query's `filter` parameter to override this behavior. + +The query above returns a "Student" if: + +- their name ends with "Brandel" +- `and` + - their date of birth is earlier than 2000-01-01 + - `or` + - their email exists. + +## Sort search results + +Use the `sort` parameter to sort your search results by a field in ascending or descending order. The `field` argument accepts any field available on the model. The `direction` accepts either `asc` or `desc`. + +```graphql +query SearchAndSort { + searchStudents( + filter: { name: { wildcard: "*Brandel" } } + sort: { direction: desc, field: name } + ) { + items { + name + id + } + } +} +``` + +In the example above, the search result is sorted based on their `name` in a `desc`ending order. + +### Sort search result over multiple fields + +To sort over multiple fields, provide array of sort conditions. When sorting over multiple fields, the sort conditions are applied in the `sort` array's order. + +```graphql +query SearchAndSort { + searchStudents( + filter: { name: { wildcard: "*Brandel" } } + sort: [ + { field: name, direction: desc } # Sort condition #1 + { field: dateOfBirth, direction: asc } # Sort condition #2 + ] + ) { + items { + id + name + dateOfBirth + } + } +} +``` + +In the example above, the search result is first sorted by `name` in a `desc`ending order and then by `dateOfBirth` in an `asc`ending order. + +## Paginate over search results + +By default, the search result page size is 100. To customize the page size modify the `limit` parameter. Query for the `nextToken` and use it in your subsequent pagination requests: + +``` +query MyQuery { + searchTodos(nextToken: "") { # Pass in your nextToken in query + items { + description + id + name + createdAt + } + nextToken # Next token to paginate on + } +} +``` + +## Total count of search results + +Add the `total` field in your query response to get the total count of search result hits. + +```graphql +query MyQuery { + searchStudents(filter: { name: { wildcard: "*Brandel" } }) { + items { + id + } + total # Specify to get total counts + } +} +``` + +In the example above, the response's `total` field contains the total search result count for "Students" whose name ends with "Brandel". Note: `total` is calculated based on all records, irrespective of pagination configurations. + +## Aggregate values for search result (minimum, maximum, average, sum, terms) + +Use the `aggregates` parameter to get aggregate values such as "minimum", "maximum", "average", and "sum" returned in the `aggregateItems` field. Note: `aggregates` are calculated based on all records, irrespective of pagination configurations. + + + + +Provide the `min` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchStudents( + aggregates: { + type: min # Specifies that you want the "min" value + field: examsCompleted # Specifies the field for the aggregate value + name: "minimumExams" # provides a name to reference in the response field + } + filter: { name: { wildcard: "Rene*" } } + ) { + aggregateItems { + name + result { + ... on SearchableAggregateScalarResult { + value + } + } + } + } +} +``` + +In the example above, the response includes the minimum value of "examsCompleted" for all Students whose name starts with "Rene". + +```graphql +{ + "data": { + "searchStudents": { + "aggregateItems": [{ + "name": "minimumExams", + "result": { + "value": 7 + } + }] + } + } +} +``` + + + + + +Provide the `max` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchStudents( + aggregates: { + type: max # Specifies that you want the "max" value + field: examsCompleted # Specifies the field for the aggregate value + name: "maximumExams" # provides a name to reference in the response field + } + filter: { name: { wildcard: "Rene*" } } + ) { + aggregateItems { + name + result { + ... on SearchableAggregateScalarResult { + value + } + } + } + } +} +``` + +In the example above, the response includes the maximum value of "examsCompleted" for all Students whose name starts with "Rene". + +```graphql +{ + "data": { + "searchStudents": { + "aggregateItems": [{ + "name": "maximumExams", + "result": { + "value": 28 + } + }] + } + } +} +``` + + + + + +Provide the `avg` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchStudents( + aggregates: { + type: avg # Specifies that you want the "avg" value + field: examsCompleted # Specifies the field for the aggregate value + name: "averageExams" # provides a name to reference in the response field + } + filter: { name: { wildcard: "Rene*" } } + ) { + aggregateItems { + name + result { + ... on SearchableAggregateScalarResult { + value + } + } + } + } +} +``` + +In the example above, the response includes the average value of "examsCompleted" for all Students whose name starts with "Rene". + +```graphql +{ + "data": { + "searchStudents": { + "aggregateItems": [{ + "name": "averageExams", + "result": { + "value": 17.3 + } + }] + } + } +} +``` + + + + + +Provide the `sum` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchStudents( + aggregates: { + type: sum # Specifies that you want the "sum" value + field: examsCompleted # Specifies the field for the aggregate value + name: "examsSum" # provides a name to reference in the response field + } + filter: { name: { wildcard: "Rene*" } } + ) { + aggregateItems { + name + result { + ... on SearchableAggregateScalarResult { + value + } + } + } + } +} +``` + +In the example above, the response includes the sum of all "examsCompleted" values for all Students whose name starts with "Rene". + +```graphql +{ + "data": { + "searchStudents": { + "aggregateItems": [{ + "name": "examsSum", + "result": { + "value": 392 + } + }] + } + } +} +``` + + + + +Provide the `terms` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchTodos( + aggregates: { field: description, type: terms, name: "descriptionTerms" } + ) { + aggregateItems { + result { + ... on SearchableAggregateBucketResult { + __typename + buckets { + doc_count + key + } + } + } + name + } + } +} +``` + +In the example above, the response includes the terms for the description and their count: + +```graphql +{ + "data": { + "searchTodos": { + "aggregateItems": [ + { + "result": { + "__typename": "SearchableAggregateBucketResult", + "buckets": [{ + "doc_count": 2, + "key": "Shopping list" + }, { + "doc_count": 1, + "key": "Me next todo" + }] + }, + "name": "descriptionTerms" + } + ] + } + } +} +``` + + + + +## Set up OpenSearch for production environments + +By default, Amplify CLI will configure a t2.small instance type. This is great for getting started and prototyping but not recommended to be used in the production environment per the [OpenSearch best practices](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp.html). + +To configure the OpenSearch instance type per environment: + +1. Run `amplify env add` to create a new environment (e.g. "prod") +2. Edit the `amplify/team-provider-info.json` file and set `OpenSearchInstanceType` to the instance type that works for your application + +```json +{ + "dev": { + "categories": { + "api": { + "": { + "OpenSearchInstanceType": "t2.small.elasticsearch" + } + } + } + }, + "prod": { + "categories": { + "api": { + "": { + "OpenSearchInstanceType": "t2.medium.elasticsearch" + } + } + } + } +} +``` + +3. Deploy your changes with `amplify push` + +Learn more about Amazon OpenSearch Service instance types [here](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-instance-types.html). + +## How it works + +The `@searchable` directive streams the data of an @model type to Amazon OpenSearch Service and configures search resolvers to query against OpenSearch. + +Type definition of the `@searchable` directive: + +```graphql +# Streams data from DynamoDB to OpenSearch and exposes search capabilities. +directive @searchable(queries: SearchableQueryMap) on OBJECT +input SearchableQueryMap { + search: String +} +``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx new file mode 100644 index 00000000000..f1c12938cfc --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx @@ -0,0 +1,92 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Troubleshooting', + description: 'Add authorization rules to your GraphQL schema to control access to your data.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/troubleshooting/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +## Deploying multiple index changes at once + +You can make `@index` updates on one "amplify push". Under the hood, Amplify CLI needs to locally sequence multiple individual deployments to your DynamoDB table because each Global Secondary Index (GSI), managed by `@index`, change requires time to create the new index. + +If your deployment fails locally when updating multiple GSIs, you'll have the ability to run: + +- `amplify push --iterative-rollback` to rollback the last-known-good state +- `amplify push --force` to rollback the last-known-good state and try redeploying your changes again using. + +```console +Attempting to mutate more than 1 global secondary index at the same time. +``` + +If you're running into the error above during `amplify push`, it is likely that you don't have this feature enabled. To enable multiple GSI updates, set the "enableIterativeGsiUpdates" feature flag to true in your `amplify/cli.json` file. + +## Backfill OpenSearch index from DynamoDB table + +When you add `@searchable` to a `@model` type with existing data, then you need to backfill the OpenSearch index. Download the following Python script to help you backfill your OpenSearch index: + +[DynamoDB to OpenSearch backfill script](https://raw.githubusercontent.com/aws-amplify/amplify-category-api/main/packages/graphql-elasticsearch-transformer/scripts/ddb_to_es.py) + +The script creates an event stream of your DynamoDB records and sends them to your OpenSearch Index. Execute the script with the following parameters to initiate the backfill: + +| Parameter | Description | Required | +| --- | --- | --- | +| `--rn` | DynamoDB table region. See [AWS Regions](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) for available options | Yes | +| `--tn` | DynamoDB table name. Format: `{@model type name}-{AppSync API ID}-{Amplify environment}` | Yes | +| `--lf` | ARN of the "DynamoDB to OpenSearch streaming" Lambda function. Format: `arn:aws:lambda:{region}:{AWS Account ID}:function:amplify-{Amplify project name}-{Amplify environment}-{Random string}-OpenSearchStreamingLambd-{Random string}` | Yes | +| `--esarn` | ARN of the DynamoDB table stream. Format: `arn:aws:dynamodb:{region}:{AWS Account ID}:table/{@model type name}-{AppSync API ID}-{Amplify environment}/stream/{Table creation date}` | Yes | +| `--ak` | AWS Access Key ID. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | +| `--sk` | AWS Secret Access Key. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | +| `--st` | AWS Session Token. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | + +In the example below, the `Post` table data in `us-west-2` gets backfilled in the OpenSearch index. + +```bash +python3 ddb_to_es.py \ + --rn 'us-west-2' \ # Use the region in which your table and OpenSearch domain reside + --tn 'Post-XXXX-dev' \ # Table name + --lf 'arn:aws:lambda:us-west-2:<...>:function:amplify-<...>-OpenSearchStreamingLambd-<...>' \ # Lambda function ARN, find the DynamoDB to OpenSearch streaming functions, copy entire ARN + --esarn 'arn:aws:dynamodb:us-west-2:<...>:table/Post-<...>/stream/2019-20-03T00:00:00.350' # Event source ARN, copy the full DynamoDB table ARN +``` + +## Index with multiple sort key fields + +When you add an `@index` directive with 2 or more sort key fields, you will need to backfill the new composite sort key for existing data. With `@index(sortKeyFields: ["status", "date"])`, you will need to backfill the `status#date` field with composite key values made up of each object's `status` and `date` fields joined by a `#`. You do not need to backfill data for `@index` directives with zero to one sort key field(s). + +## Maximum Global Secondary Index limit for DynamoDB table exceeded + +If you have increased the [soft limit of GSI per table](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html) in DynamoDB beyond 20, then iterative deployments will fail with the following warning. + +```console +DynamoDB can have max of 20 GSIs. +To disable this check, use the --disable-gsi-limit-check option. +``` + +In order to disable this behavior, update your deploy scripts or ci commands to include the `--disable-gsi-limit-check` option to circumvent this validation during pushes. + +```bash +amplify push --disable-gsi-limit-check +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx index a630ae0f945..0c42691fa7f 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx @@ -1,7 +1,7 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { - title: 'Advanced workflows', + title: 'Advanced Workflows', description: "Learn more about advanced workflows in Amplify's API category", platforms: [ 'flutter', diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx index 2cefbe378b8..b395a0f5ca6 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx @@ -4,7 +4,6 @@ export const meta = { title: 'Customize your auth rules', description: "Learn more about how to configure authorization modes in Amplify's API category", platforms: [ - 'flutter', 'javascript', 'react-native', 'swift', From 5a2a7956534139541024d0d45c7982b3b1f98e24 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:55:43 -0700 Subject: [PATCH 04/88] chore: Storage and functions v1 transfer from current to prev --- src/directory/directory.mjs | 12 + .../functions/build-options/index.mdx | 141 +++++ .../functions/configure-options/index.mdx | 114 ++++ .../functions/environment-variables/index.mdx | 86 +++ .../functions/graphql-from-lambda/index.mdx | 504 ++++++++++++++++++ .../prev/build-a-backend/functions/index.mdx | 36 ++ .../functions/layers/index.mdx | 229 ++++++++ .../functions/secrets/index.mdx | 105 ++++ .../functions/set-up-function/index.mdx | 161 ++++++ .../restapi/configure-rest-api/index.mdx | 286 ++++++++++ .../restapi/override-api-gateway/index.mdx | 318 +++++++++++ .../restapi/test-api/index.mdx | 178 +++++++ .../storage/configure-storage/index.mdx | 186 +++++++ .../build-a-backend/storage/import/index.mdx | 256 +++++++++ .../index.mdx | 110 ++++ .../build-a-backend/storage/move/index.mdx | 26 + .../build-a-backend/storage/copy/index.mdx | 20 +- .../storage/get-properties/index.mdx | 20 +- .../storage/transfer-acceleration/index.mdx | 89 +++- 19 files changed, 2874 insertions(+), 3 deletions(-) create mode 100644 src/pages/[platform]/prev/build-a-backend/functions/build-options/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/functions/configure-options/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/functions/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/functions/layers/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/functions/secrets/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index ab55076eb7e..5ebd48dc3dc 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -1688,6 +1688,9 @@ export const directory = { { path: 'src/pages/gen1/[platform]/build-a-backend/troubleshooting/migrate-from-javascript-v5-to-v6/index.mdx' }, + { + path: 'src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx' + }, { path: 'src/pages/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/index.mdx' } @@ -1734,6 +1737,9 @@ export const directory = { { path: 'src/pages/gen1/[platform]/build-ui/formbuilder/lifecycle/index.mdx' }, + { + path: 'src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx' + }, { path: 'src/pages/gen1/[platform]/build-ui/formbuilder/call-to-action/index.mdx' }, @@ -1757,12 +1763,18 @@ export const directory = { { path: 'src/pages/gen1/[platform]/build-ui/uibuilder/slots/index.mdx' }, + { + path: 'src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx' + }, { path: 'src/pages/gen1/[platform]/build-ui/uibuilder/theming/index.mdx' }, { path: 'src/pages/gen1/[platform]/build-ui/uibuilder/responsive/index.mdx' }, + { + path: 'src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx' + }, { path: 'src/pages/gen1/[platform]/build-ui/uibuilder/override/index.mdx' }, diff --git a/src/pages/[platform]/prev/build-a-backend/functions/build-options/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/build-options/index.mdx new file mode 100644 index 00000000000..0b4019f0afd --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/functions/build-options/index.mdx @@ -0,0 +1,141 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Build options', + description: "Use build options for the function category in Amplify to execute a script before a function is deployed, for example, to transpile Typescript or ES6 with Babel into a format that is supported by the AWS Lambda's node runtime.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/build-options/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + + + + +In some cases, it might be necessary to execute a script before a function is deployed, e.g. to transpile Typescript or ES6 with Babel or with `tsc` into a format that is supported by the AWS Lambda's node runtime. `amplify push` will look for a `script` definition in the project root's `package.json` with the name `amplify:` and run it right after `npm install` is called in the function resource's `src` directory. + +**Example: Transpiling Typescript code with TSC** + +Make sure you have the `tsc` command installed globally by running `npm install -g typescript` or locally by running `npm install --save-dev typescript` + +Let's say, a function resource has been created with `amplify function add` and it is called `generateReport`. The ES6 source code for this function is located in `amplify/backend/function/generateReport/lib` and the resource's `src` directory only contains the auto-generated `package.json` for this function. In order to compile TypeScript, you have to add the following script definition to your project root's `package.json`: + +```json +{ + "scripts": { + "amplify:generateReport": "cd amplify/backend/function/generateReport && tsc -p ./tsconfig.json && cd -" + }, +} +``` + +Navigate into `amplify/backend/function/generateReport` and create `tsconfig.json` then add the following to it: + +```json +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "lib": ["dom", "esnext"], + "module": "commonjs", + "moduleResolution": "node", + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./src", + "baseUrl": "./", + "rootDir": "./lib", + "paths": { + "src": ["./lib"] + } + } +} +``` + +**Note:** It is important to note that if you are using `aws-sdk` in your TypeScript file, you will get a timeout if you attempt to import it with the following: + +```js +import AWS from 'aws-sdk'; +``` + +Change to this: + +```js +import * as AWS from 'aws-sdk'; +``` + +Once you run `amplify push`, the `amplify:generateReport` script will be executed, either by `yarn` or by `npm` depending on the existence of a `yarn.lock` file in the project root directory. + +**Example: Transpiling ES6 code with Babel** + +Let's say, a function resource has been created with `amplify function add` and it is called `generateReport`. The ES6 source code for this function is located in `amplify/backend/function/generateReport/lib` and the resource's `src` directory only contains the auto-generated `package.json` for this function. In order to run Babel, you have to add the following script definition and dev dependencies to your project root's `package.json`: + +```json +{ + "scripts": { + "amplify:generateReport": "cd amplify/backend/function/generateReport && babel lib -d src && cd -" + }, + "devDependencies": { + "@babel/cli": "^7.5.5", + "@babel/preset-env": "^7.5.5", + } +} +``` + +Babel needs to be configured properly so that the transpiled code can be run on AWS Lambda. This can be done by adding a `.babelrc` file to the resource folder (`amplify/backend/function/generateReport/.babelrc` in this case): + +```json +{ + "presets": [ + [ + "env", + { + "exclude": ["transform-regenerator"], + "targets": { + "node": "10.18" + } + } + ] + ], + "plugins": [ + "transform-async-to-generator", + "transform-exponentiation-operator", + "transform-object-rest-spread" + ] +} +``` + +Once you run `amplify push`, the `amplify:generateReport` script will be executed, either by `yarn` or by `npm` depending on the existence of a `yarn.lock` file in the project root directory. + + + + + +There are no existing build options for Python functions. The process of building and packaging Python functions is in line with Amazon's [existing documentation](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-venv) for manually creating a Lambda deployment package which depends on a virtual environment. + +Amplify will run `pipenv install` in your function's source directory during builds using either Pipenv's default virtual environment, or whichever virtual environment happens to be active. Then, during the packaging stage, the contents of the `site-packages` directory for that virtual environment will be zipped up along with the function-specific files. + +The contents of the Python build can include local development dependencies (e.g. for testing) in addition to those necessary for your function to run. Packages installed as "editable" (using the `-e` flag) will not be packaged, as they are represented as an `.egg-link` file pointing to the local, editable code of the dependency. + + + + diff --git a/src/pages/[platform]/prev/build-a-backend/functions/configure-options/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/configure-options/index.mdx new file mode 100644 index 00000000000..8d6b61eab10 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/functions/configure-options/index.mdx @@ -0,0 +1,114 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure Lambda function settings', + description: 'Learn how to configure custom settings for your Lambda function', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/configure-options/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +You may want to override the Amplify CLI default configurations for your Lambda function or configure changes not available within the `amplify add function` workflow. + +*Example*: When creating a `Node.js` function, the CLI will automatically configure a runtime version, a default memory size, and more. There are a few things you may want to override or configure: + +1. Runtime +2. Memory size +3. Environment variables + +Let's look at how to update all of these things. + +## Updating the Runtime + +You may want to tweak the runtime version to be either a newer or older version than the Amplify-generated default. + +Let's say we've deployed a Lambda function using a Node.js runtime and you want to modify the version of the runtime to be `14.x`. + +To do so, open __amplify/backend/function/function-name/function-name-cloudformation-template.json__ and set the `Runtime` property in the `LambdaFunction` resource to: + +```json +"Resources": { + "LambdaFunction": { + ... + "Properties": { + "Runtime": "nodejs14.x", // Runtime now set to 14.x + "Layers": [], + } + ... + } + }, +} +``` + +Next, deploy the updates using the Amplify CLI: + +```sh +amplify push +``` + +## Updating the default memory size + +When you deploy a function with Amplify, the default memory size will be set to a low setting (128MB). Often you will want to increase the default memory size in order to improve performance. A popular memory setting in Lambda is 1024MB as it speeds the function noticeably while usually keeping the cost the same or close to it. + +To update the memory size, open __amplify/backend/function/function-name/function-name-cloudformation-template.json__ and set the `MemorySize` property in the `LambdaFunction` resource: + +```json +"Resources": { + "LambdaFunction": { + ... + "Properties": { + "Runtime": "nodejs14.x", + "MemorySize": 1024, // Memory size now set to 1024 MB + "Layers": [], + } + ... + } + }, +} +``` + +Next, deploy the updates using the Amplify CLI: + +```sh +amplify push +``` + +_To learn more about optimizing resources allocation for Lambda functions, check out [this](https://dev.to/aws/deep-dive-finding-the-optimal-resources-allocation-for-your-lambda-functions-35a6) blog post._ + +## Setting an environment variable + +A very common scenario is the need to set and use an environment variable in your Lambda function. + +There are generally two types of environment variables: +- [Secret values (example: access keys, API keys etc.)](/[platform]/build-a-backend/functions/secrets/) +- [Non-secret values (example: endpoint information, locale information etc.)](/[platform]/build-a-backend/functions/environment-variables/) + + + +To view all configuration options available in AWS Lambda, check out the documentation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-environment.html) + +To learn more about extending the Amplify CLI with custom resources, check out the documentation [here](/[platform]/tools/cli/custom/cdk/) + + diff --git a/src/pages/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx new file mode 100644 index 00000000000..dea2617a370 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx @@ -0,0 +1,86 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Environment variables', + description: 'Configure environment variables for AWS Lambda functions', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/environment-variables/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +Amplify CLI allows you to configure environment variables for your Lambda functions. Each Amplify environment can have a different environment variable value. This enables use cases such as switching between dev and prod URLs depending on the environment. + +> Environment variables should NOT be used for storing sensitive configuration values such as database passwords, API keys, or access tokens. Use [function secrets configuration](/[platform]/build-a-backend/functions/secrets/) instead! + +## Configuring environment variables +To configure a new function with environment variables, run `amplify add function`, select `yes` to the advanced settings prompt and select `yes` to the environment variables configuration prompt. From there, you will be able to specify a key and value for the environment variable. + +```console +$ amplify add function +... +? Do you want to configure advanced settings? Yes +... +? Do you want to configure environment variables for this function? Yes +? Enter the environment variable name: API_URL +? Enter the environment variable value: https://example.com/test +? Select what you want to do with environment variables: (Use arrow keys) + Add new environment variable + Update existing environment variables + Remove existing environment variables +> I'm done +``` + +To configure environment variables for an existing function, run `amplify update function`, and select `Environment variables configuration`. You can then add, update, or remove environment variables. + +```console +$ amplify update function +... +? Which setting do you want to update? + Resource access permissions + Scheduled recurring invocation + Lambda layers configuration +> Environment variables configuration + Secret values configuration +? Select what you want to do with environment variables: +> Add new environment variable + Update existing environment variables + Remove existing environment variables + I'm done +``` + +## Multi-environment flows +When creating a new Amplify environment using `amplify env add`, Amplify CLI asks if you want to apply all environment variable values to the new environment or modify them. If you choose to apply the existing values, you can still make edits anytime by running `amplify update function`. + +When creating a new Amplify environment using `amplify env add --yes`, Amplify CLI will apply all environment variable values from the current environment to the new environment. + +In multi-environment workflows, you may have added a new environment variable in one Amplify environment and then checked out a different Amplify environment. In this case, on the next `amplify push`, Amplify CLI will detect that there is a new environment variable that does not have a value specified in the current environment and prompt for one. +Running `amplify push --yes` in this case will fail with a message explaining the missing environment variable values. + +In [**git-based** multi-environment workflows](/[platform]/tools/cli/teams/), you may run into errors during deployment. For example, this happens when you add an environment variable in `envA` (corresponding to a git branch `branchA`), then `amplify checkout envB` and `git checkout branchB` and `git merge` branchA into branchB. Upon pushing `envB`, Amplify CLI detects that a new environment variable has been added but can't infer a value for it. To resolve this issue, run the following commands in the terminal: + +1. `amplify env checkout ` +2. `amplify push` - when prompted, enter a new value for the environment variable(s) +3. `git commit` +4. `git push` diff --git a/src/pages/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx new file mode 100644 index 00000000000..51cbb4bed84 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx @@ -0,0 +1,504 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Call a GraphQL API from a Lambda function', + description: 'Interact with a GraphQL API from a Lambda function.', + platforms: [ + 'flutter', + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +You can call an AppSync GraphQL API from a Node.js app or a Lambda function. Take a basic `Todo` app as an example: + +```graphql +type Todo @model @auth(rules: [{ allow: public }]) { + name: String + description: String +} +``` + +This API will have operations available for `Query`, `Mutation`, and `Subscription`. Let's take a look at how to perform both a **query** as well as a **mutation** from a Lambda function using Node.js. + +## Utilizing Lambda function template (IAM authorization) + +First, create a Lambda function with `amplify add function` and choose the `AppSync - GraphQL API request (with IAM)` to get started. Be sure to grant access to your GraphQL API when prompted by the CLI to grant access to other resources in the project. Alternatively, you can create the [function from scratch](#create-from-scratch). + +```console +amplify add function +? Select which capability you want to add: Lambda function (serverless function) +? Provide an AWS Lambda function name: myfunction +? Choose the runtime that you want to use: NodeJS +? Choose the function template that you want to use: AppSync - GraphQL API request (with IAM) + +Available advanced settings: +- Resource access permissions +- Scheduled recurring invocation +- Lambda layers configuration +- Environment variables configuration +- Secret values configuration + +? Do you want to configure advanced settings? Yes +? Do you want to access other resources in this project from your Lambda function? Yes +? Select the categories you want this function to have access to. api +? Select the operations you want to permit on Query, Mutation, Subscription + +You can access the following resource attributes as environment variables from your Lambda function + API__GRAPHQLAPIENDPOINTOUTPUT + API__GRAPHQLAPIIDOUTPUT + API__GRAPHQLAPIKEYOUTPUT + ENV + REGION +``` + + + +The function can only be added when the GraphQL API with IAM authorization exists. + + + +### Create from scratch + +```console +amplify add function +? Select which capability you want to add: Lambda function (serverless function) +? Provide an AWS Lambda function name: myfunction +? Choose the runtime that you want to use: NodeJS +? Choose the function template that you want to use: Hello World + +Available advanced settings: +- Resource access permissions +- Scheduled recurring invocation +- Lambda layers configuration +- Environment variables configuration +- Secret values configuration + +? Do you want to configure advanced settings? Yes +? Do you want to access other resources in this project from your Lambda function? Yes +? Select the categories you want this function to have access to. api +? Select the operations you want to permit on Query, Mutation, Subscription + +You can access the following resource attributes as environment variables from your Lambda function + API__GRAPHQLAPIENDPOINTOUTPUT + API__GRAPHQLAPIIDOUTPUT + API__GRAPHQLAPIKEYOUTPUT + ENV + REGION +``` + +The examples on this page use [`node-fetch`](https://www.npmjs.com/package/node-fetch) to make a HTTP request to your GraphQL API. When the Node.js v18 runtime is released for Lambda this dependency can be removed in favor of [native `fetch`](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#fetch) To get started, add the `node-fetch` module as a dependency: + +**CommonJS**: + +For functions written using CommonJS, you will need to install version 2 of `node-fetch` + +```diff +{ + "name": "myfunction", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "node-fetch": "2" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +**ESM**: + +```diff +{ + "name": "myfunction", ++ "type": "module", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "node-fetch": "^3.2.3" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +## Query + +Using an API Key for authenticating your requests, you can query the GraphQL API to get a list of all `Todo`s. To paginate over the list queries, you need to pass in a `limit` and `nextToken` on the `listTodos` query. See more at [GraphQL pagination ](/[platform]/build-a-backend/graphqlapi/query-data/). + +{/* prettier-ignore */} +```js +import { default as fetch, Request } from 'node-fetch'; + +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; + +const query = /* GraphQL */ ` + query LIST_TODOS { + listTodos { + items { + id + name + description + } + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + /** @type {import('node-fetch').RequestInit} */ + const options = { + method: 'POST', + headers: { + 'x-api-key': GRAPHQL_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ query }) + }; + + const request = new Request(GRAPHQL_ENDPOINT, options); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 400; + body = { + errors: [ + { + status: response.status, + message: error.message, + stack: error.stack + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` + +## Mutation + +In this example you will create a mutation showing how to pass in variables as arguments to create a `Todo` record. + +{/* prettier-ignore */} +```js +import { default as fetch, Request } from 'node-fetch'; + +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; + +const query = /* GraphQL */ ` + mutation CREATE_TODO($input: CreateTodoInput!) { + createTodo(input: $input) { + id + name + createdAt + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + const variables = { + input: { + name: 'Hello, Todo!' + } + }; + + /** @type {import('node-fetch').RequestInit} */ + const options = { + method: 'POST', + headers: { + 'x-api-key': GRAPHQL_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ query, variables }) + }; + + const request = new Request(GRAPHQL_ENDPOINT, options); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 400; + body = { + errors: [ + { + status: response.status, + message: error.message, + stack: error.stack + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` + +## IAM Authorization + +Let's take a look at another example schema that uses `iam` authorization. + +```graphql +type Todo @model @auth(rules: [{ allow: private, provider: iam }]) { + name: String + description: String +} +``` + +The CLI will automatically configure the Lambda execution IAM role to call the GraphQL API. Before writing your Lambda function you will first need to install the appropriate AWS SDK v3 dependencies: + +```diff +{ + "name": "myfunction", ++ "type": "module", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-crypto/sha256-js": "^2.0.1", ++ "@aws-sdk/credential-provider-node": "^3.76.0", ++ "@aws-sdk/protocol-http": "^3.58.0", ++ "@aws-sdk/signature-v4": "^3.58.0", ++ "node-fetch": "^3.2.3" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +Then, the following example will sign the request to call the GraphQL API using IAM authorization. + +{/* due to placeholder text */} + +{/* prettier-ignore */} +```js +import crypto from '@aws-crypto/sha256-js'; +import { defaultProvider } from '@aws-sdk/credential-provider-node'; +import { SignatureV4 } from '@aws-sdk/signature-v4'; +import { HttpRequest } from '@aws-sdk/protocol-http'; +import { default as fetch, Request } from 'node-fetch'; + +const { Sha256 } = crypto; +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; + +const query = /* GraphQL */ ` + query LIST_TODOS { + listTodos { + items { + id + name + description + } + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + const endpoint = new URL(GRAPHQL_ENDPOINT); + + const signer = new SignatureV4({ + credentials: defaultProvider(), + region: AWS_REGION, + service: 'appsync', + sha256: Sha256 + }); + + const requestToBeSigned = new HttpRequest({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + host: endpoint.host + }, + hostname: endpoint.host, + body: JSON.stringify({ query }), + path: endpoint.pathname + }); + + const signed = await signer.sign(requestToBeSigned); + const request = new Request(GRAPHQL_ENDPOINT, signed); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 500; + body = { + errors: [ + { + message: error.message + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` + +### CommonJS + +When writing functions with CommonJS, you will need to install version 2 of `node-fetch`: + +```diff +{ + "name": "myfunction", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-crypto/sha256-js": "^2.0.1", ++ "@aws-sdk/credential-provider-node": "^3.76.0", ++ "@aws-sdk/protocol-http": "^3.58.0", ++ "@aws-sdk/signature-v4": "^3.58.0", ++ "node-fetch": "2" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +Similar to the example above you can now write your handler. The difference here is the use of `require()` rather than `import ... from` + +```js +const { Sha256 } = require('@aws-crypto/sha256-js'); +const { defaultProvider } = require('@aws-sdk/credential-provider-node'); +const { SignatureV4 } = require('@aws-sdk/signature-v4'); +const { HttpRequest } = require('@aws-sdk/protocol-http'); +const { default: fetch, Request } = require('node-fetch'); + +const GRAPHQL_ENDPOINT = + process.env.API_ < YOUR_API_NAME > _GRAPHQLAPIENDPOINTOUTPUT; +const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; + +const query = /* GraphQL */ ` + query LIST_TODOS { + listTodos { + items { + id + name + description + } + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +exports.handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + const endpoint = new URL(GRAPHQL_ENDPOINT); + + const signer = new SignatureV4({ + credentials: defaultProvider(), + region: AWS_REGION, + service: 'appsync', + sha256: Sha256 + }); + + const requestToBeSigned = new HttpRequest({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + host: endpoint.host + }, + hostname: endpoint.host, + body: JSON.stringify({ query }), + path: endpoint.pathname + }); + + const signed = await signer.sign(requestToBeSigned); + const request = new Request(GRAPHQL_ENDPOINT, signed); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 500; + body = { + errors: [ + { + message: error.message + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` diff --git a/src/pages/[platform]/prev/build-a-backend/functions/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/index.mdx new file mode 100644 index 00000000000..be5e840404b --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/functions/index.mdx @@ -0,0 +1,36 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; +import { getChildPageNodes } from '@/utils/getChildPageNodes'; + +export const meta = { + title: 'Functions', + description: 'Build and deploy serverless functions to perform various tasks, such as handling API requests, executing backend logic, or integrating with other AWS services.', + platforms: [ + 'flutter', + ], + route: '/[platform]/build-a-backend/functions', + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + const childPageNodes = getChildPageNodes(meta.route); + return { + props: { + platform: context.params.platform, + meta, + childPageNodes + } + }; +} + + diff --git a/src/pages/[platform]/prev/build-a-backend/functions/layers/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/layers/index.mdx new file mode 100644 index 00000000000..6ebdbfaf42b --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/functions/layers/index.mdx @@ -0,0 +1,229 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Reuse code and assets using layers', + description: "Use Amplify CLI's Lambda layer capability to reuse code & assets across functions.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/layers/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +Lambda layers allow you to pull common code & assets for your Lambda function into a centralized location. With Lambda layers you can: + +1. _*Re-use your code & assets*_: Your Lambda functions can leverage these layers to reuse shared code & assets across functions +2. _*Speed up function deployments*_: Iterating on your Lambda function will be significantly faster because it can be independently updated from the layer which usually contains the bulk of large static content + +> **Known limitation**: Functions using a layer can't be mocked locally using `amplify mock`. We recommend you to create a dev environment and test the functions inside the AWS Lambda console. + +![Lambda layer architecture diagram](/images/layers-architecture.gif) + +The general workflow breaks down into the following steps: + +1. Create a Lambda layer +2. Add shared code & assets to the layer +3. Add the Lambda layer to a function +4. Deploy the layer & function + +## Create a Lambda Layer + +To create a layer, run the following command within your Amplify project: + +```bash +amplify add function +``` + +Select `Lambda layer` as the capability to add + +```console +? Select which capability you want to add: +> Lambda layer (shared code & resource used across functions) +``` + +```console +? Provide a name for your Lambda layer: (layer-name) +? Choose the runtime that you want to use: (Use arrow keys) +❯ NodeJS + Python +``` + +Next, you'll be guided through a workflow to provide a **layer name**, and select a **supported runtime**. Currently Amplify CLI provides NodeJS or Python runtime support for layers. + +```console +? The current AWS account will always have access to this layer. + Optionally, configure who else can access this layer. (Hit to skip) +◯ Specific AWS accounts +◯ Specific AWS organization +◯ Public (everyone on AWS can use this layer) +``` + +After that, you'll be asked to configure your **layer's permission**. + +The **current AWS account will always have access to this layer**. +In addition, the customer can configure access for: + +- **Specific AWS accounts**: provide a comma-separated list of AWS Account IDs to provide access to them. +- **Specific AWS organizations**: provide a comma-separated list of AWS Organization IDs to provide access to them. *AWS Organization IDs start with `o-`.* +- **Public**: make this layer available for everyone AWS. Anyone in AWS can reference this layer using its ARN. + +```console +Next steps: +Move your libraries to the following folder: +[NodeJS]: amplify/backend/function//lib/nodejs + +Include any files you want to share across runtimes in this folder: +amplify/backend/function//opt + +"amplify function update " - configure a function with this Lambda layer +"amplify push" - builds all of your local backend resources and provisions them in the cloud +``` + +Your Lambda layer is ready to use after the permissions are set up. + +## Add shared code & assets + +Now that your layer is set up, you'll see a new folder with the `layer-name` added to `amplify/backend/function/`. The respective runtime's folder structure is autogenerated. + +### Add shared code + + + + + +A `nodejs` folder is auto-generated for you. In there you'll find an empty `package.json` file and a `node_modules` folder. If you want to offload other node_modules you can either: + +1. `cd` into the `nodejs` folder and add the dependencies into the `package.json` file, or +2. move all your existing function's `node_modules` content into the layer's `node_modules` folder + +Any dependency listed within the layer's `package.json` file will be installed and packaged during `amplify push`. + +Any node module that is in the layer's `node_modules` folder can be accessed from the function as if the node module is in the function's `node_modules` folder. + +*In order to take advantage of Lambda layer's for your NodeJS function, you don't even need to update your function's code!* + + + + + +A `python` folder is auto-generated for you. In there you'll find an empty `Pipfile` file. Any packages listed within the layer's `Pipfile` file will be installed and packaged during `amplify push`. You can `import` these packages from within your Python function just like any other package within your Python function. + + + + + +### Add shared assets + +Any assets like large images or other files that you want to share across various functions can be placed in the `amplify/backend/function//opt/` folder. Your function's code can import any assets by looking for files in the `/opt/` path. + +### Lambda layer versions + +Every time `amplify push` or `amplify update function` is run, Amplify CLI checks if a layer's content has changed and automatically creates a new *layer version*. Layer versions are immutable and functions always use a specific layer version. + +In order to speed up deployments when vast amount of node_modules exist, Amplify CLI scans only for changes within each module's `package.json` file. If you don't see Amplify CLI detect your latest changes, verify that at least of your node module's `package.json` content has changed. + +## Add a layer to a function + +You can either create a new function and add Lambda layers by running `amplify add function` or add layers to an existing function using `amplify update function`. Select `Lambda function` when prompted and you'll be presented the following question during the guided flow: + +```console +... +? Do you want to enable Lambda layers for this function? Yes +? Provide existing layers or select layers in this project to access from this function (pick up to 5): + ◯ Provide existing Lambda layer ARNs +❯◉ myamplifylayer1 + ◯ myamplifylayer2 +``` + +You can either add an existing layer in AWS by referencing its ARN or select a layer from your Amplify project that's listed below. + +```console +? Select a version for myamplifylayer1: +❯ Always choose latest version + 2: Updated layer version 2021-06-08T05:33:42.651Z + 1: Updated layer version 2021-06-08T05:30:43.101Z +``` + +When adding a layer from your Amplify project, you'll also be able to select a specific layer version or always choose the latest layer version. The largest layer version number represents the most recent changes. + +```console +? Modify the layer order: +(Layers with conflicting files will overwrite contents of layers earlier in the list): +- layer2 +- layer3 +- layer6 +- +- +``` + +Given that layers can have overlapping contents, the order of the layer matters. You can adjust the layer's order if needed in the next step. + +Now, you've successfully added a layer to your function. + +## Deploy Lambda layers & functions with Lambda layers + +Once you're ready with your changes in your layer and functions, you can deploy them by running `amplify push`. + +If a layer’s content has been updated and it has permissions associated, Amplify CLI will prompt you whether you want to carry the permissions forward to a newer version. + +```console +Content changes in Lambda layers detected. +Suggested configuration for new layer versions: + +myamplifylayer1 + - Description: Updated layer version 2021-06-08T05:33:42.651Z + +? Accept the suggested layer version configurations? (Y/n) +``` + +During `amplify push`, you get to modify the layer version description. By default, Amplify CLI will populate the description as `Updated layer version `. + +## Update layer content + +Any file changes within a layer's folder are automatically tracked by Amplify CLI. If there are changes available, the Amplify CLI will create a new layer version with the changes. + +## Update layer settings + +You can update layer's permissions by running `amplify update function` and selecting `Lambda layer`. + +Next, you'll be prompted to select the layer for which you want to update the settings for. + +#### Note: Update Layer Permissions from Public to Specific + - To update a lambda layer from Public access to Specific (Account/Organization) access, please remember to remove Public access by **un-selecting** the option in the 'amplify update' CLI flow before selecting a specific AWS account/organization. + - If you have already selected 'Public' access, just adding additional 'specific' AWS accounts/organizations will not have any effect on the Lambda Layer configuration. It will not automatically remove Public access. + +## Remove a layer + +To remove a Lambda layer, run the `amplify function remove` command and select `Lambda layers`. Next, you'll be prompted to select which layer to remove. You can delete specific layer versions or all of them. + +> Warning: When you delete a layer, you can no longer configure functions to use it. However, any function that already uses the layer continues to have access to it. + +```console +? Choose the resource you would want to remove (layer) +When you delete a layer version, you can no longer configure functions to use it. +However, any function that already uses the layer version continues to have access to it. +? Choose the Layer versions you want to remove. +❯◯ 1: Updated layer version 2021-06-08T05:30:43.101Z + ◯ 2: Updated layer version 2021-06-08T05:33:42.651Z +? Are you sure you want to delete the resource? This action deletes all files related to this resource from the backend directory. (Y/n) +``` diff --git a/src/pages/[platform]/prev/build-a-backend/functions/secrets/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/secrets/index.mdx new file mode 100644 index 00000000000..fcf0f3862cc --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/functions/secrets/index.mdx @@ -0,0 +1,105 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Access secret values', + description: 'Configure Lambda functions to securely access secret values', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/secrets/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +Amplify CLI allows you to configure secret values that can be securely accessed from a Lambda function. Each Amplify environment can have a different secret value. This enables use cases such as different API keys for a dev and prod environment. Secrets should be used for values such as database passwords, API keys, and access tokens. + +> To access non-sensitive configuration values in a Lambda function, use [environment variables](/[platform]/build-a-backend/functions/environment-variables/). + +## Configuring secret values +To configure a new function with secret values, run `amplify add function`, select `yes` to the advanced settings prompt and select `yes` to the secrets configuration prompt. From there, you can specify the name and value of the secret. + +```console +$ amplify add function +... +? Do you want to configure advanced settings? Yes +... +? Do you want to configure secret values this function can access? Yes +? Enter a secret name (this is the key used to look up the secret value): API_KEY +? Enter the value for API_KEY: [hidden] +? What do you want to do? (Use arrow keys) + Add a secret + Update a secret + Remove secrets +> I'm done +``` + +To configure secrets for an existing function, run `amplify update function`, and select `Secret values configuration`. You can then add, update and remove secret values. + +```console +$ amplify update function +... +? Which setting do you want to update? + Resource access permissions + Scheduled recurring invocation + Lambda layers configuration + Environment variables configuration +> Secret values configuration +? What do you want to do? +> Add a secret + Update a secret + Remove secrets + I'm done +``` + +> Note: Amplify CLI never stores secrets locally. All secret values are immediately stored in [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) using the SecureString parameter type. + +## Accessing the values in your function +To access the secret values in your Lambda function, use the [AWS SSM GetParameter API](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html). Amplify CLI will automatically supply the SSM parameter name of the secret as an environment variable to the function. This value can be passed into the API call as the "Name" to retrieve the value. Ensure that the API call has "WithDecryption" specified as `true`. + +If your Lambda function is using the Node.js runtime, a comment block will be placed at the top of your `index.js` file with example code to retrieve the secret values. + +```js +const aws = require('aws-sdk'); + +const { Parameters } = await (new aws.SSM()) + .getParameters({ + Names: ["EXAMPLE_SECRET_1", "EXAMPLE_SECRET_2"].map(secretName => process.env[secretName]), + WithDecryption: true, + }) + .promise(); + +// Parameters will be of the form { Name: 'secretName', Value: 'secretValue', ... }[] +``` + +## Multi-environment flows +When creating a new Amplify environment using `amplify env add`, Amplify CLI asks if you want to apply all secret values to the new environment or modify them. If you choose to apply the existing values, you can still make edits anytime using `amplify update function`. + +When creating a new Amplify environment using `amplify env add --envName --yes`, Amplify CLI will apply all secret values from the current environment to the new environment. + +In multi-environment workflows, you may have added a new secret in one Amplify environment and then checked out a different Amplify environment. In this case, on the next `amplify push`. Amplify CLI will detect that there is a new secret that does not have a value specified in the current environment and prompt for one. Running `amplify push --yes` in this case will fail with a message explaining the missing secret values. + +In [**git-based** multi-environment workflows](/[platform]/tools/cli/teams/), you may run into errors during deployment. For example, this happens when you add a secret in `envA` (corresponding to a git branch `branchA`), then `amplify env checkout envB` and `git checkout branchB` and `git merge` branchA into branchB. Upon pushing `envB`, Amplify CLI detects that a new secret has been added but can't infer a value for it. To resolve this issue, run the following commands in the terminal: + +1. `amplify env checkout ` +2. `amplify push` - when prompted, enter a new value for the secret(s) +3. `git commit` +4. `git push` diff --git a/src/pages/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx new file mode 100644 index 00000000000..98144a46542 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx @@ -0,0 +1,161 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Set up a function', + description: 'Use Amplify CLI to add powerful Lambda functions to your cloud-based mobile and web app with a simple guided workflow.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter' + ], + canonicalPath: '/javascript/build-a-backend/functions/set-up-function/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +## Set up a function + +You can add a Lambda function to your project which you can use alongside a REST API or as a datasource in your GraphQL API using the [`@function` directive](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#lambda-function-resolver). + +```bash +amplify add function +``` + +```console +? Select which capability you want to add: Lambda function (serverless function) +? Provide a friendly name for your resource to be used as a label for this category in the project: lambdafunction +? Provide the AWS Lambda function name: lambdafunction +? Choose the runtime that you want to use: NodeJS +? Choose the function template that you want to use: (Use arrow keys) +> Hello world function + CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB) + Serverless express function (Integration with Amazon API Gateway) +``` + +## Function templates + +- The `Hello World function` will create a basic hello world Lambda function +- The `CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB)` function will add a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) Lambda function template for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command) +- The `Serverless express function (Integration with Amazon API Gateway)` will add a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) Lambda function template with routing enabled for your REST API paths. + +You can update the Lambda execution role policies for your function to access other resources generated and maintained by the CLI using the CLI. + +```console +$ amplify update function +Please select the Lambda Function you would want to update: lambdafunction +? Which setting do you want to update? Resource access permissions +? Select the category (Press to select, to toggle all, to invert selection) +> api + function + storage + auth +? Select the operations you want to permit on (Press to select, to toggle all, to invert selection) +> Query + Mutation + Subscription + +You can access the following resource attributes as environment variables from your Lambda function + API__GRAPHQLAPIENDPOINTOUTPUT + API__GRAPHQLAPIIDOUTPUT + API__GRAPHQLAPIKEYOUTPUT +``` + +Behind the scenes, the CLI automates populating of the resource identifiers for the selected resources as Lambda environment variables which you will see in your function code as well. This process additionally configures CRUD level IAM policies on the Lambda execution role to access these resources from the Lambda function. For instance, you might grant permissions to your Lambda function to read/write to a DynamoDB table in the Amplify project by using the above flow and the appropriate IAM policy would be set on that Lambda function's execution policy which is scoped to that table only. + +## Supported Lambda runtimes + +Amplify CLI enables you to create, test and deploy Lambda functions with the following runtimes: + +| Runtime | Default Version | Requirements | +| --- | --- | --- | +| NodeJS | 14.x | - Install [NodeJS](https://nodejs.org/en/) | +| Java | 11 | - Install [Java 11 JDK](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) and [Gradle 5+](https://docs.gradle.org/current/userguide/installation.html) | +| Go | 1.x | - Install [Go](https://golang.org/doc/install) | +| .NET Core | 3.1 | - Install [.NET Core SDK](https://docs.microsoft.com/en-us/dotnet/core/install/sdk) | +| Python | 3.8.x | - Install [python3](https://www.python.org/downloads/) and [pipenv](https://pypi.org/project/pipenv/)
- Ensure `python3` and `pipenv` commands are available in your `PATH` | + +In order to create and test Lambda functions locally, you need to have the runtime's requirements (table above) fulfilled. You'll be asked to `Choose the runtime you would like to use:` when running `amplify add function`. + +Once a runtime is selected, you can select a function template for the runtime to help bootstrap your Lambda function. + +## Access existing AWS resource from Lambda Function + +You can grant your Lambda function access to your existing resources. After running `amplify add function`, the CLI generates a `custom-policies.json` file under the folder `amplify/backend/function//`. The file is where you can specify the [resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html) and [actions](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html) that grant Lambda Function access to the specified AWS resources. + +### File Structure + +```json +[ + { + "Action": ["s3:CreateBucket"], + "Resource": ["arn:aws:s3:::*"] + } +] +``` + +**Action:** Specify the actions that are required to be granted to your AWS resource. Wild characters ‘\*’ is accepted. + +**Resource**: Specify resources that the AWS resource needs access. The resource accepts multiple Arns for a service and wild card character ‘\*’ is accepted. + +> Note: Specifying resource or action as ‘\*’ is not recommended as best practice. This gives the Amplify function resource Administrative privileges which should be avoided. + +If your Amplify resource requires access to multiple AWS services and resources, create another block to grant access to the additional services and resources. + +```json +[ + { + "Action": ["s3:CreateBucket"], + "Resource": ["arn:aws:s3:::*"] + }, + { + "Action": ["iam:GetPolicy"], + "Resource": ["arn:aws:iam:::policy/*"] + } +] +``` + +Optionally, the `Effect` field can be specified to use ‘Allow’ or ‘Deny’. If not specified the field defaults to ‘Allow’ + +```json +{ + "Action": ["s3:CreateBucket"], + "Resource": ["arn:aws:s3:::*"], + "Effect": "Allow" +} +``` + +### Multi-Environment Workflow + +To specify AWS ARN resources across environments, an optional `\${env}` parameter can be used for resources. The `\${env}` parameter in the AWS ARN resource will get populated with the current Amplify environment name at deployment. + +```json +"Resource": ["arn:aws:s3:::${env}my-bucket"] +``` + +### Next Step + +On running `amplify push` commands, the IAM policies specified in the `custom-policies.json` file will be appended to the existing IAM policy list tied to the Lambda Function's execution role. + +## Schedule recurring Lambda functions + +Amplify CLI allows you to schedule Lambda functions to be executed periodically (e.g every minute, hourly, daily, weekly, monthly or yearly). You can also formulate more complex schedules using AWS Cron Expressions such as: _“10:15 AM on the last Friday of every month”_. Review the [Schedule Expression for Rules documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions) for more details. + +To schedule your Lambda function, answer **Yes** to `Do you want to invoke this function on a recurring schedule?` in the `amplify add function` flow. Once you deploy a function, it'll create a CloudWatch Rule to periodically execute the selected Lambda function. + +For more information on files generated in the function resource folder, see [Function Category Files](/[platform]/tools/cli/reference/files/#function-category-files). diff --git a/src/pages/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx b/src/pages/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx new file mode 100644 index 00000000000..9e98241c13f --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx @@ -0,0 +1,286 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure REST API', + description: "Use Amplify CLI's simple guided workflow to add REST APIs to cloud-based web and mobile apps.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/restapi/configure-rest-api/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +The Amplify CLI provides a guided workflow to easily add, develop, test and manage REST APIs to access your AWS resources from your web and mobile applications. + +A REST API or HTTP endpoint will be composed by one or more paths. Eg: `/items`. Each path will use a Lambda function to handle HTTP requests and responses. Amplify CLI creates a single resource in Amazon API Gateway so you can handle all routes, HTTP Methods and paths, with a single Lambda function via a Lambda Proxy integration. HTTP proxy integrations forward all requests and responses directly through to your HTTP endpoint. + +Amplify CLI let's you choose either an existing Lambda function or create a new one. To kickstart your implementation, you can choose between the following templates: + +- Serverless ExpressJS function +- CRUD function for DynamoDB + +> Lambda templates use [serverless-express](https://github.com/awslabs/aws-serverless-express) and provide the building blocks to start your REST API development. + +> See the list of all [supported Lambda runtimes](/[platform]/build-a-backend/functions/set-up-function/). + +Amplify CLI allows you to restrict REST API access to + +- Only authenticated users; or +- Authenticated and Guest users +- User Pool Groups + +See a description of these user types below + +| User type | Description | +| --- | --- | +| Authenticated user | User needs to sign in to use the REST API | +| Guest user | User doesn't need to sign in to use the REST API | +| User Pool Group | User needs to sign in and belong to the User Pool Group to use the REST API | + +For each user type you can further specify what actions it has access to. + +| User type | Actions | Http Method | Authentication Provider | +| --- | --- | --- | --- | +| Authenticated user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | +| Guest user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | +| User Pool Group | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | + +REST APIs have support for [multiple environments](/[platform]/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. + +Because Amplify environments could be in separate AWS accounts, you cannot use the environment feature of API Gateway. Each Amplify environment will have a separate API Gateway resource associated with it. For example: + +```console +https://.execute-api.eu-west-2.amazonaws.com/dev/items +https://.execute-api.eu-west-2.amazonaws.com/prod/items +``` + +## Create a REST API + +Navigate into the root of a JavaScript, iOS, or Android project and run: + +```bash +amplify init +``` + +Follow the wizard to create a new app. After finishing the wizard run: + +```bash +amplify add api +``` + +Select the following options: + +- Please select from one of the below mentioned services: **REST** +- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsApi** +- Provide a path (e.g., /book/\{isbn}): **/items** + +This will be the configuration for `/items` path in API Gateway: + +```console +/ + |_ /items Main resource. Eg: /items + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser + |_ /\{proxy+} Proxy resource. Eg: /items/, /items/id, items/object/\{id} + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser +``` + +By default Amplify CLI creates a greedy path variable `/items/\{proxy+}` that catches all child resources for a path and forwards them to your Lambda. This will match all child routes including `/items/id` and `/items/object/id`. + +- Choose a Lambda source **Create a new Lambda function** +- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsLambda** +- Provide the AWS Lambda function name: **itemsLambda** +- Choose the runtime that you want to use: **NodeJS** +- Choose the function template that you want to use: **Serverless ExpressJS function** + +The Lambda function template **Serverless ExpressJS function** implements route handlers for `GET`, `POST`, `PUT` and `DELETE` Http Methods and paths for `/items` and `/items/*`. Some possible routes examples include: + +```console +GET /items List all items +GET /items/1 Load an item by id +POST /items Create an item +PUT /items Update an item +DELETE /items/1 Delete an item by id +``` + +- Do you want to access other resources in this project from your Lambda function? **No** +- Do you want to invoke this function on a recurring schedule? **No** +- Do you want to configure Lambda layers for this function? **No** +- Do you want to edit the local lambda function now? **Yes** + +> You are not going to change this template but it's good that you have it open as you follow the next steps. + +- Press enter to continue +- Restrict API access **Yes** +- Who should have access? **Authenticated and Guest users** +- What kind of access do you want for Authenticated users? **create, read, update, delete** +- What kind of access do you want for Guest users? **read** + +When configuration of your API is complete, the CLI displays a message confirming that you have configured local CLI metadata for this category. You can confirm this by running `amplify status`. Finally deploy your changes to the cloud: + +Amplify CLI restricts API access combining Amazon Cognito for authentication and AWS IAM (Identity and Access Management) for granting execution permissions on routes. + +- Do you want to add another path? **No** + +Deploy your new API. + +```bash +amplify push +``` + +At the end of this command you can take note of your new REST API url. + +```console +REST API endpoint: https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev +``` + +> REST APIs follow this pattern `https://{restapi-id}.execute-api.\{region}.amazonaws.com/\{environment}/\{path}`. + +Let's see an overview of all the resources created by Amplify CLI. + +```console +REST + |_ /items (path) + |_ itemsApi (Amazon API Gateway) + |_ itemsLambda (AWS Lambda) + |_ Logs (Amazon CloudWatch) +``` + +## Create REST API and restrict specific routes to specific User Pool Groups + +If your app uses User Pool Groups to manage different user types and would like to restrict access of specific routes to specific User Pool Groups. You can accomplish this by the following flow: + +- Create API route. +- Add API route handler function. +- Restrict-access to the API route to the User Pool Group. + +> The following example flow assumes the existence of two User Pool Groups : AdminUsers and GuestUsers for a Book store. The app would like to limit admin functionality like updating book records to the AdminUsers User Pool Group, while borrowing and returning books would be limited to the GuestUsers User Pool Group. +> +> - Path : /book/admin is restricted to AdminUsers and commands are handled by the bookAdminHandler lambda function +> - Path : /book/guest is restricted to GuestUsers and commands are handled by the bookGuestHandler lambda function + +```bash +amplify add api +$> ? Select from one of the below mentioned services: REST +$> ✔ Provide a friendly name for your resource to be used as a label for this category in the project: · mybookapi +$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/admin +$> ✔ Choose a Lambda source · Create a new Lambda function +$> ? Provide an AWS Lambda function name: bookAdminHandler +$> ? Choose the runtime that you want to use: NodeJS +$> ? Choose the function template that you want to use: Hello World +$> ? Do you want to configure advanced settings? No +$> ? Do you want to edit the local lambda function now? No +Successfully added resource bookAdminHandler locally. +$> ✔ Restrict API access? (Y/n) · yes +$> ✔ Restrict access by: · Individual Groups +$> ✔ Select groups: AdminUsers +$> ✔ What permissions do you want to grant to AdminUsers users? · create, read, update, delete +$> ✔ Do you want to add another path? (y/N) · yes +$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/guest +$> ✔ Choose a Lambda source · Create a new Lambda function +$> ? Provide an AWS Lambda function name: bookGuestHandler +$> ? Choose the runtime that you want to use: NodeJS +$> ? Choose the function template that you want to use: Hello World +$> ? Do you want to configure advanced settings? No +$> ? Do you want to edit the local lambda function now? No +Successfully added resource bookGuestHandler locally. +$> ✔ Restrict API access? (Y/n) · yes +$> ✔ Restrict access by: Individual Groups +$> ✔ Select groups: GuestUsers +$> ✔ What permissions do you want to grant to GuestUsers users? create, read, update +$> ✔ Do you want to add another path? (y/N) No +✅ Successfully added resource mybookapi locally +``` + +At the end of this command you can verify the routes and their respective User Pool Group restrictions in the `cli-inputs.json` file at the following path. + +```bash + /amplify/backend/api//cli-inputs.json +``` + +## REST endpoint that triggers new Lambda functions + +During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths. + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source Create a new Lambda function +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda +? Provide the AWS Lambda function name: itemsLambda +? Choose the function template that you want to use: + CRUD function for Amazon DynamoDB +❯ Serverless ExpressJS function +``` + +## REST endpoint that triggers existing Lambda functions + +During the CLI setup, you'll be guided through to use your own Lambda functions which you've initialized as a part of your CLI project using the `amplify add function` command. This would allow you to have custom logic in your Lambda function and not use the predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) templates generated by the CLI as in the examples above. + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source + Create a new Lambda function +❯ Use a Lambda function already added in the current Amplify project +``` + +## Set up a REST API with Amazon DynamoDB + +During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths with support for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command). + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source Create a new Lambda function +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda +? Provide the AWS Lambda function name: itemsLambda +? Choose the function template that you want to use: +❯ CRUD function for Amazon DynamoDB + Serverless ExpressJS function +``` + +In the example above with `/items` path, the following API will be created for you: + +1. GET /items/[ID] will return a list containing the item at the [ID]. If the item does not exist then an empty array is returned. +2. GET /items/object/[ID] will return a single item at [ID]. If the item does not exist then an empty object is returned. +3. PUT /items with your item in the request body will create or update the item. +4. POST /items with your item in the request body will create or update the item. +5. DELETE /items/object/[ID] will delete the item. + +When you have a sort key, you can append it to the end of the path, for example: `GET /items/object/[ID]/[SORT_KEY_ID]` diff --git a/src/pages/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx b/src/pages/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx new file mode 100644 index 00000000000..2c41556c8c1 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx @@ -0,0 +1,318 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Override Amplify-generated API Gateway resources', + description: "The 'amplify override api' command generates a developer-configurable 'overrides' TypeScript file which provides Amplify-generated API Gateway resources as CDK constructs. For example, developers can configure a custom description or the minimum compression size of their REST API.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/restapi/override-api-gateway/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +```bash +amplify override api +``` + +Run the command above to override Amplify-generated Amazon API Gateway resources. + +The command creates a new `overrides.ts` file under `amplify/backend/api//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). + +Apply all the overrides in the `override(...)` function. For example: + +```ts +// This file is used to override the REST API resources configuration +import { AmplifyApiRestResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiRestResourceStackTemplate) { + resources.restApi.description = "Custom description"; + resources.restApi.minimumCompressionSize = 1024; +} +``` + +To change a field on a particular path, use `resources.restApi.body.paths[\]`: + +```ts +export function override(resources: AmplifyApiRestResourceStackTemplate) { + // Change the default CORS response header Access-Control-Allow-Origin from "'*'" to the API's domain + resources.restApi.body.paths['/items'].options['x-amazon-apigateway-integration'].responses.default.responseParameters['method.response.header.Access-Control-Allow-Origin'] = { 'Fn::Sub': "'https://${ApiId}.execute-api.${AWS::Region}.amazonaws.com'" }; +} +``` + +You can override the following REST API resources that Amplify generates: + +
+ +|Amplify-generated resource|Description| +|-|-| +|[restApi](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html)|The Amazon API Gateway REST API created by `amplify add api`| +|[deploymentResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-deployment.html)|The deployment resource that deploys the REST API above to a stage.| +|[policies](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|User pool group-related IAM policy. Example: `resources.policies["/items"].groups["Admin"]` + + +
+ +## Authorize API requests with Cognito User Pools + +Amazon Cognito User Pools is a common service to use alongside API Gateway when +adding user Sign-Up and Sign-In to your application. If your application needs to +interact with other AWS services such as S3 on behalf of the user who invoked +an endpoint, you will need to use IAM credentials with Cognito Identity Pools. + +Amplify CLI does not support Cognito User Pool authorizers out-of-the-box. To +implement this functionality, you must override your REST API and add a Cognito +User Pool authorizer yourself by adding the following code into the +`override(...)` function, in order. + +First, assuming the Cognito User Pool you would like to use as an authorizer is +the Auth resource configured with your Amplify Project, create a parameter that resolves +to its User Pool ARN: + +```ts +// Replace the following with your Auth resource name +const authResourceName = ""; +const userPoolArnParameter = "AuthCognitoUserPoolArn"; + +// Add a parameter to your Cloud Formation Template for the User Pool's ID +resources.addCfnParameter({ + type: "String", + description: "The ARN of an existing Cognito User Pool to authorize requests", + default: "NONE", + }, + userPoolArnParameter, + { "Fn::GetAtt": [`auth${authResourceName}`, "Outputs.UserPoolArn"], } +); +``` + + + +Make sure to replace `` with the name of your auth resource. +This is the name of the folder in `amplify/backend/auth` that was created when +you added an Auth resource to your Amplify project. + + + +Now, define a REST API authorizer with Cognito User Pools using the OpenAPI extension, [`x-amazon-apigateway-authorizer`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html). This change will be applied by modifying the security definition of your REST API: + +```ts +// Create the authorizer using the AuthCognitoUserPoolArn parameter defined above +resources.restApi.addPropertyOverride("Body.securityDefinitions", { + Cognito: { + type: "apiKey", + name: "Authorization", + in: "header", + "x-amazon-apigateway-authtype": "cognito_user_pools", + "x-amazon-apigateway-authorizer": { + type: "cognito_user_pools", + providerARNs: [ + { + 'Fn::Join': ['', [{ Ref: userPoolArnParameter }]], + }, + ], + }, + }, +}); +``` + +Finally, update the security methods for all of the paths in your REST API to +use this new Cognito User Pool authorizer. You also add the `Authorization` header +as a parameter on incoming requests for these paths as a place for users to provide +their Cognito User ID Tokens. + +```ts +// For every path in your REST API +for (const path in resources.restApi.body.paths) { + // Add the Authorization header as a parameter to requests + resources.restApi.addPropertyOverride( + `Body.paths.${path}.x-amazon-apigateway-any-method.parameters`, + [ + ...resources.restApi.body.paths[path]["x-amazon-apigateway-any-method"] + .parameters, + { + name: "Authorization", + in: "header", + required: false, + type: "string", + }, + ] + ); + // Use your new Cognito User Pool authorizer for security + resources.restApi.addPropertyOverride( + `Body.paths.${path}.x-amazon-apigateway-any-method.security`, + [ { Cognito: [], }, ] + ); +} +``` + + + +Note that you can add more advanced logic to only use the Cognito User Pool authorizer +with some paths or methods. + + + +When performing requests to your REST API, make sure to add the `Authorization` +header with an ID Token provided by Cognito. + +Requests to endpoints are now populated with information from Cognito about the +user who is invoking the +endpoint, and you can reuse the verified ID Token in your endpoint resolvers to assume +the identity of the user for accessing other services like AWS AppSync or S3. + +## Authorize API requests with Lambda authorizer + +While Amplify CLI does not support Lambda authorizers natively out-of-box, you can implement this functionality by overriding your REST API resources. The following steps will walk you through how to create token-based Lambda authorizer. + +First, you need to have a Lambda authorizer function with required [authorization logic](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html#api-gateway-lambda-authorizer-lambda-function-create) in your Amplify project to use it as an authorizer. Refer to the steps to [set up a function](/[platform]/build-a-backend/functions/set-up-function/) using `amplify add function` + +After running `amplify override api`, add the following code to `override(...)` function. +Initially, create a parameter that resolves to Lambda Function ARN + +```ts +// Replace the following with your Function resource name +const functionResourcename = ""; +const functionArnParameter = "FunctionArn"; + +// Adding parameter to your Cloud Formation Template for Authorizer function arn +resources.addCfnParameter( + { + type: "String", + description: "The ARN of an existing Lambda Function to authorize requests", + default: "NONE", + }, + functionArnParameter, + { "Fn::GetAtt": [`function${functionResourcename}`, "Outputs.Arn"], } +); +``` + + + + +Make sure to replace `` with the name of your function resource. This is the name of the folder in `amplify/backend/function` that was created when you added an function resource to your Amplify project. + + + +Next, define the Lambda authorizer using the OpenAPI extension, [`x-amazon-apigateway-authorizer`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html). This change will be applied by modifying the security definition of your REST API: + +```ts +// Create the authorizer using the functionArnParameter parameter defined above +resources.restApi.addPropertyOverride("Body.securityDefinitions", { + Lambda: { + type: "apiKey", + name: "Authorization", + in: "header", + "x-amazon-apigateway-authtype": "oauth2", + "x-amazon-apigateway-authorizer": { + type: "token", + authorizerUri: + { + 'Fn::Join': [ + '', + [ + "arn:aws:apigateway:", + { Ref: 'AWS::Region' }, + ":lambda:path/2015-03-31/functions/", + { Ref: functionArnParameter }, + "/invocations" + ] + ], + }, + authorizerResultTtlInSeconds: 0 + }, + }, +}); +``` + +As API Gateway needs permission to invoke the Authorizer lambda function, add resource based policy to the function using following code: + +```ts +// Adding Resource Based policy to Lambda authorizer function +resources.addCfnResource( + { + type: "AWS::Lambda::Permission", + properties: { + Action: "lambda:InvokeFunction", + FunctionName: {Ref: functionArnParameter}, + Principal: "apigateway.amazonaws.com", + SourceArn:{ + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "" + }, + "/*/*" + ] + ] + } + } + }, + "LambdaAuthorizerResourceBasedPolicy" +); + +``` + + +Make sure to replace `` with the name of your Rest API resource. This is the name of the folder in `amplify/backend/api` that was created when you added an Rest API resource to your Amplify project. + + + +Finally, update the security methods for all of the paths in your REST API to use this new Lambda authorizer. You can also add the Authorization header as a parameter on incoming requests for these paths as a place for users to provide their Auth token. + +```ts +for (const path in resources.restApi.body.paths) { + // Add the Authorization header as a parameter to requests + resources.restApi.addPropertyOverride( + `Body.paths.${path}.x-amazon-apigateway-any-method.parameters`, + [ + ...resources.restApi.body.paths[path]["x-amazon-apigateway-any-method"] + .parameters, + { + name: "Authorization", + in: "header", + required: false, + type: "string", + }, + ] + ); + // Use your new Lambda authorizer for security + resources.restApi.addPropertyOverride( + `Body.paths.${path}.x-amazon-apigateway-any-method.security`, + [ { Lambda: [], }, ] + ); +} +``` + +Note that you can add more advanced logic to only use the Lambda authorizer with some paths or methods. + + +When performing requests to your REST API, make sure to add the Authorization header with an token required by Lambda authorizer function. diff --git a/src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx b/src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx new file mode 100644 index 00000000000..6effbae55fd --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx @@ -0,0 +1,178 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Test the REST API', + description: 'Learn how you can test the REST API from the terminal, with Amplify Mock, or with the API Gateway console.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/restapi/test-api/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +## Test the API from the terminal + +If Guest users have access to your REST API you can test it from the terminal using Curl. + +[Curl](https://github.com/curl/curl) is a command-line tool that lets you transfer data to and from a server using various protocols. + +> Curl is available in many distributions including Mac, Windows and Linux. Follow the install instructions in the [docs](https://curl.haxx.se/docs/install.html). + + + + +### GET method example + +```bash +curl https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos +``` + +### POST method example + +```bash +curl -H "Content-Type: application/json" -d '{"name":"todo-1"}' https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos +``` + + + + +### GET method example + +```bash +curl https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos +``` + +### POST method example + +```bash +curl -H "Content-Type: application/json" -d {\"name\":\"todo-1\"} https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos +``` + + + + +> Important! Testing methods using production endpoints may result in changes to resources that cannot be undone. + +## Test the API with Amplify Mock + +Amplify CLI allows you to quickly test your REST APIs by using the `amplify mock function` command. + +Let's test your new REST API using the route below with HTTP Method `GET` and path `/todos?limit=10` which includes a `limit` query string parameter. + +```console +GET /todos?limit=10 +``` + +> Important! Testing methods using production endpoints may result in changes to resources that cannot be undone. + +Before you continue, edit the file at `{project}/amplify/backend/function/todosLambda/src/event.json` and replace its content for the purpose of the test. + +```json +{ + "httpMethod": "GET", + "path": "/todos", + "queryStringParameters": { + "limit": "10" + } +} +``` + +Make sure you have saved the changes and run + +```bash +amplify mock function todosLambda +``` + +Select the following options: + +- Provide the path to the event JSON object relative to `{project}/amplify/backend/function/todosLambda` __src/event.json__ + +```console +Starting execution... +EVENT: {"httpMethod":"GET","path":"/todos","queryStringParameters":{"limit":"10"}} +App started + +Result: +{"statusCode":200,"body":"{\"success\":\"get call succeed!\",\"url\":\"/todos?limit=10\"}","headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","content-type":"application/json; charset=utf-8","content-length":"55", "date":"Tue, 18 Aug 2020 16:50:53 GMT","connection":"close"},"isBase64Encoded":false} +Finished execution. +``` + +## Test the API with API Gateway console + +Let's test your new REST API using the route below with HTTP Method `GET` and path `/todos?limit=10` which includes a `limit` query string parameter. + +```console +GET /todos?limit=10 +``` + +> Important! Testing methods with the API Gateway console may result in changes to resources that cannot be undone. + +- Sign in to the API Gateway console at [https://console.aws.amazon.com/apigateway](https://console.aws.amazon.com/apigateway). +- Choose the `todosApi` REST API. +- In the Resources pane, choose the method you want to test. Pick `ANY` right under `/todos`. + +```console +/ + |_ /todos Main resource. Eg: /todos + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser + |_ /{proxy+} Proxy resource. Eg: /todos/, /todos/id, todos/object/{id} + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser +``` + +- In the Method Execution pane, in the Client box, choose TEST. Choose the `GET` method. Add `limit=10` to the Query String `{todos}` field. + +- Choose Test to run the test for `GET /todos?limit=10`. The following information will be displayed: request, status, latency, response body, response headers and logs. + +```bash +Request: /todos?limit=10 +Status: 200 +Latency: 139 ms +Response Body +{ + "success": "get call succeed!", + "url": "/todos?limit=10" +} +Response Headers +{"access-control-allow-origin":"*","date":"Tue, 18 Aug 2020 17:36:14 GMT","content-length":"55","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","x-powered-by":"Express","content-type":"application/json; charset=utf-8","connection":"close"} +Logs +Execution log for request 4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f +Tue Aug 18 17:36:14 UTC 2020 : Starting execution for request: 4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f +Tue Aug 18 17:36:14 UTC 2020 : HTTP Method: GET, Resource Path: /todos +Tue Aug 18 17:36:14 UTC 2020 : Method request path: {} +Tue Aug 18 17:36:14 UTC 2020 : Method request query string: {limit=10} +Tue Aug 18 17:36:14 UTC 2020 : Method request headers: {} +Tue Aug 18 17:36:14 UTC 2020 : Method request body before transformations: +Tue Aug 18 17:36:14 UTC 2020 : Endpoint request URI: https://lambda.eu-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-west-2:664371068953:function:expressLambda-dev/invocations +Tue Aug 18 17:36:14 UTC 2020 : Endpoint request headers: { X-Amz-Date=20200818T173614Z, X-Amz-Source-Arn=arn:aws:execute-api:eu-west-2:664371068953:s3zmw6fqy5/test-invoke-stage/GET/todos, Accept=application/json, User-Agent=AmazonAPIGateway_s3zmw6fqy5, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDEaCWV1LXdlc3QtMiJGMEQCIC3KIeR66WhaCBw+eJ+GPhF7y4hz9xC2nN+ARb7T3psyAiBdsoaD9yMfiw2dHWjQM5x7vM11XmToNSGu64mckUQdzSq0AwgaEAEaDDU0NDM4ODgxNjY2MyIMIzObNbCd6QtYwb0IKpEDpHXEzkM2OYq7JfL0U/WbF09KNamodfnifRYwZd/GNOwykykc/zHiU9X0XZPRd+QTnQe/9eoy8DaxBkDgRzQQjTThQWJWadtcfjryTLRKpVeo1UueL+f6DTUDf+URjb0P9CN1gPm+ntZD3LSyAXGwACKG7YMA5/HyeEk [TRUNCATED] +Tue Aug 18 17:36:14 UTC 2020 : Endpoint request body after transformations: {"resource":"/todos","path":"/todos","httpMethod":"GET","headers":null,"multiValueHeaders":null,"queryStringParameters":{"limit":"10"},"multiValueQueryStringParameters":{"limit":["10"]},"pathParameters":null,"stageVariables":null,"requestContext":{"resourcePath":"/todos","httpMethod":"GET","requestTime":"18/Aug/2020:17:36:14 +0000","path":"/todos","accountId":"EXAMPLE_ID","protocol":"HTTP/1.1","stage":"test-invoke-stage","domainPrefix":"testPrefix","requestTimeEpoch":1597772174890,"requestId":"4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f","identity":{"cognitoIdentityPoolId":null,"cognitoIdentityId":null,"apiKey":"test-invoke-api-key","principalOrgId":null,"cognitoAuthenticationType":null,"userArn":"arn:aws:iam::664371068953:root","apiKeyId":"test-invoke-api-key-id","userAgent":"aws-internal/3 aws-sdk-java/1.11.820 Linux/4.9.217-0.1.ac.205.84.332.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.252-b09 java/1.8.0_252 v [TRUNCATED] +Tue Aug 18 17:36:14 UTC 2020 : Sending request to https://lambda.eu-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-west-2:664371068953:function:expressLambda-dev/invocations +Tue Aug 18 17:36:15 UTC 2020 : Received response. Status: 200, Integration latency: 137 ms +Tue Aug 18 17:36:15 UTC 2020 : Endpoint response headers: {Date=Tue, 18 Aug 2020 17:36:15 GMT, Content-Type=application/json, Content-Length=443, Connection=keep-alive, sampled=0} +Tue Aug 18 17:36:15 UTC 2020 : Endpoint response body before transformations: {"statusCode":200,"body":"{\"success\":\"get call succeed!\",\"url\":\"/todos?limit=10\"}","headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","content-type":"application/json; charset=utf-8","content-length":"55","date":"Tue, 18 Aug 2020 17:36:14 GMT","connection":"close"},"isBase64Encoded":false} +Tue Aug 18 17:36:15 UTC 2020 : Method response body after transformations: {"success":"get call succeed!","url":"/todos?limit=10"} +Tue Aug 18 17:36:15 UTC 2020 : Method response headers: {x-powered-by=Express, access-control-allow-origin=*, access-control-allow-headers=Origin, X-Requested-With, Content-Type, Accept, content-type=application/json; charset=utf-8, content-length=55, date=Tue, 18 Aug 2020 17:36:14 GMT, connection=close, Sampled=0} +Tue Aug 18 17:36:15 UTC 2020 : Successfully completed execution +Tue Aug 18 17:36:15 UTC 2020 : Method completed with status: 200 +``` diff --git a/src/pages/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx new file mode 100644 index 00000000000..f3f8404bb43 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx @@ -0,0 +1,186 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure Storage', + description: 'Use Amplify CLI to create and manage cloud-connected file and data storage for your app.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/storage/configure-storage/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +Amplify CLI's `storage` category enables you to create and manage cloud-connected file & data storage. Use the `storage` category when you need to store: + +1. app content (images, audio, video etc.) in an public, protected or private storage bucket or +2. app data in a NoSQL database and access it with a REST API + Lambda + +## Setup a new storage resource + +You can setup a new storage resource by running the following command: + +```bash +amplify add storage +``` + +Amplify allows you to either setup a app content storage (images, audio, video etc.) backed by Amazon S3 or a NoSQL database backed by Amazon DynamoDB. + +### Adding S3 storage + +```console +? Please select from one of the below mentioned services: +> Content (Images, audio, video, etc.) + NoSQL Database +? Please provide a friendly name for your resource that will be used to label this category in the project: +> mystorage +? Please provide bucket name: +> mybucket +``` + +Follow the prompts to provide your content storage's resource name. + + + +The storage resource created by Amplify CLI has retention enabled which prevents accidental deletion or loss of data. Hence, running `amplify remove storage` will not delete the storage resource and will need to be manually deleted on the AWS console. + + + +### S3 Access permissions + +Next, configure the access permissions for your Amazon S3 bucket. If you haven't set up the `auth` category already, the Amplify CLI will guide you through a workflow to enable the auth category. + +```console +? Restrict access by? +> Auth/Guest Users + Individual Groups + Both + Learn more +``` + + + +**NOTE:** Run `amplify update storage` to change the access permissions for your Amazon S3 bucket + + + +#### Auth/Guest Users access + +Select `Auth/Guest Users`, to scope permissions based on an individual user's authentication status. On the next question you'll be able to select if only authenticated users can access resources, or authenticated and guest users: + +``` +? Who should have access: +❯ Auth users only + Auth and guest users +``` + +Then you'll be prompted to set the access scopes for your authenticated and (if selected prior) unauthenticated users. + +```console +? What kind of access do you want for Authenticated users? +> ◉ create/update + ◯ read + ◯ delete +? What kind of access do you want for Guest users? + ◯ create/update +> ◉ read + ◯ delete +``` + +Granting access to authenticated users will allow the specified CRUD operations on objects in the bucket starting with the prefix `/public/`, `/protected/{cognito:sub}/`, and `/private/{cognito:sub}/`. `{cognito:sub}` is the sub of the Cognito identity of the authenticated user. + +Granting access to guest users will allow the specified CRUD operations on objects in the bucket starting with the prefix `/public/`. + +#### Individual Group access + +Select `Individual Groups` to scope access permissions based on [Cognito User Groups](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) + +```console +? Select groups: + ◉ EMPLOYEE +> ◉ MANAGER +``` + +Then select the CRUD operations you want to permit for each selected Cognito user group + +```console +? What kind of access do you want for EMPLOYEE users? + ◯ create/update +> ◉ read + ◯ delete +? What kind of access do you want for MANAGER users? + ◉ create/update + ◯ read +> ◉ delete +``` + +> Note: CRUD operations selected here will apply to ALL objects in the bucket, not just objects under a particular prefix. + +> Note: If you combine `Auth/Guest user access` and `Individual Group access`, users who are members of a group will only be granted the permissions of the group, and not the authenticated user permissions. + +### S3 Lambda trigger + +Lastly, you have the option of configuring a Lambda function that can execute in response to S3 events. + +```console +? Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) +``` + +Learn more about this workflow [here](/[platform]/tools/cli/usage/lambda-triggers/#s3-lambda-triggers). + +That's it! Your content storage is set up! Head to the [library's storage docs](/[platform]/build-a-backend/storage/set-up-storage/) to integrate this newly created S3 bucket into your app. + +### Adding a NoSQL database + +```console +? Please select from one of the below mentioned services: +> Content (Images, audio, video, etc.) + NoSQL Database +? Please provide a friendly name for your resource that will be used to label this category in the project: +> dynamo2e1dc4eb +? Please provide table name: +> dynamo2e1dc4eb +``` + +Follow the prompts to provide your NoSQL Database's resource name. Next, you'll go through a table-creation wizard. First, you'll create the columns of your table: + +```console +You can now add columns to the table. + +? What would you like to name this column: id +? Please choose the data type: string +? Would you like to add another column? Yes +``` + +Then, you'll need to specify your indexes. The concept behind "indexes", "partition key", "sort key" and "global secondary indexes" are explained in-depth [here](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey). + +```console +? Please choose partition key for the table: id +? Do you want to add a sort key to your table? (y/N) +``` + +```console +? Do you want to add a Lambda Trigger for your Table? (y/N) +``` + +If you want to configure a Lambda trigger for your Table, you'll have the option. Learn more about this workflow [here](/[platform]/tools/cli/usage/lambda-triggers/#dynamodb-lambda-triggers). + +That's it! Your NoSQL Database is set up! diff --git a/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx new file mode 100644 index 00000000000..ff7da184d50 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx @@ -0,0 +1,256 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Import an S3 bucket or DynamoDB table', + description: 'Learn how you can import existing S3 bucket or DynamoDB table resources as a storage resource for other Amplify categories (API, Function, and more) using the Amplify CLI.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/storage/import/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +Import an existing S3 bucket or DynamoDB tables into your Amplify project. Get started by running `amplify import storage` command to search for & import an S3 or DynamoDB resource from your account. + +```bash +amplify import storage +``` + +Make sure to run `amplify push` to complete the import process and deploy this backend change to the cloud. + +The `amplify import storage` command will: + +- automatically populate your Amplify Library configuration files (aws-exports.js, amplifyconfiguration.json) with your chosen S3 bucket information +- provide your designated S3 bucket or DynamoDB table as a storage mechanism for all storage-dependent categories (API, Function, Predictions, and more) +- enable Lambda functions to access the chosen S3 or DynamoDB resource if you permit it + +This feature is particularly useful if you're trying to: + +- enable Amplify categories (such as API and Function) to access your existing storage resources; +- incrementally adopt Amplify for your application stack; +- independently manage S3 and DynamoDB resources while working with Amplify. + +> Note: Amplify does not manage the lifecycle of an imported resource. + +## Import an existing S3 bucket + +Select the "S3 bucket - Content (Images, audio, video, etc.)" option when you've run `amplify import storage`. + +Run `amplify push` to complete the import procedure. + +> Amplify projects are limited to exactly one S3 bucket. + +### Connect to an imported S3 bucket with Amplify Libraries + +By default, Amplify Libraries assumes that S3 buckets are configured with the following access patterns: + +- `public/` - Accessible by all users of your app +- `protected/{user_identity_id}/` - Readable by all users, but writable only by the creating user +- `private/{user_identity_id}/` - Only accessible for the individual user + +You can either configure your IAM role to use the Amplify-recommended policies or in your Amplify libraries configuration [overwrite the default storage path behavior](/[platform]/build-a-backend/storage/configure-access/#customize-object-key-path). + +It is highly recommended to review your S3 bucket's CORS settings. Review the [recommendation guide here](/[platform]/build-a-backend/storage/set-up-storage/#amazon-s3-bucket-cors-policy-setup). + +### Configuring IAM role to use Amplify-recommended policies + +If you're using an imported S3 bucket with an imported Cognito resource, then you'll need to update the policy of your Cognito Identity Pool's authenticated and unauthenticated role. Create new __managed policies__ (not *inline policies*) for these roles with the following statements: + +> Make sure to replace `{YOUR_S3_BUCKET_NAME}` with your S3 bucket's name. + +#### Unauthenticated role policies + +- IAM policy statement for `public/`: + +```json +{ + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/public/*" + ], + "Effect": "Allow" +} +``` + +- IAM policy statement for read access to `public/`, `protected/`, and `private/`: + +```json +{ + "Action": [ + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/*" + ], + "Effect": "Allow" +}, +{ + "Condition": { + "StringLike": { + "s3:prefix": [ + "public/", + "public/*", + "protected/", + "protected/*" + ] + } + }, + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}" + ], + "Effect": "Allow" +} +``` + +#### Authenticated role policies + +- IAM policy statement for `public/`: + +```json +{ + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/public/*" + ], + "Effect": "Allow" +} +``` + +- IAM policy statement for `protected/`: + +```json +{ + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/${cognito-identity.amazonaws.com:sub}/*" + ], + "Effect": "Allow" +} +``` + +- IAM policy statement for `private/`: + +```json +{ + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/private/${cognito-identity.amazonaws.com:sub}/*" + ], + "Effect": "Allow" +} +``` + +- IAM policy statement for read access to `public/`, `protected/`, and `private/`: + +```json +{ + "Action": [ + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/*" + ], + "Effect": "Allow" +}, +{ + "Condition": { + "StringLike": { + "s3:prefix": [ + "public/", + "public/*", + "protected/", + "protected/*", + "private/${cognito-identity.amazonaws.com:sub}/", + "private/${cognito-identity.amazonaws.com:sub}/*" + ] + } + }, + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}" + ], + "Effect": "Allow" +} +``` + +## Import an existing DynamoDB table + +Select the "DynamoDB table - NoSQL Database" option when you've run `amplify import storage`. In order to successfully import your DynamoDB table, your DynamoDB table needs to be located within the same region as your Amplify project. + +Run `amplify push` to complete the import procedure. + +> Amplify projects can contain multiple DynamoDB tables. + +## Multi-environment support + +When you create a new environment through `amplify env add`, Amplify CLI will assume by default that you're managing your app's storage resources outside of an Amplify project. You'll be asked to either import a different S3 bucket or DynamoDB tables or maintain the same imported storage resource. + +If you want to have Amplify manage your storage resources in a new environment, run `amplify remove storage` to unlink the imported storage resources and `amplify add storage` to create new Amplify-managed S3 buckets and DynamoDB tables in the new environment. + +## Unlink an existing S3 bucket or DynamoDB table + +In order to unlink your existing Storage resource run `amplify remove storage`. This will only unlink the S3 bucket or DynamoDB table referenced from the Amplify project. It will not delete the S3 bucket or DynamoDB table itself. + +Run `amplify push` to complete the unlink procedure. + +## Configure environment variables for Amplify Hosting builds + +In order to successfully build your application with Amplify Hosting add the following environment variables to your build environment: + +|Environment Variable|Description|Imported Resource|Required +|-|-|-|-| +|AMPLIFY_STORAGE_BUCKET_NAME|The name of the S3 bucket being imported for storage|S3 bucket|Yes +|AMPLIFY_STORAGE_REGION|The AWS region in which the S3 bucket or the DynamoDB table is located (for example: us-east-1, us-west-2, etc.)|S3 bucket or DynamoDB table|Yes +|AMPLIFY_STORAGE_TABLES|The name of the storage resource and DynamoDB table being imported for storage|DynamoDB table|Yes + + +The value of the AMPLIFY_STORAGE_TABLES environment variable needs to be in a json format such as: + +``` +{ + "STORAGE_RESOURCE_NAME_1":"DDB_TABLE_NAME_1", + "STORAGE_RESOURCE_NAME_2":"DDB_TABLE_NAME_2" // If you are importing more than a single DynamoDB table +} +``` +The values for the `STORAGE_RESOURCE_NAME` and `DDB_TABLE_NAME` fields can be retrieved from the amplify/team-provider-info.json file. + diff --git a/src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx new file mode 100644 index 00000000000..8de05ae2a74 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx @@ -0,0 +1,110 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Modify Amplify-generated resources', + description: "The 'amplify override storage' command generates a developer-configurable 'overrides' TypeScript file which provides Amplify-generated S3 and DynamoDB resources as CDK constructs. For example, developers can run the 'amplify override storage' command to enable Transfer Acceleration for Amplify-generated S3 buckets.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/storage/modify-amplify-generated-resources/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +Amplify Storage uses underlying AWS services and resources such as S3 and DynamoDB. You can customize these underlying resources for your specific use case. + +```bash +amplify override storage +``` + +Run the command above to override Amplify-generated storage resources including the S3 bucket, DynamoDB tables, and more. + +The command creates a new `overrides.ts` file under `amplify/backend/storage//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). + +## Customize Amplify-generated S3 resources + +Apply all the overrides in the `override(...)` function. For example to enable versioning on your S3 bucket: + +```ts +import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyS3ResourceTemplate) { + resources.s3Bucket.versioningConfiguration = { + status: 'Enabled' + }; +} +``` + +You can override the following S3-related resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [s3Bucket](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html) | The S3 bucket that Amplify generates for file storage upon `amplify add storage` | +| [s3AuthPublicPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `public/*` prefix | +| [s3AuthProtectedPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `protected/*` prefix | +| [s3AuthPrivatePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `private/*` prefix | +| [s3AuthUploadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `uploads/*` prefix | +| [s3AuthReadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' read access | +| [s3GuestPublicPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' write access to `public/*` prefix | +| [s3GuestUploadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' write access to `uploads/*` prefix | +| [s3GuestReadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' read access | + +
+ +For example, you can use `amplify override storage` to add additional PUT and GET access IAM policy statements to the S3 bucket's default public Auth policy: + +```ts +import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyS3ResourceTemplate) { + resources.s3AuthPublicPolicy.policyDocument.Statement = [ + ...resources.s3AuthPublicPolicy.policyDocument.Statement, + { + Effect: 'Allow', + Action: ['s3:PutObject', 's3:PutObjectAcl', 's3:GetObject'], + Resource: '' + } + ]; +} +``` + +Please refer to the [IAM documentation](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html) for more information on actions, resources, and condition keys for Amazon S3. + +## Customize Amplify-generated DynamoDB tables + +Apply all the overrides in the `override(...)` function. For example to enable time-to-live specification on your DynamoDB table: + +```ts +import { AmplifyDDBResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyDDBResourceTemplate) { + resources.dynamoDBTable.timeToLiveSpecification = { + attributeName: 'ttl', + enabled: true + }; +} +``` + +You can override the following DynamoDB resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [dynamoDBTable](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The DynamoDB table that Amplify creates upon `amplify add storage` | diff --git a/src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx new file mode 100644 index 00000000000..3988e173a4c --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx @@ -0,0 +1,26 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Move files', + description: "Learn more about how to move files using Amplify's storage category.", + platforms: ['flutter'] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +import flutter0 from '/src/fragments/lib/storage/flutter/move.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx index 510ff3236cb..cfaa834f46a 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx @@ -9,7 +9,21 @@ export const meta = { 'angular', 'nextjs', 'react', - 'vue' + 'vue', + 'flutter' + ], + canonicalObjects: [ + { + platforms: [ + 'angular', + 'nextjs', + 'javascript', + 'vue', + 'react', + 'flutter' + ], + canonicalPath: '/javascript/prev/build-a-backend/storage/copy/' + } ] }; @@ -41,3 +55,7 @@ import js0 from '/src/fragments/lib-v1/storage/js/copy.mdx'; import reactnative0 from '/src/fragments/lib-v1/storage/js/copy.mdx'; + +import flutter0 from '/src/fragments/lib/storage/flutter/copy.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx index 6f6c183fd2b..511dded6999 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx @@ -8,7 +8,21 @@ export const meta = { 'angular', 'nextjs', 'react', - 'vue' + 'vue', + 'flutter' + ], + canonicalObjects: [ + { + platforms: [ + 'vue', + 'nextjs', + 'javascript', + 'react', + 'angular', + 'flutter' + ], + canonicalPath: '/javascript/prev/build-a-backend/storage/get-properties/' + } ] }; @@ -36,3 +50,7 @@ import js0 from '/src/fragments/lib-v1/storage/js/get-properties.mdx'; vue: js0 }} /> + +import flutter0 from '/src/fragments/lib/storage/flutter/get-properties.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx index 8cc21b0b48e..9b57008d7ba 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx @@ -9,7 +9,22 @@ export const meta = { 'angular', 'nextjs', 'react', - 'vue' + 'vue', + 'flutter' + ], + canonicalObjects: [ + { + platforms: [ + 'angular', + 'vue', + 'react', + 'nextjs', + 'javascript', + 'react-native', + 'flutter' + ], + canonicalPath: '/javascript/prev/build-a-backend/storage/transfer-acceleration/' + } ] }; @@ -38,3 +53,75 @@ import js from '/src/fragments/lib-v1/storage/js/transfer-acceleration.mdx'; vue: js }} /> + + + +You can enable [Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html) for fast and secure transfer of files over long distances between your end user device and the S3 bucket. You can override the storage resource for this configuration and then leverage the `useAccelerateEndpoint` parameter to use the accelerated S3 endpoint. + +## Override storage resource + +Start by overriding your storage resources to enable Transfer Acceleration on your S3 bucket. + +```sh +$ amplify override storage +✅ Successfully generated "override.ts" folder at /amplify/backend/storage/accelerated-bucket +✔ Do you want to edit override.ts file now? (Y/n) · yes +Edit the file in your editor: /amplify/backend/storage/accelerated-bucket/override.ts +``` + +In the generated `override.ts` file use the following CDK snippet to enable transfer acceleration. + +```javascript +// amplify/backend/storage/accelerated-bucket/override.ts +import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyS3ResourceTemplate) { + resources.s3Bucket.accelerateConfiguration = { + accelerationStatus: 'Enabled' + } +} +``` + +Next, deploy this storage resource: + +```sh +amplify push +``` + +## Use Transfer Acceleration on Storage Operations + +You can use transfer acceleration when calling the following APIs: + +* `getUrl` +* `downloadData` +* `downloadFile` +* `uploadData` +* `uploadFile` + +Set `useAccelerateEndpoint` to `true` in the corresponding Storage S3 plugin options to apply an accelerated S3 endpoint to the operation. For example, upload a file using transfer acceleration: + +```dart +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +Future uploadFileUsingAcceleration(String filePath, String key) async { + final localFile = AWSFile.fromPath(filePath); + try { + final uploadFileOperation = Amplify.Storage.uploadFile( + localFile: localFile, + key: key, + options: const StorageUploadFileOptions( + pluginOptions: S3UploadFilePluginOptions( + useAccelerateEndpoint: true, + ), + ), + ); + + final result = await uploadFileOperation.result; + safePrint('Uploaded file: ${result.uploadedItem.key}'); + } on StorageException catch (error) { + safePrint('Something went wrong uploading file: ${error.message}'); + } +} +``` + + From 848691948376afbc73f31d1a2a0305dab2615127 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:40:25 -0700 Subject: [PATCH 05/88] chore: transfer push notifications v1 docs from current to prev --- .../common/app-badge-count.mdx | 34 +++++ .../common/enable-rich-notifications.mdx | 48 +++++++ .../getting_started/cross-platform-prereq.mdx | 45 +++++++ .../getting_started/getting-started.mdx | 119 ++++++++++++++++++ .../common/identify-user.mdx | 42 +++++++ .../interact-with-notifications.mdx | 32 +++++ .../notification-lifecycle.mdx | 37 ++++++ .../notification-opened.mdx | 44 +++++++ .../notification-received.mdx | 44 +++++++ .../common/receive-device-token.mdx | 20 +++ .../common/record-notifications.mdx | 21 ++++ .../common/register-device.mdx | 17 +++ .../common/remote-media.mdx | 3 + .../common/request-permissions.mdx | 61 +++++++++ .../cross-platform-setup.mdx | 16 +++ .../setup_push_service/setup-push-service.mdx | 16 +++ .../push-notifications/common/testing.mdx | 1 + .../app-badge-count/index.mdx | 9 +- .../enable-rich-notifications/index.mdx | 9 +- .../identify-user/index.mdx | 13 +- .../push-notifications/index.mdx | 5 +- .../interact-with-notifications/index.mdx | 9 +- .../receive-device-token/index.mdx | 9 +- .../request-permissions/index.mdx | 9 +- .../set-up-push-notifications/index.mdx | 13 +- .../set-up-push-service/index.mdx | 13 +- .../test-notifications/index.mdx | 13 +- 27 files changed, 692 insertions(+), 10 deletions(-) create mode 100644 src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/enable-rich-notifications.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/getting_started/cross-platform-prereq.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/identify-user.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-lifecycle.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-opened.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-received.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/receive-device-token.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/record-notifications.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/register-device.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/remote-media.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/request-permissions.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/setup_push_service/cross-platform-setup.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx create mode 100644 src/fragments/lib-v1/push-notifications/common/testing.mdx diff --git a/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx b/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx new file mode 100644 index 00000000000..c261a3b62e2 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx @@ -0,0 +1,34 @@ +The app badge count, when set, can be seen on your app's icon on a user's device. Amplify provides you with simple helpers to manipulate this number. + + + App badge count helpers are safe to call (but will be ignored) even when your + app is running on platforms where badges are not supported. + + +## Get the current badge count + +Use `getBadgeCount` to get the current app badge count. You might need to do this to calculate the value when setting the badge count. + +import flutterGetBadgeCount from '/src/fragments/lib/push-notifications/flutter/app_badge_count/get-badge-count.mdx'; +import reactNativeGetBadgeCount from '/src/fragments/lib/push-notifications/react-native/app_badge_count/get-badge-count.mdx'; + + + +## Update the badge count + +Use `setBadgeCount` to set the current app badge count. Setting the badge count to `0` (zero) will remove the badge from your app's icon. + +import flutterSetBadgeCount from '/src/fragments/lib/push-notifications/flutter/app_badge_count/set-badge-count.mdx'; +import reactNativeSetBadgeCount from '/src/fragments/lib/push-notifications/react-native/app_badge_count/set-badge-count.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/enable-rich-notifications.mdx b/src/fragments/lib-v1/push-notifications/common/enable-rich-notifications.mdx new file mode 100644 index 00000000000..d320a8d2388 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/enable-rich-notifications.mdx @@ -0,0 +1,48 @@ + + On Android, Amplify Push Notifications should already be configured to handle + rich content for you. + + +Amplify currently supports adding images to your notifications, but there are some additional steps required. + +## Create a Notification Service Extension + +1. Open `.xcworkspace` located inside the `/ios` folder of your application project with Xcode. + +2. In the Xcode project, select **File > New > Target...** + +![The file menu is selected in the toolbar, then the new option is selected, then the target option.](/images/push-notifications/cross-platform-service-extension/01_new-target.png) + +3. Select **Notification Service Extension > Next**. + +![The notification service extension option is selected in the new target popup.](/images/push-notifications/cross-platform-service-extension/02_target-type.png) + +4. Enter a name for your service extension (e.g. MyNotificationServiceExtension) and select **Finish**. + +![Form for the new target options.](/images/push-notifications/cross-platform-service-extension/03_target-name.png) + +## Provide the extension with the Amplify service class + +1. Open `Podfile` located inside the `/ios` folder of your application project with a text editor. + +2. Add `AmplifyUtilsNotifications` to the extension you created above. + +import flutterAddNotificationsPod from '/src/fragments/lib/push-notifications/flutter/enable_rich_notifications/add-notifications-pod.mdx'; +import reactNativeAddNotificationsPod from '/src/fragments/lib/push-notifications/react-native/enable_rich_notifications/add-notifications-pod.mdx'; + + + +4. Open `.xcworkspace` located inside the `/ios` folder of your application project with Xcode. + +5. Find your extension folder in the Project navigator pane and select the **Info** Property List. + +![Extension info](/images/push-notifications/cross-platform-service-extension/04_extension-info.png) + +6. Update the `NSExtensionPrincipalClass` property with the value `AmplifyUtilsNotifications.AUNotificationService`. + +![Extension class](/images/push-notifications/cross-platform-service-extension/05_extension-class.png) diff --git a/src/fragments/lib-v1/push-notifications/common/getting_started/cross-platform-prereq.mdx b/src/fragments/lib-v1/push-notifications/common/getting_started/cross-platform-prereq.mdx new file mode 100644 index 00000000000..90a8f040f74 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/getting_started/cross-platform-prereq.mdx @@ -0,0 +1,45 @@ +You should have [completed the CLI and project setup steps.](/[platform]/start/project-setup/prerequisites/) + + + + +An application targeting at least iOS 13.0, using Xcode 14.1 or later. + +import apnsPreReq from '/src/fragments/lib/push-notifications/ios/getting_started/apns-pre-req.mdx'; + + + +### Set Entitlements + +Using Amplify Push Notifications with APNs requires the following capabilities: + +- Push Notifications +- Background Processing -> Remote Notifications + +To add these capabilities: + +import iosSetEntitlements from '/src/fragments/lib/push-notifications/ios/getting_started/ios-set-entitlements.mdx'; + + + + + + +An application targeting at least Android SDK API level 24. + +import fcmPreReq from '/src/fragments/lib/push-notifications/android/getting_started/fcm-pre-req.mdx'; + + + +### Applying the Google services plugin + +The Firebase documentation directs you to add the Google services plugin to your app `build.gradle` using the [Gradle plugins DSL](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block). However, we recommend you continue to use the [Legacy Plugin Application](https://docs.gradle.org/current/userguide/plugins.html#sec:old_plugin_application) instead: + +```groovy +apply plugin: 'com.google.gms.google-services' +``` + +If you prefer using the plugins DSL, you should add the `plugins` block to the very top of the file or you may experience issues when building your app for Android. + + + diff --git a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx new file mode 100644 index 00000000000..eb7fdc908e2 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx @@ -0,0 +1,119 @@ +The Push Notifications category allows you to integrate push notifications in your app with Amazon Pinpoint targeting, campaign, and journey management support. You can segment your users, trigger push notifications to your app, and record metrics in Pinpoint when users receive or open notifications. Amazon Pinpoint helps you to create messaging campaigns and journeys targeted to specific user segments or demographics and collect interaction metrics with push notifications. + +import reactNativeExpoCallout from '/src/fragments/lib/push-notifications/react-native/getting_started/expo-callout.mdx'; + + + +## Prerequisites + +import androidPreReq from '/src/fragments/lib/push-notifications/android/getting_started/10_pre_req.mdx'; +import crossPlatformPreReq from '/src/fragments/lib/push-notifications/common/getting_started/cross-platform-prereq.mdx'; +import iosPreReq from '/src/fragments/lib/push-notifications/ios/getting_started/10_pre_req.mdx'; + + + +import iosSetEntitlements from '/src/fragments/lib/push-notifications/ios/getting_started/20_set_entitlements.mdx'; + + + +import iosCreateAppDelegate from '/src/fragments/lib/push-notifications/ios/getting_started/30_create_app_delegate.mdx'; + + + +## Set up backend resources + +To use Push Notifications with Amplify, you have the option to either have the Amplify CLI setup resources for you, or you can use an existing Amazon Pinpoint resource in your AWS account. + + + + +> Prerequisite: [Install and configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/) + + + +Push Notifications requires version **10.8.0+** of the Amplify CLI. You can check your current version with `amplify -version` and upgrade to the latest version with `amplify upgrade`. + + + +To start provisioning push notification resources in the backend, go to your project directory and execute the command: + +```sh +amplify add notifications +``` + +import androidCliResources from '/src/fragments/lib/push-notifications/android/getting_started/20_cli_resources.mdx'; +import iosCliResources from '/src/fragments/lib/push-notifications/ios/getting_started/40_cli_resources.mdx'; +import flutterCliResources from '/src/fragments/lib/push-notifications/flutter/getting_started/20_cli_resources.mdx'; +import reactNativeCliResources from '/src/fragments/lib/push-notifications/react-native/getting_started/20_cli_resources.mdx'; + + + + + + +import androidExistingResources from '/src/fragments/lib/push-notifications/android/getting_started/30_existing_resources.mdx'; +import flutterExistingResources from '/src/fragments/lib/push-notifications/flutter/getting_started/30_existing_resources.mdx'; +import iosExistingResources from '/src/fragments/lib/push-notifications/ios/getting_started/50_existing_resources.mdx'; +import reactNativeExistingResources from '/src/fragments/lib/push-notifications/react-native/getting_started/30_existing_resources.mdx'; + + + + + + +## Install Amplify Libraries + +import androidInstallLib from '/src/fragments/lib/push-notifications/android/getting_started/40_install_lib.mdx'; +import flutterInstallLib from '/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx'; +import iosInstallLib from '/src/fragments/lib/push-notifications/ios/getting_started/60_install_lib.mdx'; +import reactNativeInstallLib from '/src/fragments/lib/push-notifications/react-native/getting_started/40_install_lib.mdx'; + + + +import reactNativeIntegrateNativeModule from '/src/fragments/lib/push-notifications/react-native/getting_started/50_integrate_native_modules.mdx'; + + + +## Initialize Amplify Push Notifications + +import androidInitPN from '/src/fragments/lib/push-notifications/android/getting_started/50_init_push_notifications.mdx'; +import flutterInitPN from '/src/fragments/lib/push-notifications/flutter/getting_started/50_init_push_notifications.mdx'; +import iosInitPN from '/src/fragments/lib/push-notifications/ios/getting_started/70_init_push_notifications.mdx'; +import reactNativeInitPN from '/src/fragments/lib/push-notifications/react-native/getting_started/60_init_push_notifications.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/identify-user.mdx b/src/fragments/lib-v1/push-notifications/common/identify-user.mdx new file mode 100644 index 00000000000..353c9d98eda --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/identify-user.mdx @@ -0,0 +1,42 @@ +This call identifies the current user (which could be unauthenticated or authenticated) to Amazon Pinpoint. The user ID can be any string which identifies the user in the context of your application. + +## Get the user ID from Amplify Auth + +import androidGetUser from '/src/fragments/lib/push-notifications/android/identify_user/10_get_auth_user.mdx'; +import flutterGetUser from '/src/fragments/lib/push-notifications/flutter/identify_user/10_get_auth_user.mdx'; +import iosGetUser from '/src/fragments/lib/push-notifications/ios/identify_user/10_get_auth_user.mdx'; +import reactNativeGetUser from '/src/fragments/lib/push-notifications/react-native/identify_user/10_get_auth_user.mdx'; + + + +## Identify the user to Amazon Pinpoint + +Once you have a string that identifies the current user (either from the Auth category as shown above or through your own application logic), you can identify the user to Amazon Pinpoint with the following: + +import androidSendToPinpoint from '/src/fragments/lib/push-notifications/android/identify_user/20_send_to_pinpoint.mdx'; +import flutterSendToPinpoint from '/src/fragments/lib/push-notifications/flutter/identify_user/20_send_to_pinpoint.mdx'; +import iosSendToPinpoint from '/src/fragments/lib/push-notifications/ios/identify_user/20_send_to_pinpoint.mdx'; +import reactNativeSendToPinpoint from '/src/fragments/lib/push-notifications/react-native/identify_user/20_send_to_pinpoint.mdx'; + + + +import androidSendWithProfile from '/src/fragments/lib/push-notifications/android/identify_user/30_send_to_pinpoint_profile.mdx'; +import iosSendWithProfile from '/src/fragments/lib/push-notifications/ios/identify_user/30_send_to_pinpoint_profile.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx new file mode 100644 index 00000000000..9ac148c28b6 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx @@ -0,0 +1,32 @@ +Push notifications are powerful engagement tools for your users as they can be delivered even when your app is not in the active foreground. As a result, there are many cases where it is helpful (or perhaps necessary) to interact with notification events differently depending upon your app's state. + +- **Foreground state**: Your app is running, active and visible. +- **Background state**: Your app is still running but is not currently active and visible. The user is usually on the home screen or in another app. +- **Terminated state**: Your app is no longer running, even in the background. The user can initiate this by swiping your app away in the app switcher. + +import notificationLifecycle from '/src/fragments/lib/push-notifications/common/interact_with_notifications/notification-lifecycle.mdx'; + + + +import notificationReceived from '/src/fragments/lib/push-notifications/common/interact_with_notifications/notification-received.mdx'; + + + +import notificationOpened from '/src/fragments/lib/push-notifications/common/interact_with_notifications/notification-opened.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-lifecycle.mdx b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-lifecycle.mdx new file mode 100644 index 00000000000..41b397100c2 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-lifecycle.mdx @@ -0,0 +1,37 @@ +## Notification lifecycle + +Before delving into details about the various functions Amplify provides, it can be helpful to better understand the lifecycle of a notification as it moves through your app in its various states once you've integrated Amplify Push Notifications. + +For the purposes of this guide, we will simplify the terminology as follows + +import flutterTerminology from '/src/fragments/lib/push-notifications/flutter/interact_with_notifications/terminology.mdx'; +import reactNativeTerminology from '/src/fragments/lib/push-notifications/react-native/interact_with_notifications/terminology.mdx'; + + + +### In a foreground state + +![Chart of the foreground state lifecycle](/images/push-notifications/cross-platform-lifecycle/pn-lifecycle-foreground.png) + +- Notifications are not displayed when received in foreground +- It is possible for notifications to arrive in your user's notification center but not be opened until a later time +- Since it is possible to interact with the notification center at any time on a device, your user could even open a notification while your app is in the active foreground + +### In a background state + +![Chart of the background state lifecycle](/images/push-notifications/cross-platform-lifecycle/pn-lifecycle-background.png) + +- _Notification A_ represents an example of a notification which was received but never interacted with +- Recall that, in a background state, your app is still running and therefore does not need to be launched — only brought back into the foreground + +### In a terminated state + +![Chart of the terminated state lifecycle](/images/push-notifications/cross-platform-lifecycle/pn-lifecycle-terminated.png) + +- _Notification A_ represents an example of a notification which was received but never interacted with +- Recall that, in a terminated state, your app is no longer running and therefore needs to be launched diff --git a/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-opened.mdx b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-opened.mdx new file mode 100644 index 00000000000..f111326be95 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-opened.mdx @@ -0,0 +1,44 @@ +## Respond to a notification being opened + +When a user taps on a notification displayed on their device, your app will be either launched or brought to the foreground. Knowing the contents of the notification the user interacted with can help you take further action (e.g. follow a deep link) or glean additional insight into your user engagement. + +To help you with this, Amplify provides two ways of handling notifications being opened. It is recommended that you handle both to ensure your users the most consistent and seamless experience. + +| App state | Handle with | +| :---------------------: | :---------------------: | +| Foreground / Background | `onNotificationOpened` | +| Terminated | `getLaunchNotification` | + +### onNotificationOpened + +Add `onNotificationOpened` listeners to respond to a push notification being opened while your app is in a foreground **or** background state. + +import flutterOnNotificationOpened from '/src/fragments/lib/push-notifications/flutter/interact_with_notifications/on-notification-opened.mdx'; +import reactNativeOnNotificationOpened from '/src/fragments/lib/push-notifications/react-native/interact_with_notifications/on-notification-opened.mdx'; + + + +### getLaunchNotification + +When your app is launched from a terminated state, you must call `getLaunchNotification` to obtain the notification which launched your app. + +Calling `getLaunchNotification` _consumes_ the launch notification and will yield a `null` result if: + +- You called it more than once (i.e. subsequent calls will be `null`) +- Another notification was opened while your app was running (either in foreground or background) +- Your app was brought back to the foreground by some other means (e.g. user tapped the app icon) + +import flutterGetLaunchNotification from '/src/fragments/lib/push-notifications/flutter/interact_with_notifications/get-launch-notification.mdx'; +import reactNativeGetLaunchNotification from '/src/fragments/lib/push-notifications/react-native/interact_with_notifications/get-launch-notification.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-received.mdx b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-received.mdx new file mode 100644 index 00000000000..57770fa8001 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-received.mdx @@ -0,0 +1,44 @@ +## Respond to a notification being received + +Push notifications received by your users are useful engagement tools but they also provide a data delivery mechanism to your app! + +Your app will likely need to respond to notifications being received in different ways depending on its state, namely while it is either actively in the foreground (where your app may respond by updating UI) or not (where your app may respond by performing tasks to ensure your app experience is up to date). + +| App state | Handle with | +| :---------------------: | :----------------------------------: | +| Foreground | `onNotificationReceivedInForeground` | +| Background / Terminated | `onNotificationReceivedInBackground` | + +### Notification received in foreground + +Notifications received while your app is in the foreground state do not get displayed. But their contents may be useful for updating your app (e.g. updating the UI to reflect a new inbox message). + +Add `onNotificationReceivedInForeground` listeners to respond to a push notification being received while your app is in a foreground state. + +import flutterOnForegroundNotification from '/src/fragments/lib/push-notifications/flutter/interact_with_notifications/on-foreground-notification.mdx'; +import reactNativeOnForegroundNotification from '/src/fragments/lib/push-notifications/react-native/interact_with_notifications/on-foreground-notification.mdx'; + + + +### Notification received in background + +You may be able to improve your users' experience by having your app perform tasks in the background (e.g. fetching data) so they will have the most up-to-date experience when they next launch your app. + +Add `onNotificationReceivedInBackground` listeners to respond to a push notification being received while your app is in a background **or** terminated state. + +For background notifications to be handled while your app is terminated, it is important to note: + +import flutterOnBackgroundNotification from '/src/fragments/lib/push-notifications/flutter/interact_with_notifications/on-background-notification.mdx'; +import reactNativeOnBackgroundNotification from '/src/fragments/lib/push-notifications/react-native/interact_with_notifications/on-background-notification.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/receive-device-token.mdx b/src/fragments/lib-v1/push-notifications/common/receive-device-token.mdx new file mode 100644 index 00000000000..156a38e3db9 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/receive-device-token.mdx @@ -0,0 +1,20 @@ +Push notifications are delivered to your user's devices through a device token which uniquely identifies your app. Although Amplify will automatically register this token with Amazon Pinpoint, it can still be useful to have access to this token for your app's use cases (e.g. to send direct notifications to a specific device). + +### onTokenReceived + +Add `onTokenReceived` listeners to respond to a token being received by your app. + +A token will be received by your app: + +- On every app launch, including the first install +- When a token changes (this may happen if the service invalidates the token for any reason) + +import flutterOnTokenReceived from '/src/fragments/lib/push-notifications/flutter/receive_device_token/on-token-received.mdx'; +import reactNativeOnTokenReceived from '/src/fragments/lib/push-notifications/react-native/receive_device_token/on-token-received.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/record-notifications.mdx b/src/fragments/lib-v1/push-notifications/common/record-notifications.mdx new file mode 100644 index 00000000000..cf97beb0be0 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/record-notifications.mdx @@ -0,0 +1,21 @@ +Amazon Pinpoint enables you to record when users receive or open push notifications in order to gather metrics on the effectiveness of customer engagement campaigns. The Amplify Push Notifications plugin provides a simple API to record and send these events to Amazon Pinpoint. + +## Record Notification Received + +import android0 from '/src/fragments/lib/push-notifications/android/record_notifications/20_record_notification_received.mdx'; + + + +import ios1 from '/src/fragments/lib/push-notifications/ios/record_notifications/10_notification_received.mdx'; + + + +## Record Notification Opened + +import android3 from '/src/fragments/lib/push-notifications/android/record_notifications/30_record_notification_opened.mdx'; + + + +import ios4 from '/src/fragments/lib/push-notifications/ios/record_notifications/20_notification_opened.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/register-device.mdx b/src/fragments/lib-v1/push-notifications/common/register-device.mdx new file mode 100644 index 00000000000..e679d608c73 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/register-device.mdx @@ -0,0 +1,17 @@ +import ios1 from '/src/fragments/lib/push-notifications/ios/register_device/10_register_intro.mdx'; + + + +import ios4 from '/src/fragments/lib/push-notifications/ios/register_device/20_register_with_apns.mdx'; + + + +## Register with Amazon Pinpoint + +import android0 from '/src/fragments/lib/push-notifications/android/register_device/10_register_with_pinpoint.mdx'; + + + +import ios7 from '/src/fragments/lib/push-notifications/ios/register_device/30_register_with_pinpoint.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/remote-media.mdx b/src/fragments/lib-v1/push-notifications/common/remote-media.mdx new file mode 100644 index 00000000000..dcdac08a5de --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/remote-media.mdx @@ -0,0 +1,3 @@ +import ios0 from '/src/fragments/lib/push-notifications/ios/remote_media/10_remote_media.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx b/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx new file mode 100644 index 00000000000..2890b948232 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx @@ -0,0 +1,61 @@ +Depending on your users' platform and operating system version, it is likely that you will need to request their permission to display push notifications. + +To learn more about the platform-specific guidances for requesting permissions, you can visit the respective documentations for [iOS](https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications) and [Android](https://developer.android.com/develop/ui/views/notifications/notification-permission). To best aid you in giving your users a good permission experience with platform idiomatic flows, Amplify provides the functionality below. + +## Get permission status + +The first step to request permissions from your user is to understand the current status of permissions. Your app may behave differently in response to these possible statuses below. + +- **Should Request** - No permissions have been requested yet. It is idiomatic at this time to simply request for permissions from the user. +- **Should Explain Then Request** - It is recommended at this time to provide some context or rationale to the user explaining why you want to send them push notifications before requesting for permissions. +- **Granted** - Permissions have been granted by the user. No further actions are needed and their app is ready to display notifications. +- **Denied** - Permissions have been denied by the user. Further attempts to request permissions will no longer trigger a permission dialog. Your app should now either degrade gracefully or prompt your user to grant the permissions needed in their device settings. + +import flutterGetPermissionStatus from '/src/fragments/lib/push-notifications/flutter/request_permissions/get-permission-status.mdx'; +import reactNativeGetPermissionStatus from '/src/fragments/lib/push-notifications/react-native/request_permissions/get-permission-status.mdx'; + + + +## Request permissions + +Once you have determined if the current permission status requires you to request permissions from the user, you can call `requestPermissions()` to make that request. + +Amplify requests all supported notification permissions by default. But you can also choose not to request specific permissions. + + + It is recommended that you specify these permissions if needed but it is + important to note that they are ignored by Android + + +- **Alert**: When set to true, requests the ability to display notifications to the user. +- **Sound**: When set to true, requests the ability to play a sound in response to notifications. +- **Badge**: When set to true, requests the ability to update the app's badge. + +import flutterRequestPermissions from '/src/fragments/lib/push-notifications/flutter/request_permissions/request-permissions.mdx'; +import reactNativeRequestPermissions from '/src/fragments/lib/push-notifications/react-native/request_permissions/request-permissions.mdx'; + + + +## Sample permissions flow + +Use `getPermissionStatus()` and `requestPermissions()` together to handle permission request flows. Below is a sample implementation of the expected logic. + +import flutterSamplePermissionsFlow from '/src/fragments/lib/push-notifications/flutter/request_permissions/sample-permissions-flow.mdx'; +import reactNativeSamplePermissionsFlow from '/src/fragments/lib/push-notifications/react-native/request_permissions/sample-permissions-flow.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/setup_push_service/cross-platform-setup.mdx b/src/fragments/lib-v1/push-notifications/common/setup_push_service/cross-platform-setup.mdx new file mode 100644 index 00000000000..a32da00a7fc --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/setup_push_service/cross-platform-setup.mdx @@ -0,0 +1,16 @@ + + + +import apnsSetup from '/src/fragments/lib/push-notifications/ios/setup_push_service/setup-apns.mdx'; + + + + + + +import fcmSetup from '/src/fragments/lib/push-notifications/android/setup_push_service/setup-fcm.mdx'; + + + + + \ No newline at end of file diff --git a/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx b/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx new file mode 100644 index 00000000000..ba45aaf9eb1 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx @@ -0,0 +1,16 @@ +import androidSetupService from '/src/fragments/lib/push-notifications/android/setup_push_service/setup-fcm.mdx'; +import crossPlatformSetupService from '/src/fragments/lib/push-notifications/common/setup_push_service/cross-platform-setup.mdx'; +import iosSetupService from '/src/fragments/lib/push-notifications/ios/setup_push_service/setup-apns.mdx'; + + + +import androidHandlingActions from '/src/fragments/lib/push-notifications/android/setup_push_service/handling-actions.mdx'; + + diff --git a/src/fragments/lib-v1/push-notifications/common/testing.mdx b/src/fragments/lib-v1/push-notifications/common/testing.mdx new file mode 100644 index 00000000000..ebffa1395c2 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/common/testing.mdx @@ -0,0 +1 @@ +You can create messaging campaigns and send push notifications to your app with Amazon Pinpoint! Just follow these instructions on [Amazon Pinpoint User Guide](https://docs.aws.amazon.com/pinpoint/latest/userguide/messages-mobile.html) for the next steps. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx index 7688917e69f..72fae1dafd1 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx @@ -3,7 +3,10 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'Add app badge count', description: 'Get and set the application badge count.', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter' + ] }; export const getStaticPaths = async () => { @@ -22,3 +25,7 @@ export function getStaticProps(context) { import appBadgeCount from '/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/app-badge-count.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx index 4eef23f2102..7825769fd6c 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx @@ -3,7 +3,10 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'Enable rich notifications', description: 'Enable rich notifications for you app.', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter' + ] }; export const getStaticPaths = async () => { @@ -22,3 +25,7 @@ export function getStaticProps(context) { import enableRichNotifications from '/src/fragments/lib-v1/push-notifications/react-native/enable_rich_notifications/enable-rich-notifications.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/enable-rich-notifications.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx index 5bf874a1ab2..e89be8248b6 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx @@ -3,7 +3,10 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'Identify user to Amazon Pinpoint', description: 'Provide information about a user to Amazon Pinpoint.', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter' + ] }; export const getStaticPaths = async () => { @@ -22,3 +25,11 @@ export function getStaticProps(context) { import identifyUser from '/src/fragments/lib-v1/push-notifications/react-native/identify_user/identify-user.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/identify-user.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx index 8516e450773..7d47c934064 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx @@ -5,7 +5,10 @@ export const meta = { title: 'Push Notifications', description: 'Drive customer engagement using push notifications with campaign analytics and targeting', - platforms: ['react-native'], + platforms: [ + 'react-native', + 'flutter' + ], route: '/gen1/[platform]/prev/build-a-backend/push-notifications' }; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx index 4c17dbf7651..a2352a39102 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx @@ -3,7 +3,10 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'Interact with notifications', description: 'Interact with push notifications through various events.', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter' + ] }; export const getStaticPaths = async () => { @@ -22,3 +25,7 @@ export function getStaticProps(context) { import interactWithNotifications from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/interact-with-notifications.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx index dfe6bc08733..8c2f5ce75bd 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx @@ -3,7 +3,10 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'Receive a device token', description: 'Receive a device token for use with push notifications.', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter', + ] }; export const getStaticPaths = async () => { @@ -22,3 +25,7 @@ export function getStaticProps(context) { import receiveDeviceToken from '/src/fragments/lib-v1/push-notifications/react-native/receive-device-token.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/receive-device-token.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx index de3df5dd6ce..951bb6581da 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx @@ -3,7 +3,10 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'Request permissions', description: 'Request permissions to display push notifications to users.', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter' + ] }; export const getStaticPaths = async () => { @@ -22,3 +25,7 @@ export function getStaticProps(context) { import requestPermissions from '/src/fragments/lib-v1/push-notifications/react-native/request-permissions.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx index 03773d5a7a5..cdd99af5786 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx @@ -3,7 +3,10 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'Set up Amplify Push Notifications', description: 'Use of Amplify Push Notifications', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter' + ] }; export const getStaticPaths = async () => { @@ -22,3 +25,11 @@ export function getStaticProps(context) { import gettingStarted from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/getting-started.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx index fae4d6b20ff..b25258258b1 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx @@ -4,7 +4,10 @@ export const meta = { title: 'Set up push notification services', description: 'Learn how to setup the various push notification services for your mobile app.', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter' + ] }; export const getStaticPaths = async () => { @@ -23,3 +26,11 @@ export function getStaticProps(context) { import crossPlatformSetupService from '/src/fragments/lib/push-notifications/common/setup_push_service/cross-platform-setup.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx index 7e54b02245f..27fd205214d 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx @@ -3,7 +3,10 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'Test push notifications', description: 'Overview of testing your push notifications', - platforms: ['react-native'] + platforms: [ + 'react-native', + 'flutter' + ] }; export const getStaticPaths = async () => { @@ -22,3 +25,11 @@ export function getStaticProps(context) { import testing from '/src/fragments/lib/push-notifications/common/testing.mdx'; + +import commonRoute from '/src/fragments/lib-v1/push-notifications/common/testing.mdx'; + + From d83842631600b0efc2afb4f36700124fd6c7a325 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:29:20 -0700 Subject: [PATCH 06/88] chore: transfer existing resources v1 from current to prev --- .../existing-resources/cdk/index.mdx | 835 ++++++++++++++ .../existing-resources/cli/index.mdx | 1012 +++++++++++++++++ .../existing-resources/index.mdx | 29 + 3 files changed, 1876 insertions(+) create mode 100644 src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx create mode 100644 src/pages/[platform]/prev/build-a-backend/existing-resources/index.mdx diff --git a/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx new file mode 100644 index 00000000000..61c80a2ef2f --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx @@ -0,0 +1,835 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Connect to existing AWS resources built with the CDK', + description: "Connect a new app to AWS resources built with the CDK.", + platforms: [ + 'flutter', + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +This guide shows you how to connect a new app to AWS resources you've already created using the [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/). The AWS CDK is an open-source software development framework for defining cloud infrastructure as code with modern programming languages. This infrastructure is then deployed through [AWS CloudFormation](https://aws.amazon.com/cloudformation/). + + + +In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then create and connect a React web app to the GraphQL API. + + + + + +In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then build and integrate a Flutter app with the GraphQL API. + + + +Before you begin, you will need: + + + +* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. +* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. +* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. + + + + + +* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. +* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. +* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. +* Flutter and its command-line tools [installed](https://docs.flutter.dev/get-started/install) and configured. + + + +## Build a GraphQL API using the Amplify Data CDK construct + +The CDK provides a simple way to define cloud infrastructure in code. In this section, we will use the CDK to build out the backend resources for our application. + +**Step 1:** Create a folder for the CDK app by running the following command in your terminal. + +```bash title="Terminal" showLineNumbers={false} +mkdir cdk-backend +``` + +**Step 2:** Navigate to the `cdk-backend` folder and create a new CDK project by running the `cdk init` command and specifying your preferred language. + +```bash title="Terminal" +cd cdk-backend +cdk init --language typescript +``` + +**Step 3:** Open the newly created CDK project using VS Code, or your preferred IDE. + +**Step 4:** In your terminal, navigate to the `cdk_backend` root folder, and install the AWS Amplify Data package by running the following command. + +```bash title="Terminal" showLineNumbers={false} +npm install @aws-amplify/data-construct +``` + +**Step 5:** Update the `cdk_backend/lib/cdk-backend-stack.ts` file as shown in the following code to use the `AmplifyData` construct to create an AWS AppSync API. + +```ts title="lib/cdk-backend-stack.ts" +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { + AmplifyData, + AmplifyDataDefinition +} from '@aws-amplify/data-construct'; + +export class CdkBackendStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // highlight-start + new AmplifyData(this, 'AmplifyCdkData', { + definition: AmplifyDataDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + id: ID! + name: String! + description: String + complete: Boolean + } + `), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } + }); + // highlight-end + } +} +``` + +**Step 6:** Deploy the CDK stacks by running the following command. + +```bash showLineNumbers={false} +cdk deploy +``` + +**Step 7:** The CDK will prepare the resources for deployment and will display the following prompt. Enter **Y** and press **Enter**. + +The CDK preparing for deployment. + +The CDK will deploy the stacks and display the following confirmation. Note the details of the deployed API; we’re going to use them in the next section. + +CDK deploying the stacks. + +Now that you have built the backend API with the CDK, you can connect a frontend. + + + +## Build a React app and connect to the GraphQL API + +In this section, we will connect a React web app to our existing GraphQL API. First, we will create a new React project and install the necessary Amplify packages. Next, we will use the Amplify CLI to generate GraphQL code matching our API structure. Then, we will add React components to perform queries and mutations to manage to-do items in our API. After that, we will configure the Amplify library with details of our backend API. Finally, we will run the application to demonstrate full CRUD functionality with our existing API. + +**Step 1:** Create a React app by running the following command in your terminal. + +```bash title="Terminal" showLineNumbers={false} +npx create-react-app react-amplify-connect +``` + +**Step 2:** Open the newly created React app using VS Code, or your preferred IDE. + +**Step 3:** Install the `aws-amplify`, `@aws-amplify/ui-react`, and `@aws-amplify/cli` packages by running the following commands. + +```bash title="Terminal" showLineNumbers={false} +npm install aws-amplify @aws-amplify/ui-react @aws-amplify/cli +``` + +**Step 4:** Use the awsAppsyncApiId and awsAppsyncRegion values of the CDK stack you created previously to generate the GraphQL client helper code by running the following command. + +awsAppsyncApiId and awsAppsyncRegion values highlighted within the outputs of the CDK stack. + +```react showLineNumbers={false} +npx @aws-amplify/cli codegen add --apiId --region +``` + +**Step 5:** Accept the default values for the prompts. + +```console title="Terminal" showLineNumbers={false} +? Choose the type of app that you're building javascript +? What javascript framework are you using react +✔ Getting API details +? Choose the code generation language target javascript +? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js +? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes +? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 +✔ Downloaded the schema +✔ Generated GraphQL operations successfully and saved at src/graphql +``` + +The Amplify CLI will create the GraphQL client helper code inside the `src/graphql` folder. + +mutations.js, queries.js, and subscriptions.js within the graphql folder. + +**Step 6:** Update the `App.js` file with the following code to create a form with a button to create to-dos, as well as a way to fetch and render the to-do list. + +```jsx title="src/App.js" +import { Amplify} from 'aws-amplify' +import '@aws-amplify/ui-react/styles.css'; +import { useEffect, useState } from 'react'; +import { generateClient } from 'aws-amplify/api'; +import { createTodo } from './graphql/mutations'; +import { listTodos } from './graphql/queries'; + +Amplify.configure({ + API: { + GraphQL: { + // highlight-start + endpoint: '', + region: '', + defaultAuthMode: 'apiKey', + apiKey: '' + // highlight-end + } + } +}); + +const initialState = { name: '', description: '' }; +const client = generateClient(); + +const App = () => { + const [formState, setFormState] = useState(initialState); + const [todos, setTodos] = useState([]); + + useEffect(() => { + fetchTodos(); + }, []); + + function setInput(key, value) { + setFormState({ ...formState, [key]: value }); + } + + async function fetchTodos() { + try { + const todoData = await client.graphql({ + query: listTodos + }); + const todos = todoData.data.listTodos.items; + setTodos(todos); + } catch (err) { + console.log('error fetching todos'); + } + } + + async function addTodo() { + try { + if (!formState.name || !formState.description) return; + const todo = { ...formState }; + setTodos([...todos, todo]); + setFormState(initialState); + await client.graphql({ + query: createTodo, + variables: { + input: todo + } + }); + } catch (err) { + console.log('error creating todo:', err); + } + } + + return ( +
+

Amplify Todos

+ setInput('name', event.target.value)} + style={styles.input} + value={formState.name} + placeholder="Name" + /> + setInput('description', event.target.value)} + style={styles.input} + value={formState.description} + placeholder="Description" + /> + + {todos.map((todo, index) => ( +
+

{todo.name}

+

{todo.description}

+
+ ))} +
+ ); +}; + +const styles = { + container: { + width: 400, + margin: '0 auto', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + padding: 20 + }, + todo: { marginBottom: 15 }, + input: { + border: 'none', + backgroundColor: '#ddd', + marginBottom: 10, + padding: 8, + fontSize: 18 + }, + todoName: { fontSize: 20, fontWeight: 'bold' }, + todoDescription: { marginBottom: 0 }, + button: { + backgroundColor: 'black', + color: 'white', + outline: 'none', + fontSize: 18, + padding: '12px 0px' + } +}; + +export default App; +``` + + +**Step 7:** Run the app using the following command. + +```bash title="Terminal" showLineNumbers={false} +npm start +``` + +**Step 8:** Use the form to create a few to-do items. + + + +In this section, we generated GraphQL code, created React components, configured Amplify, and connected the app to the API. This enabled full CRUD functionality with our backend through queries and mutations. + +
+ + + +## Build a Flutter app and connect to the GraphQL API + +In this section, we will connect a Flutter mobile app to the GraphQL API we created with the CDK. First, we will initialize a Flutter project, define models matching our schema, and use Amplify to integrate CRUD operations. Then we will add UI pages to manage to-do items with queries and mutations. Finally, we will configure Amplify with our backend details and run the app to demonstrate full functionality with our existing API. + +**Step 1:** Create a Flutter app by running the following command in your terminal. + +```bash title="Terminal" showLineNumbers={false} +flutter create flutter_todo_app --platforms=web +``` + +**Step 2:** Open the newly created Flutter app by running the following commands in your terminal. + +```bash title="Terminal" +cd flutter_todo_app +code . -r +``` + +**Step 3**: Update the `pubspec.yaml` file in the app’s root directory to add the required dependencies, as shown in the following code. + +{/* cSpell:disable */} +```yaml title="pubspec.yaml" +name: flutter_todo_app +description: "A new Flutter project." +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: '>=3.2.0 <4.0.0' + +// highlight-start +dependencies: + flutter: + sdk: flutter + amplify_flutter: ^1.0.0 + amplify_api: ^1.0.0 + go_router: ^6.5.5 + cupertino_icons: ^1.0.2 +// highlight-end + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true +``` +{/* cSpell:enable */} + +**Step 4:** Run the following command in your terminal to install the dependencies you added to the `pubspec.yaml` file. + +```bash title="Terminal" showLineNumbers={false} +flutter pub get +``` + +**Step 5:** Install the @aws-amplify/cli packages by running the following command. + +```bash title="Terminal" showLineNumbers={false} +npm install @aws-amplify/cli +``` + +**Step 6:** Create a new folder and name it `graphql`. Inside it, create the file `schema.graphql`. + +The schema.graphql file inside the graphql folder. + +**Step 7:** Update the file `schema.graphql`, as shown in the following example, to define the Todo data model, similar to what you used for the CDK app. + +```graphql title="schema.graphql" +type Todo @model @auth(rules: [{ allow: public }]) { + id: ID! + name: String! + description: String + complete: Boolean +} +``` + +**Step 8:** Run the following command to generate the GraphQL client helper models inside the `lib/models` folder. + +```bash showLineNumbers={false} +npx @aws-amplify/cli codegen models --model-schema ./graphql --target flutter --output-dir ./lib/models +``` + +The ModelProvider.dart and Todo.dart files within the models folder. + +**Step 9:** Create the file `todo_item_page.dart` inside the `lib` folder and update it with the following code to present a form to the user for creating a to-do item. Once submitted, the form will initiate a GraphQL mutation to add or modify the item in the database. + +```dart title="todo_item_page.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../models/ModelProvider.dart'; + +class ToDoItemPage extends StatefulWidget { + const ToDoItemPage({ + required this.todoItem, + super.key, + }); + + final Todo? todoItem; + + @override + State createState() => _ToDoItemPageState(); +} + +class _ToDoItemPageState extends State { + final _formKey = GlobalKey(); + final TextEditingController _nameController = TextEditingController(); + final TextEditingController _descriptionController = TextEditingController(); + + late final String _nameText; + late bool _isDone; + + bool get _isCreate => _todoItem == null; + Todo? get _todoItem => widget.todoItem; + + @override + void initState() { + super.initState(); + + final todoItem = _todoItem; + if (todoItem != null) { + _nameController.text = todoItem.name; + _descriptionController.text = todoItem.description ?? ''; + + _nameText = 'Update to-do Item'; + _isDone = todoItem.complete ?? false; + } else { + _nameText = 'Create to-do Item'; + _isDone = false; + } + } + + @override + void dispose() { + _nameController.dispose(); + _descriptionController.dispose(); + + super.dispose(); + } + + Future submitForm() async { + if (!_formKey.currentState!.validate()) { + return; + } + + // If the form is valid, submit the data + final name = _nameController.text; + final description = _descriptionController.text; + final complete = _isDone; + + // highlight-start + if (_isCreate) { + // Create a new todo item + final newEntry = Todo( + name: name, + description: description.isNotEmpty ? description : null, + complete: complete, + ); + final request = ModelMutations.create(newEntry); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Create result: $response'); + } else { + // Update todoItem instead + final updateToDoItem = _todoItem!.copyWith( + name: name, + description: description.isNotEmpty ? description : null, + complete: complete, + ); + final request = ModelMutations.update(updateToDoItem); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Update result: $response'); + } + // highlight-end + + // Navigate back to homepage after create/update executes + if (mounted) { + context.pop(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_nameText), + ), + body: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _nameController, + decoration: const InputDecoration( + labelText: 'Name (required)', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a name'; + } + return null; + }, + ), + TextFormField( + controller: _descriptionController, + decoration: const InputDecoration( + labelText: 'Description', + ), + ), + SwitchListTile( + title: const Text('Done'), + value: _isDone, + onChanged: (bool value) { + setState(() { + _isDone = value; + }); + }, + secondary: const Icon(Icons.done_all_outlined), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: submitForm, + child: Text(_nameText), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} +``` + +**Step 10:** Create the file `home_page.dart.dart` inside the `lib` folder and update it with the following code. This page will use a GraphQL query to retrieve the list of to-do items and display them in a ListView widget. The page will also allow the user to delete a to-do item by using a GraphQL mutation. + +```dart title="home_page.dart.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../models/ModelProvider.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + var _todoItems = []; + + @override + void initState() { + super.initState(); + _refreshTodoItems(); + } + + // highlight-start + Future _refreshTodoItems() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (response.hasErrors) { + safePrint('errors: ${response.errors}'); + return; + } + setState(() { + _todoItems = todos!.whereType().toList(); + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + } + } + + Future _deleteToDoItem(Todo todoItem) async { + final request = ModelMutations.delete(todoItem); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Delete response: $response'); + await _refreshTodoItems(); + } + // highlight-end + + Future _openToDoItem({Todo? todoItem}) async { + await context.pushNamed('manage', extra: todoItem); + // Refresh the entries when returning from the + // todo item screen. + await _refreshTodoItems(); + } + + Widget _buildRow({ + required String name, + required String description, + required bool isDone, + TextStyle? style, + }) { + return Row( + children: [ + Expanded( + child: Text( + name, + textAlign: TextAlign.center, + style: style, + ), + ), + Expanded( + child: Text( + description, + textAlign: TextAlign.center, + style: style, + ), + ), + Expanded( + child: isDone ? const Icon(Icons.done) : const SizedBox(), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton( + // Navigate to the page to create new todo item + onPressed: _openToDoItem, + child: const Icon(Icons.add), + ), + appBar: AppBar( + title: const Text('To-Do List'), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.only(top: 25), + child: RefreshIndicator( + onRefresh: _refreshTodoItems, + child: Column( + children: [ + if (_todoItems.isEmpty) + const Text('Use the \u002b sign to add new to-do items') + else + const SizedBox(height: 30), + _buildRow( + name: 'Name', + description: 'Description', + isDone: false, + style: Theme.of(context).textTheme.titleMedium, + ), + const Divider(), + Expanded( + child: ListView.builder( + itemCount: _todoItems.length, + itemBuilder: (context, index) { + final todoItem = _todoItems[index]; + return Dismissible( + key: ValueKey(todoItem), + background: const ColoredBox( + color: Colors.red, + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Align( + alignment: Alignment.centerRight, + child: Icon(Icons.delete, color: Colors.white), + ), + ), + ), + onDismissed: (_) => _deleteToDoItem(todoItem), + child: ListTile( + onTap: () => _openToDoItem( + todoItem: todoItem, + ), + title: _buildRow( + name: todoItem.name, + description: todoItem.description ?? '', + isDone: todoItem.complete ?? false, + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} +``` + +**Step 11:** Update `main.dart` to configure Amplify using the details of the GraphQL API you created using the CDK app in the previous section. + +```dart title="main.dart" +import 'package:amplify_api/amplify_api.dart'; + +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'models/ModelProvider.dart'; +import 'home_page.dart'; +import 'todo_item_page.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await _configureAmplify(); + runApp(const MyApp()); +} + +Future _configureAmplify() async { + try { + final api = AmplifyAPI(modelProvider: ModelProvider.instance); + + await Amplify.addPlugins([api]); + const amplifyconfig = '''{ + "api": { + "plugins": { + "awsAPIPlugin": { + "flutter_todo_app": { + "endpointType": "GraphQL", + // highlight-start + "endpoint": "", + "region": "", + "authorizationType": "API_KEY", + "apiKey": "" + // highlight-end + } + } + } + } +}'''; + + await Amplify.configure(amplifyconfig); + + safePrint('Successfully configured'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // GoRouter configuration + static final _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomePage(), + ), + GoRoute( + path: '/manage-todo-item', + name: 'manage', + builder: (context, state) => ToDoItemPage( + todoItem: state.extra as Todo?, + ), + ), + ], + ); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: _router, + debugShowCheckedModeBanner: false, + builder: (context, child) { + return child!; + }, + ); + } +} +``` + +**Step 12:** Run the app in the Chrome browser using the following command. + +```bash title="Terminal" showLineNumbers={false} +flutter run -d chrome +``` + + + + + +## Conclusion + +Congratulations! You used the AWS Amplify Data CDK construct to create a GraphQL API backend using AWS AppSync. You then connected your app to that API using the Amplify libraries. If you have any feedback, leave a [GitHub issue](https://github.com/aws-amplify/docs/issues) or join our [Discord Community](https://discord.gg/amplify)! + +## Clean up resources + +Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the CDK app created above. + +```bash title="Terminal" showLineNumbers={false} + cdk destroy +``` diff --git a/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx b/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx new file mode 100644 index 00000000000..700b0c56e16 --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx @@ -0,0 +1,1012 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Connect to existing AWS resources with Amplify CLI', + description: "Use the Amplify CLI to connect existing AWS resources to a new app.", + platforms: [ + 'flutter', + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +The AWS Amplify CLI (Command Line Interface) CLI provides a simple workflow for provisioning cloud resources like authentication, databases, and storage for apps through the command line. + + +In this guide, you will learn how to connect a new Flutter mobile app to backend resources you've already created using the Amplify CLI. + + + +Connecting a web app? We also offer a version of this guide for integrating existing backends with React using the Amplify CLI. Check out the [React guide](/react/build-a-backend/existing-resources/cli/). + + + + + +In this guide, you will learn how to connect a new React web app to backend resources you've already created using the Amplify CLI. + + + +Connecting a mobile app? We also offer a version of this guide for integrating existing backends with Flutter using the Amplify CLI. Check out the [Flutter guide](/flutter/build-a-backend/existing-resources/cli/). + + + + + +## Connect mobile app to existing AWS resources + +This guide will walk you through connecting a new Flutter app to AWS resources created with Amplify for an existing Flutter app. If you don't already have an existing app, you can follow this [Flutter tutorial](https://docs.amplify.aws/start/getting-started/setup/q/integration/flutter/) to create a budget tracker app that uses Amplify Auth and API resources. + +Before you begin, you will need: + +* An existing Flutter app +* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. +* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. +* Flutter [installed](https://docs.flutter.dev/get-started/install) and configured. +* A text editor combined with Flutter’s command-line tools. For this guide, we will use VS Code, but you can use your preferred IDE. + +### Find the AWS backend details +Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app. + +**Step 1:** In your existing app, open the file `/amplify/team-provider-info.json`. + +The team-provider-info.json file within the file directory of the Amplify app. + +**Step 2:** In the `team-provider-info.json` file, note the following: + +1. The environment you want to use +2. The `AmplifyAppId` for the required environment + +{/* cSpell:disable */} +The environment and AmplifyAppId in team-provider-info.json file. +{/* cSpell:enable */} + +### Create the Flutter app +Now that we have gathered the necessary backend details, we can start building out the new Flutter app. + +**Step 1:** Create a Flutter app by running the following command in your terminal. + +```bash showLineNumbers={false} + +flutter create amplify_connect_resources + +``` + + + +**Step 2:** Open the newly created Flutter app using VS Code by running the following commands in your terminal. + +``` +cd amplify_connect_resources +code . -r +``` + +![Open the created app using VS Code.](/images/existing-resources/app-vscode-mobile-cli.png) + + +**Step 3:** Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal. + +```bash showLineNumbers={false} +amplify pull --appId --envName +``` + + +**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. + +Accept the default values for the prompts about the default editor, type of app, and storage location of the configuration file. Then answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. + +{/* cSpell:disable */} +``` +? Select the authentication method you want to use: AWS profile + +For more information on AWS Profiles, see: +https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html + +? Please choose the profile you want to use AwsWest1 +Amplify AppID found: dorapq24trw9r. Amplify App name is:amplifyBudget +Backend environment dev found in Amplify Console app: amplifyBudget +? Choose your default editor: Visual Studio Code +✔ Choose the type of app that you're building · flutter +Please tell us about your project +? Where do you want to store your configuration file? ./lib/ +? Do you plan on modifying this backend? Yes +⠦ Fetching updates to backend environment: dev from the cloud.⠋ Building resource api/amplifyBudget +⠹ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully. + +Edit your schema at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema +✔ Successfully pulled backend environment dev from the cloud. +✅ + +✅ Successfully pulled backend environment dev from the cloud. +Run 'amplify pull' to sync future upstream changes. +``` +{/* cSpell:enable */} + +The Amplify CLI will add a new folder named `amplify` to the app's root folder, which contains the Amplify project and backend details. It will also add a new Dart file, `amplifyconfiguration.dart`, to the `lib/` folder. The app will use this file at runtime to locate and connect to the backend resources you have provisioned. + +The Amplify folder and amplifyconfiguration.dart file within the file directory of the Amplify app. + +**Step 5:** Update the file `pubspec.yaml` in the app root directory to add the required packages. In this example, we will use the same packages as the app created in this guide. To do this, update the `pubspec.yaml` as shown in the following. + +{/* cSpell:disable */} +```yaml title="pubspec.yaml" +name: budget_tracker +description: A new Flutter project. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: '>=3.1.0 <4.0.0' +dependencies: +// highlight-start + amplify_api: ^1.0.0 + amplify_auth_cognito: ^1.0.0 + amplify_authenticator: ^1.0.0 + amplify_flutter: ^1.0.0 +// highlight-end + flutter: + sdk: flutter +// highlight-next-line + go_router: ^6.5.5 + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true + +``` +{/* cSpell:enable */} + + +**Step 6**: To enable type-safe interaction with the GraphQL schema, use this command to generate the required Dart files. + +```bash showLineNumbers={false} + +amplify codegen models + +``` +The Amplify CLI will generate the Dart files in the `lib/models` folder. + +The models folder within the file directory of the Amplify app. + +**Step 7:** Update the `main.dart` file with the following code to introduce the Amplify Authenticator and integrate Amplify API with your app to create, update, query, and delete `BudgetEntry` items. Typically, you would break this file up into smaller modules but we've kept it as a single file for this guide. + +```bash title="main.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_authenticator/amplify_authenticator.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'amplifyconfiguration.dart'; +import 'models/ModelProvider.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await _configureAmplify(); + runApp(const MyApp()); +} + +// highlight-start +Future _configureAmplify() async { + + try { + + final api = AmplifyAPI(modelProvider: ModelProvider.instance); + final auth = AmplifyAuthCognito(); + await Amplify.addPlugins([api, auth]); + await Amplify.configure(amplifyconfig); + + safePrint('Successfully configured'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } +} +// highlight-end + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // GoRouter configuration + static final _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomeScreen(), + ), + GoRoute( + path: '/manage-budget-entry', + name: 'manage', + builder: (context, state) => ManageBudgetEntryScreen( + budgetEntry: state.extra as BudgetEntry?, + ), + ), + ], + ); + + @override + Widget build(BuildContext context) { + return Authenticator( + child: MaterialApp.router( + routerConfig: _router, + debugShowCheckedModeBanner: false, + // highlight-next-line + builder: Authenticator.builder(), + ), + ); + } +} + +class LoadingScreen extends StatelessWidget { + const LoadingScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } +} + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + var _budgetEntries = []; + + @override + void initState() { + super.initState(); + _refreshBudgetEntries(); + } + + Future _refreshBudgetEntries() async { + try { + final request = ModelQueries.list(BudgetEntry.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (response.hasErrors) { + safePrint('errors: ${response.errors}'); + return; + } + setState(() { + _budgetEntries = todos!.whereType().toList(); + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + } + } + + Future _deleteBudgetEntry(BudgetEntry budgetEntry) async { + final request = ModelMutations.delete(budgetEntry); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Delete response: $response'); + await _refreshBudgetEntries(); + } + + Future _navigateToBudgetEntry({BudgetEntry? budgetEntry}) async { + await context.pushNamed('manage', extra: budgetEntry); + // Refresh the entries when returning from the + // budget entry screen. + await _refreshBudgetEntries(); + } + + double _calculateTotalBudget(List items) { + var totalAmount = 0.0; + for (final item in items) { + totalAmount += item?.amount ?? 0; + } + return totalAmount; + } + + Widget _buildRow({ + required String title, + required String description, + required String amount, + TextStyle? style, + }) { + return Row( + children: [ + Expanded( + child: Text( + title, + textAlign: TextAlign.center, + style: style, + ), + ), + Expanded( + child: Text( + description, + textAlign: TextAlign.center, + style: style, + ), + ), + Expanded( + child: Text( + amount, + textAlign: TextAlign.center, + style: style, + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton( + // Navigate to the page to create new budget entries + onPressed: _navigateToBudgetEntry, + child: const Icon(Icons.add), + ), + appBar: AppBar( + title: const Text('Budget Tracker'), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.only(top: 25), + child: RefreshIndicator( + onRefresh: _refreshBudgetEntries, + child: Column( + children: [ + if (_budgetEntries.isEmpty) + const Text('Use the \u002b sign to add new budget entries') + else + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Show total budget from the list of all BudgetEntries + Text( + 'Total Budget: \$ ${_calculateTotalBudget(_budgetEntries).toStringAsFixed(2)}', + style: const TextStyle(fontSize: 24), + ) + ], + ), + const SizedBox(height: 30), + _buildRow( + title: 'Title', + description: 'Description', + amount: 'Amount', + style: Theme.of(context).textTheme.titleMedium, + ), + const Divider(), + Expanded( + child: ListView.builder( + itemCount: _budgetEntries.length, + itemBuilder: (context, index) { + final budgetEntry = _budgetEntries[index]; + return Dismissible( + key: ValueKey(budgetEntry), + background: const ColoredBox( + color: Colors.red, + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Align( + alignment: Alignment.centerRight, + child: Icon(Icons.delete, color: Colors.white), + ), + ), + ), + onDismissed: (_) => _deleteBudgetEntry(budgetEntry), + child: ListTile( + onTap: () => _navigateToBudgetEntry( + budgetEntry: budgetEntry, + ), + title: _buildRow( + title: budgetEntry.title, + description: budgetEntry.description ?? '', + amount: + '\$ ${budgetEntry.amount.toStringAsFixed(2)}', + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class ManageBudgetEntryScreen extends StatefulWidget { + const ManageBudgetEntryScreen({ + required this.budgetEntry, + super.key, + }); + + final BudgetEntry? budgetEntry; + + @override + State createState() => + _ManageBudgetEntryScreenState(); +} + +class _ManageBudgetEntryScreenState extends State { + final _formKey = GlobalKey(); + final TextEditingController _titleController = TextEditingController(); + final TextEditingController _descriptionController = TextEditingController(); + final TextEditingController _amountController = TextEditingController(); + + late final String _titleText; + + bool get _isCreate => _budgetEntry == null; + BudgetEntry? get _budgetEntry => widget.budgetEntry; + + @override + void initState() { + super.initState(); + + final budgetEntry = _budgetEntry; + if (budgetEntry != null) { + _titleController.text = budgetEntry.title; + _descriptionController.text = budgetEntry.description ?? ''; + _amountController.text = budgetEntry.amount.toStringAsFixed(2); + _titleText = 'Update budget entry'; + } else { + _titleText = 'Create budget entry'; + } + } + + @override + void dispose() { + _titleController.dispose(); + _descriptionController.dispose(); + _amountController.dispose(); + super.dispose(); + } + + Future submitForm() async { + if (!_formKey.currentState!.validate()) { + return; + } + + // If the form is valid, submit the data + final title = _titleController.text; + final description = _descriptionController.text; + final amount = double.parse(_amountController.text); + + if (_isCreate) { + // Create a new budget entry + final newEntry = BudgetEntry( + title: title, + description: description.isNotEmpty ? description : null, + amount: amount, + ); + final request = ModelMutations.create(newEntry); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Create result: $response'); + } else { + // Update budgetEntry instead + final updateBudgetEntry = _budgetEntry!.copyWith( + title: title, + description: description.isNotEmpty ? description : null, + amount: amount, + ); + final request = ModelMutations.update(updateBudgetEntry); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Update result: $response'); + } + + // Navigate back to homepage after create/update executes + if (mounted) { + context.pop(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_titleText), + ), + body: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _titleController, + decoration: const InputDecoration( + labelText: 'Title (required)', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a title'; + } + return null; + }, + ), + TextFormField( + controller: _descriptionController, + decoration: const InputDecoration( + labelText: 'Description', + ), + ), + TextFormField( + controller: _amountController, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + decoration: const InputDecoration( + labelText: 'Amount (required)', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an amount'; + } + final amount = double.tryParse(value); + if (amount == null || amount <= 0) { + return 'Please enter a valid amount'; + } + return null; + }, + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: submitForm, + child: Text(_titleText), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} + +``` + + +**Step 8:** Run the app using the following command, and use the authentication flow to create a user. Then create a few budget items. + + +```bash showLineNumbers={false} + +flutter run + + +``` + + + + + +### Conclusion + +Congratulations! Your new Flutter app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management and a scalable GraphQL API backed by Amazon DynamoDB. + + +### Clean up resources + +Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app. + +```bash showLineNumbers={false} +amplify delete + +``` + +If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the [Build & connect backend section](/[platform]/build-a-backend/) for guides on how to add and connect other backend resources. + + + +## Connect web app to existing AWS resources + +This guide will walk you through connecting a new React web app to the AWS resources created with Amplify for an existing React app. If you don't already have an existing app, you can follow this [React tutorial](https://docs.amplify.aws/react/start/getting-started/introduction/) to create a to-do app that uses Amplify Auth, API, and Hosting resources. + +Before you begin, you will need: + +* An existing React app +* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. +* The Amplify CLI [installed](https://docs.amplify.aws/cli/start/install/) and configured. +* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. + +### Find the AWS backend details +Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app. + +**Step 1:** In your existing app, open the file `/amplify/team-provider-info.json` . + +The team-provider-info.json file within the file directory of the Amplify app. + +**Step 2:** In the `team-provider-info.json` file, note the following: + +1. The environment you want to use +2. The `AmplifyAppId` for the required environment + +{/* cSpell:disable */} +The environment and AmplifyAppId in team-provider-info.json file. +{/* cSpell:enable */} + +### Create the React app +Now that we have gathered the necessary backend details, we can start building out the new React app. + +**Step 1:** Create a React app by running the following command in your terminal. + +```bash showLineNumbers={false} +npx create-react-app react-amplify-connect +``` + +**Step 2:** Open the newly created React app using VS Code by running the following commands in your terminal. + +``` +cd react-amplify-connect +code . -r +``` + +![Open the created app using VS Code.](/images/existing-resources/app-vscode-web-cli.png) + +**Step 3:** Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal. + +```bash showLineNumbers={false} +amplify pull --appId --envName +``` + +**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. + +Accept the default values for the prompts and make sure to answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. + +{/* cSpell:disable */} +``` +? Select the authentication method you want to use: AWS profile + +For more information on AWS Profiles, see: +https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html + +? Please choose the profile you want to use AwsWest1 +Amplify AppID found: dfn3u8j1nvzjc. Amplify App name is: reactamplified +Backend environment dev found in Amplify Console app: reactamplified +? Choose your default editor: Visual Studio Code +✔ Choose the type of app that you're building · javascript +Please tell us about your project +? What javascript framework are you using react +? Source Directory Path: src +? Distribution Directory Path: build +? Build Command: npm run-script build +? Start Command: npm run-script start +? Do you plan on modifying this backend? Yes +⠋ Fetching updates to backend environment: dev from the cloud. +⚠️ WARNING: your GraphQL API currently allows public create, read, update, and delete access to all models via an API Key. To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql/authorization-rules + +⠇ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully. + +Edit your schema at /Users/malakamm/development/react-amplify-connect/amplify/backend/api/reactamplified/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/react-amplify-connect/amplify/backend/api/reactamplified/schema +✔ Successfully pulled backend environment dev from the cloud. +Browserslist: caniuse-lite is outdated. Please run: + npx update-browserslist-db@latest + Why you should do it regularly: https://github.com/browserslist/update-db#readme +✅ + +✅ Successfully pulled backend environment dev from the cloud. +Run 'amplify pull' to sync future upstream changes. +``` +{/* cSpell:enable */} + +The Amplify CLI will add a new folder named `amplify` to the app's root folder, which contains the Amplify project and backend details. + +The amplify folder within the file directory of the Amplify app. + +**Step 5**: Use the following command to generate the GraphQL statements. + +```bash showLineNumbers={false} +amplify codegen add +``` + +**Step 6:** Accept the default values of the prompts. + +``` + +? Choose the code generation language target javascript +? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js +? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes +? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 +✔ Generated GraphQL operations successfully and saved at src/graphql + +``` + +The CLI will add a new folder named `graphql` to the app's root folder, which contains the GraphQL statements. + +The graphql folder within the file directory of the Amplify app. + +**Step 7:** Install the aws-amplify and @aws-amplify/ui-react packages by running the following commands. + +```bash showLineNumbers={false} +npm install aws-amplify @aws-amplify/ui-react +``` + +**Step 8:** Update the `App.js` file with the following code to create a login flow using Amplify UI and a form with a button to create to-dos, as well as a way to fetch and render the list of to-dos. + +```bash title="src/App.js" +import React, { useEffect, useState } from 'react' +import { Amplify, API, graphqlOperation } from 'aws-amplify' +import { createTodo } from './graphql/mutations' +import { listTodos } from './graphql/queries' +import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; + +// highlight-start +import awsExports from "./aws-exports"; +Amplify.configure(awsExports); +// highlight-end + +const initialState = { name: '', description: '' } + +const App = ({ signOut, user }) => { + const [formState, setFormState] = useState(initialState) + const [todos, setTodos] = useState([]) + + useEffect(() => { + fetchTodos() + }, []) + + function setInput(key, value) { + setFormState({ ...formState, [key]: value }) + } + + // highlight-start + async function fetchTodos() { + try { + const todoData = await API.graphql(graphqlOperation(listTodos)) + const todos = todoData.data.listTodos.items + setTodos(todos) + } catch (err) { console.log('error fetching todos') } + } + + async function addTodo() { + try { + if (!formState.name || !formState.description) return + const todo = { ...formState } + setTodos([...todos, todo]) + setFormState(initialState) + await API.graphql(graphqlOperation(createTodo, {input: todo})) + } catch (err) { + console.log('error creating todo:', err) + } + } + // highlight-end + + return ( +
+ Hello {user.username} + +

Amplify Todos

+ setInput('name', event.target.value)} + style={styles.input} + value={formState.name} + placeholder="Name" + /> + setInput('description', event.target.value)} + style={styles.input} + value={formState.description} + placeholder="Description" + /> + + { + todos.map((todo, index) => ( +
+

{todo.name}

+

{todo.description}

+
+ )) + } +
+ ) +} + +const styles = { + container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 }, + todo: { marginBottom: 15 }, + input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 }, + todoName: { fontSize: 20, fontWeight: 'bold' }, + todoDescription: { marginBottom: 0 }, + button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' } +} + +export default withAuthenticator(App); + + +``` + + +**Step 9:** Run the app using the following command. + +```bash showLineNumbers={false} +npm start +``` + +**Step 10:** Change the UI of the app as shown in the following to update the placeholder text of the to-do form and to use the user’s email for the hello message. + +```bash title="src/App.js" +... + return ( +
+ // highlight-next-line + Hello {*user*.attributes.email} + +

Amplify Todos

+ setInput('name', event.target.value)} + style={styles.input} + value={formState.name} + // highlight-next-line + placeholder="ToDo Name" + /> + setInput('description', event.target.value)} + style={styles.input} + value={formState.description} + // highlight-next-line + placeholder="ToDo Description" + /> + + { + todos.map((todo, index) => ( +
+

{todo.name}

+

{todo.description}

+
+ )) + } +
+ ) +.... +``` + +The `App.js` file should now look like the following code snippet. + +```bash title="src/App.js" +import React, { useEffect, useState } from 'react' +import { Amplify, API, graphqlOperation } from 'aws-amplify' +import { createTodo } from './graphql/mutations' +import { listTodos } from './graphql/queries' +import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; + +import awsExports from "./aws-exports"; +Amplify.configure(awsExports); + +const initialState = { name: '', description: '' } + +const App = ({ signOut, user }) => { + const [formState, setFormState] = useState(initialState) + const [todos, setTodos] = useState([]) + + useEffect(() => { + fetchTodos() + }, []) + + function setInput(key, value) { + setFormState({ ...formState, [key]: value }) + } + + async function fetchTodos() { + try { + const todoData = await API.graphql(graphqlOperation(listTodos)) + const todos = todoData.data.listTodos.items + setTodos(todos) + } catch (err) { console.log('error fetching todos') } + } + + async function addTodo() { + try { + if (!formState.name || !formState.description) return + const todo = { ...formState } + setTodos([...todos, todo]) + setFormState(initialState) + await API.graphql(graphqlOperation(createTodo, {input: todo})) + } catch (err) { + console.log('error creating todo:', err) + } + } + + return ( +
+ Hello {user.attributes.email} + +

Amplify Todos

+ setInput('name', event.target.value)} + style={styles.input} + value={formState.name} + placeholder="ToDo Name" + /> + setInput('description', event.target.value)} + style={styles.input} + value={formState.description} + placeholder="ToDo Description" + /> + + { + todos.map((todo, index) => ( +
+

{todo.name}

+

{todo.description}

+
+ )) + } +
+ ) +} + +const styles = { + container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 }, + todo: { marginBottom: 15 }, + input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 }, + todoName: { fontSize: 20, fontWeight: 'bold' }, + todoDescription: { marginBottom: 0 }, + button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' } +} + +export default withAuthenticator(App); + +``` + +**Step 11:** Use the following command to publish your changes. Amplify CLI will publish the changes and display the app URL. + +```bash showLineNumbers={false} +amplify publish +``` + +Amplify CLI publish the app and display a URL. + +**Step 12:** Use the URL to run the app in the browser. + + + +### Conclusion + +Congratulations! Your new React app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management, a scalable GraphQL API backed by Amazon DynamoDB, and a hosting service for publishing your app to the cloud. + +### Clean up resources + +Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app. + +```bash showLineNumbers={false} +amplify delete +``` + +If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the [Build & connect backend section](/[platform]/build-a-backend/) for guides on how to add and connect other backend resources. +
diff --git a/src/pages/[platform]/prev/build-a-backend/existing-resources/index.mdx b/src/pages/[platform]/prev/build-a-backend/existing-resources/index.mdx new file mode 100644 index 00000000000..1026dbb75ec --- /dev/null +++ b/src/pages/[platform]/prev/build-a-backend/existing-resources/index.mdx @@ -0,0 +1,29 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; +import { getChildPageNodes } from '@/utils/getChildPageNodes'; + +export const meta = { + title: 'Existing AWS resources', + description: + 'Use the Amplify CLI or AWS CDK to connect to existing AWS resources.', + platforms: [ + 'flutter', + ], + route: '/[platform]/build-a-backend/existing-resources' +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + const childPageNodes = getChildPageNodes(meta.route); + return { + props: { + platform: context.params.platform, + meta, + childPageNodes + } + }; +} + + From 158d57a48c2ced19bcc79d64813b57800d1aff96 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:47:24 -0700 Subject: [PATCH 07/88] chore: transfer datastore v1 from current to prev --- .../flutter/data-access/delete-snippet.mdx | 18 +++++++----- .../data-access/observe-update-snippet.mdx | 2 +- .../data-access/query-basic-snippet.mdx | 10 +++++-- .../data-access/query-pagination-snippet.mdx | 22 +++++++++----- .../query-predicate-multiple-snippet.mdx | 20 +++++++++---- .../query-predicate-or-snippet.mdx | 20 +++++++++---- .../data-access/query-predicate-snippet.mdx | 20 +++++++++---- .../query-sort-multiple-snippet.mdx | 26 +++++++++++------ .../data-access/query-sort-snippet.mdx | 20 +++++++++---- .../flutter/data-access/save-snippet.mdx | 10 ++++++- .../flutter/data-access/update-snippet.mdx | 18 ++++++++---- .../datastore/flutter/datastore-events.mdx | 6 ++-- .../flutter/getting-started/10_preReq.mdx | 13 ++------- .../flutter/getting-started/20_installLib.mdx | 7 +++-- .../flutter/getting-started/50_codegenCli.mdx | 2 -- .../getting-started/50_initDataStore.mdx | 3 +- .../getting-started/60_saveSnippet.mdx | 20 +++++++++++-- .../getting-started/70_querySnippet.mdx | 10 +++++-- .../getting-started/80_saveSnippet.mdx | 10 ------- .../flutter/relational/delete-snippet.mdx | 23 ++++++++++----- .../flutter/relational/query-snippet.mdx | 18 ++++++++---- .../flutter/relational/save-many-snippet.mdx | 29 +++++++++++++------ .../flutter/relational/save-snippet.mdx | 17 +++++++---- .../flutter/relational/updated-schema.mdx | 2 +- .../flutter/sync/10-installPlugin.mdx | 9 +++--- .../flutter/sync/20-savePredicate.mdx | 14 +++++++-- .../sync/30-savePredicateComparison.mdx | 2 +- .../datastore/flutter/sync/40-clear.mdx | 11 +++++-- .../flutter/sync/50-selectiveSync.mdx | 10 +++++-- .../native_common/getting-started.mdx | 10 +------ .../native_common/getting-started/common.mdx | 4 --- 31 files changed, 257 insertions(+), 149 deletions(-) delete mode 100644 src/fragments/lib-v1/datastore/flutter/getting-started/80_saveSnippet.mdx diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/delete-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/delete-snippet.mdx index 96a56af58fd..846a2ed4aff 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/delete-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/delete-snippet.mdx @@ -1,19 +1,21 @@ Below, you query for an instance with an `id` of `123`, and then delete it, if found: ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + Future deletePostsWithId() async { - final oldPosts = await Amplify.DataStore.query( + final postToDelete = (await Amplify.DataStore.query( Post.classType, where: Post.ID.eq('123'), - ); - // Query can return more than one posts with a different predicate - // For this example, it is ensured that it will return one post - final oldPost = oldPosts.first; + )) + .first; + try { - await Amplify.DataStore.delete(oldPost); - print('Deleted a post'); + await Amplify.DataStore.delete(postToDelete); } on DataStoreException catch (e) { - print('Delete failed: $e'); + safePrint('Something went wrong deleting model: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/observe-update-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/observe-update-snippet.mdx index c09839f80f3..a7954aa93f6 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/observe-update-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/observe-update-snippet.mdx @@ -17,7 +17,7 @@ class _MyAppState extends State { super.initState(); _configure(); } - + // Initialize the Amplify libraries and call `observeQuery` Future _configure() async { // ... diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/query-basic-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/query-basic-snippet.mdx index 1f20a330042..0b88f1155b9 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/query-basic-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/query-basic-snippet.mdx @@ -1,10 +1,14 @@ ```dart -Future readFromDatabase() async { +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + +Future queryPosts() async { try { final posts = await Amplify.DataStore.query(Post.classType); - print('Posts: $posts'); + safePrint('Posts: $posts'); } on DataStoreException catch (e) { - print('Query failed: $e'); + safePrint('Something went wrong querying posts: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/query-pagination-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/query-pagination-snippet.mdx index c66642a327c..31791263e6a 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/query-pagination-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/query-pagination-snippet.mdx @@ -1,12 +1,18 @@ ```dart -// Do not forget to import the following with the other imports at the top of the file -import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; -Future fetchPostsWithPagination(int page) async { - final posts = await Amplify.DataStore.query( - Post.classType, - pagination: QueryPagination(page: page, limit: 25), - ); - print('Posts: $posts'); +import 'models/ModelProvider.dart'; + +Future queryPostsWithPagination(int page) async { + try { + final posts = await Amplify.DataStore.query( + Post.classType, + pagination: QueryPagination(page: page, limit: 25), + ); + safePrint('Posts: $posts'); + } on DataStoreException catch (e) { + safePrint('Something went wrong querying posts: ${e.message}'); + } } + ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-multiple-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-multiple-snippet.mdx index 8250d980d3b..6d02c430249 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-multiple-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-multiple-snippet.mdx @@ -1,9 +1,17 @@ ```dart -Future fetchPublishedWithRatingTwoPosts() async { - final posts = await Amplify.DataStore.query( - Post.classType, - where: Post.RATING.eq(2).and(Post.STATUS.eq(PostStatus.ACTIVE)), - ); - print('Posts: $posts'); +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + +Future queryPublishedPostsWithRatingFour() async { + try { + final posts = await Amplify.DataStore.query( + Post.classType, + where: Post.RATING.eq(4).and(Post.STATUS.eq(PostStatus.ACTIVE)), + ); + safePrint('Published posts that have a rating 4: $posts'); + } on DataStoreException catch (e) { + safePrint('Something went wrong querying posts: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-or-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-or-snippet.mdx index e43137184fe..d0a62af37e1 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-or-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-or-snippet.mdx @@ -1,9 +1,17 @@ ```dart -Future fetchPublishedOrWithRatingTwoPosts() async { - final posts = await Amplify.DataStore.query( - Post.classType, - where: Post.RATING.eq(2).or(Post.STATUS.eq(PostStatus.ACTIVE)), - ); - print('Posts: $posts'); +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + +Future queryPublishedOrWithRatingTwoPosts() async { + try { + final posts = await Amplify.DataStore.query( + Post.classType, + where: Post.RATING.eq(2).or(Post.STATUS.eq(PostStatus.ACTIVE)), + ); + safePrint('Posts that are published, or have a rating 2: $posts'); + } on DataStoreException catch (e) { + safePrint('Something went wrong querying posts: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-snippet.mdx index a61b759b8eb..a49163d59e6 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/query-predicate-snippet.mdx @@ -1,9 +1,17 @@ ```dart -Future fetchPostsMoreThanFourRating() async { - final posts = await Amplify.DataStore.query( - Post.classType, - where: Post.RATING.ge(4), - ); - print('Posts: $posts'); +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + +Future queryPostsWithRatingGreaterThanFour() async { + try { + final posts = await Amplify.DataStore.query( + Post.classType, + where: Post.RATING.ge(4), + ); + safePrint('Posts that have a rating > 4: $posts'); + } on DataStoreException catch (e) { + safePrint('Something went wrong querying posts: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/query-sort-multiple-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/query-sort-multiple-snippet.mdx index 95c7fff1fec..ddb36b63dd0 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/query-sort-multiple-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/query-sort-multiple-snippet.mdx @@ -1,12 +1,20 @@ ```dart -Future fetchPostsFirstInAscendingRatingOrderThenDescendingTitleOrder() async { - final posts = await Amplify.DataStore.query( - Post.classType, - sortBy: [ - Post.RATING.ascending(), - Post.TITLE.descending(), - ], - ); - print('Posts: $posts'); +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + +Future queryPostsFirstInAscendingRatingOrderThenDescendingTitleOrder() async { + try { + final posts = await Amplify.DataStore.query( + Post.classType, + sortBy: [ + Post.RATING.ascending(), + Post.TITLE.descending(), + ], + ); + safePrint('Posts: $posts'); + } on DataStoreException catch (e) { + safePrint('Something went wrong querying posts: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/query-sort-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/query-sort-snippet.mdx index 77a6c8005c6..7b3d0cae273 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/query-sort-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/query-sort-snippet.mdx @@ -1,9 +1,17 @@ ```dart -Future fetchPostsInAscendingOrder() async { - final posts = await Amplify.DataStore.query( - Post.classType, - sortBy: [Post.RATING.ascending()], - ); - print('Posts: $posts'); +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + +Future queryPostsInAscendingOrderByRating() async { + try { + final posts = await Amplify.DataStore.query( + Post.classType, + sortBy: [Post.RATING.ascending()], + ); + safePrint('Posts: $posts'); + } on DataStoreException catch (e) { + safePrint('Something went wrong querying posts: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/save-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/save-snippet.mdx index cdffb8b03cb..e3d64718c4b 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/save-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/save-snippet.mdx @@ -1,4 +1,8 @@ ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + Future savePost() async { final newPost = Post( title: 'New Post being saved', @@ -6,6 +10,10 @@ Future savePost() async { status: PostStatus.INACTIVE, ); - await Amplify.DataStore.save(newPost); + try { + await Amplify.DataStore.save(newPost); + } on DataStoreException catch (e) { + safePrint('Something went wrong saving model: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/update-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/update-snippet.mdx index 4831855e993..bebac670813 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/update-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/update-snippet.mdx @@ -1,13 +1,21 @@ ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + Future updatePost() async { - final postsWithId = await Amplify.DataStore.query( + final oldPost = (await Amplify.DataStore.query( Post.classType, where: Post.ID.eq('123'), - ); + )) + .first; - final oldPost = postsWithId.first; - final newPost = oldPost.copyWith(id: oldPost.id, title: 'Updated Title'); + final newPost = oldPost.copyWith(title: 'Updated Title'); - await Amplify.DataStore.save(newPost); + try { + await Amplify.DataStore.save(newPost); + } on DataStoreException catch (e) { + safePrint('Something went wrong updating model: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/datastore-events.mdx b/src/fragments/lib-v1/datastore/flutter/datastore-events.mdx index fe15ee25093..33ec3b44c20 100644 --- a/src/fragments/lib-v1/datastore/flutter/datastore-events.mdx +++ b/src/fragments/lib-v1/datastore/flutter/datastore-events.mdx @@ -11,9 +11,9 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { - late StreamSubscription? hubSubscription; + late StreamSubscription hubSubscription; - // Initialize a boolean indicating if the network is up + // Initialize a boolean indicating if the network is up bool networkIsUp = false; // Initialize the libraries @@ -22,7 +22,7 @@ class _MyAppState extends State { void observeEvents() { hubSubscription = Amplify.Hub.listen(HubChannel.DataStore, (hubEvent) { if (hubEvent.eventName == 'networkStatus') { - final status = hubEvent.payload as NetworkStatusEvent?; + final status = hubEvent.payload as NetworkStatusEvent?; setState(() { networkIsUp = status?.active ?? false; }); diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx index a556eeac083..25eaf32221e 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx @@ -1,13 +1,6 @@ - [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - - As of Amplify Flutter 0.3.0, CLI version{' '} - {'>'}=7.6.10 is required. When updating to 0.3.0, you must - re-generate your models with the CLI. You can do so by running{' '} - amplify codegen models. - - -- A Flutter application targeting Flutter SDK >= 2.10.0 (stable version) with Amplify libraries integrated +- A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - An iOS configuration targeting at least iOS 13.0 - - An Android configuration targeting at least Android API level 21 (Android 5.0) or above - - For a full example of please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) + - An Android configuration targeting at least Android API level 24 (Android 7.0) or above + - For a full example of please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx index 79ee40c6d8f..07c5c650583 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx @@ -4,14 +4,15 @@ Add the following dependencies to your `pubspec.yaml` file and install dependenc ```yaml environment: - sdk: ">=2.15.0 <3.0.0" + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" dependencies: flutter: sdk: flutter - amplify_flutter: ^0.6.0 - amplify_datastore: ^0.6.0 + amplify_datastore: ^1.0.0-supports-only-mobile + amplify_flutter: ^1.0.0 ``` ### Update The Android Project diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx index 5a59463031a..92d350cda64 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx @@ -1,5 +1,3 @@ -Models can also be generated using the Amplify CLI directly. - In your terminal, make sure you are in your project/root folder and **execute the codegen command**: ```console diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/50_initDataStore.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/50_initDataStore.mdx index f8d91d89a30..4b25b9ac87a 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/50_initDataStore.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/50_initDataStore.mdx @@ -1,4 +1,4 @@ -To initialize the Amplify DataStore, use the `Amplify.addPlugin()` method to add the AWS DataStore Plugin. You also need to import the codegen dart file `ModelProvider.dart`. After that, finish configuring Amplify by calling `configure()`: +To initialize the Amplify DataStore, use the `Amplify.addPlugin()` method to add the Amplify DataStore Plugin. You also need to import the codegen dart file `ModelProvider.dart`. After that, finish configuring Amplify by calling `configure()`: ```dart import 'package:flutter/material.dart'; @@ -27,6 +27,7 @@ class _MyAppState extends State { final datastorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance); await Amplify.addPlugin(datastorePlugin); + try { await Amplify.configure(amplifyconfig); } on AmplifyAlreadyConfiguredException { diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/60_saveSnippet.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/60_saveSnippet.mdx index 3efc9bdfd81..e3d64718c4b 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/60_saveSnippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/60_saveSnippet.mdx @@ -1,5 +1,19 @@ ```dart -Post newPost = Post( - title: 'New Post being saved', rating: 15, status: PostStatus.INACTIVE); -await Amplify.DataStore.save(newPost); +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + +Future savePost() async { + final newPost = Post( + title: 'New Post being saved', + rating: 15, + status: PostStatus.INACTIVE, + ); + + try { + await Amplify.DataStore.save(newPost); + } on DataStoreException catch (e) { + safePrint('Something went wrong saving model: ${e.message}'); + } +} ``` diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/70_querySnippet.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/70_querySnippet.mdx index 1f20a330042..0b88f1155b9 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/70_querySnippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/70_querySnippet.mdx @@ -1,10 +1,14 @@ ```dart -Future readFromDatabase() async { +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + +Future queryPosts() async { try { final posts = await Amplify.DataStore.query(Post.classType); - print('Posts: $posts'); + safePrint('Posts: $posts'); } on DataStoreException catch (e) { - print('Query failed: $e'); + safePrint('Something went wrong querying posts: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/80_saveSnippet.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/80_saveSnippet.mdx deleted file mode 100644 index b6d7094919d..00000000000 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/80_saveSnippet.mdx +++ /dev/null @@ -1,10 +0,0 @@ -```dart -Future savePost() async { - final newPost = Post( - title: 'New Post being saved', - rating: 15, - status: PostStatus.INACTIVE, - ); - await Amplify.DataStore.save(newPost); -} -``` diff --git a/src/fragments/lib-v1/datastore/flutter/relational/delete-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/relational/delete-snippet.mdx index 8ee68609d07..3a463e6cf09 100644 --- a/src/fragments/lib-v1/datastore/flutter/relational/delete-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/relational/delete-snippet.mdx @@ -1,13 +1,20 @@ ```dart -Future deletePostWithId(String id) async { - final postsWithId = await Amplify.DataStore.query( - Post.classType, - where: Post.ID.eq(id), - ); +import 'package:amplify_flutter/amplify_flutter.dart'; - for (final element in postsWithId) { - await Amplify.DataStore.delete(element); - print('Deleted a post'); +import 'models/ModelProvider.dart'; + +Future deletePostWithID123AndItsComments(String id) async { + try { + final post = (await Amplify.DataStore.query( + Post.classType, + where: Post.ID.eq(id), + )) + .first; + + // DataStore also deletes all associated comments with this operation + await Amplify.DataStore.delete(post); + } on DataStoreException catch (e) { + safePrint('Something went wrong deleting models: ${e.message}'); } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/relational/query-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/relational/query-snippet.mdx index 586843a240f..a8418faec7f 100644 --- a/src/fragments/lib-v1/datastore/flutter/relational/query-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/relational/query-snippet.mdx @@ -1,11 +1,19 @@ In order to retrieve all models that are related to a parent model, you can use query predicates with the query API: ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + Future fetchAllCommentsForPostId(String postId) async { - final comments = await Amplify.DataStore.query( - Comment.classType, - where: Comment.POST.eq(postId), - ); - print('Comments: $comments'); + try { + final comments = await Amplify.DataStore.query( + Comment.classType, + where: Comment.POST.eq(postId), + ); + safePrint('Comments: $comments'); + } on DataStoreException catch (e) { + safePrint('Something went wrong querying posts: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/relational/save-many-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/relational/save-many-snippet.mdx index 4e6cfccbd62..92e4584894c 100644 --- a/src/fragments/lib-v1/datastore/flutter/relational/save-many-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/relational/save-many-snippet.mdx @@ -1,25 +1,36 @@ Add the model above to the `schema.graphql` file located by default at `amplify/backend/{api_name}/` and regenerate the models again with the following command: -```console +```bash amplify codegen models ``` Once it is regenerated, save your posts with many-to-many mode like the following: ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + Future savePostAndEditor() async { - final post = Post(title: 'My First Post'); + final post = Post( + title: 'My First Post', + rating: 10, + status: PostStatus.INACTIVE, + ); final editor = User(username: 'Nadia'); final postEditor = PostEditor(post: post, user: editor); - // first you save the post - await Amplify.DataStore.save(post); + try { + // first you save the post + await Amplify.DataStore.save(post); - // secondly, you save the editor/user - await Amplify.DataStore.save(editor); + // secondly, you save the editor/user + await Amplify.DataStore.save(editor); - // then you save the model that links a post with an editor - await Amplify.DataStore.save(postEditor); - print('Saved user, post and postEditor!'); + // then you save the model that links a post with an editor + await Amplify.DataStore.save(postEditor); + } on DataStoreException catch (e) { + safePrint('Something went wrong saving model: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/relational/save-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/relational/save-snippet.mdx index 02269e246b7..39bde1a0ac5 100644 --- a/src/fragments/lib-v1/datastore/flutter/relational/save-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/relational/save-snippet.mdx @@ -1,4 +1,8 @@ ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + Future savePostAndComment() async { final post = Post( title: 'My Post with comments', @@ -6,13 +10,16 @@ Future savePostAndComment() async { status: PostStatus.ACTIVE, ); final comment = Comment( - post: post, // Directly pass in the post instance + post: post, // associate the comment to the post content: 'Loving Amplify DataStore!', ); - await Amplify.DataStore.save(post); - print('Post saved'); - await Amplify.DataStore.save(comment); - print('Comment saved'); + try { + // Make sure to safe the parent Post first + await Amplify.DataStore.save(post); + await Amplify.DataStore.save(comment); + } on DataStoreException catch (e) { + safePrint('Something went wrong querying posts: ${e.message}'); + } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/relational/updated-schema.mdx b/src/fragments/lib-v1/datastore/flutter/relational/updated-schema.mdx index 4faa81ea568..95a028c7d0d 100644 --- a/src/fragments/lib-v1/datastore/flutter/relational/updated-schema.mdx +++ b/src/fragments/lib-v1/datastore/flutter/relational/updated-schema.mdx @@ -26,6 +26,6 @@ type Comment @model { After that, regenerate the models with the following console command: -```console +```bash amplify codegen models ``` diff --git a/src/fragments/lib-v1/datastore/flutter/sync/10-installPlugin.mdx b/src/fragments/lib-v1/datastore/flutter/sync/10-installPlugin.mdx index bd15ecfb209..40309a0c7e6 100644 --- a/src/fragments/lib-v1/datastore/flutter/sync/10-installPlugin.mdx +++ b/src/fragments/lib-v1/datastore/flutter/sync/10-installPlugin.mdx @@ -5,7 +5,7 @@ Although DataStore presents a distinct API, its cloud synchronization functional Make sure you have the following plugin dependency in your `pubspec.yaml`. ```yaml -amplify_api: ^0.6.0 +amplify_api: ^1.0.0 ``` Locate your Amplify initialization code, and add an `AmplifyAPI()` plugin. Your initialization code should already include an `AmplifyDataStore()` plugin from previous steps. Note the new `import` statement for API towards the top of the file. @@ -13,11 +13,10 @@ Locate your Amplify initialization code, and add an `AmplifyAPI()` plugin. Your Be sure to import your API library first: ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_datastore/amplify_datastore.dart'; -import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart'; -// Add the following line +// import the Amplify API plugin import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_datastore/amplify_datastore.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; import 'amplifyconfiguration.dart'; import 'models/ModelProvider.dart'; diff --git a/src/fragments/lib-v1/datastore/flutter/sync/20-savePredicate.mdx b/src/fragments/lib-v1/datastore/flutter/sync/20-savePredicate.mdx index 2229e0403e8..6bc9700457e 100644 --- a/src/fragments/lib-v1/datastore/flutter/sync/20-savePredicate.mdx +++ b/src/fragments/lib-v1/datastore/flutter/sync/20-savePredicate.mdx @@ -1,12 +1,20 @@ ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +import 'models/ModelProvider.dart'; + Future savePredicate(Post post) async { + final post = ...; // get post using the query API + final updatedPost = post.copyWith(title: '[Amplified]'); try { + // if the post title has changed to something else other than + // a string that starts with "[Amplify]", the save will be rejected await Amplify.DataStore.save( - post, + updatedPost, where: Post.TITLE.beginsWith("[Amplify]"), ); - } catch (e) { - print('Could not update post, maybe the title has been changed?'); + } on DataStoreException { + safePrint('Could not update post, maybe the title has been changed?'); } } ``` diff --git a/src/fragments/lib-v1/datastore/flutter/sync/30-savePredicateComparison.mdx b/src/fragments/lib-v1/datastore/flutter/sync/30-savePredicateComparison.mdx index 1968cd7b33d..f23dc414689 100644 --- a/src/fragments/lib-v1/datastore/flutter/sync/30-savePredicateComparison.mdx +++ b/src/fragments/lib-v1/datastore/flutter/sync/30-savePredicateComparison.mdx @@ -13,7 +13,7 @@ Future savePredicateRemotely(Post post) async { post, where: Post.TITLE.beginsWith('[Amplify]'), ); - } catch (e) { + } on DataStoreException catch (e) { ... } } diff --git a/src/fragments/lib-v1/datastore/flutter/sync/40-clear.mdx b/src/fragments/lib-v1/datastore/flutter/sync/40-clear.mdx index b14ef340527..ddce9e1b273 100644 --- a/src/fragments/lib-v1/datastore/flutter/sync/40-clear.mdx +++ b/src/fragments/lib-v1/datastore/flutter/sync/40-clear.mdx @@ -1,11 +1,16 @@ ```dart -StreamSubscription hubSubscription = Amplify.Hub.listen([HubChannel.Auth], (hubEvent) { +import 'dart:async'; + +import 'package:amplify_flutter/amplify_flutter.dart'; + +final hubSubscription = + Amplify.Hub.listen(HubChannel.Auth, (AuthHubEvent hubEvent) async { if (hubEvent.eventName == 'SIGNED_OUT') { try { await Amplify.DataStore.clear(); - print('DataStore is cleared.'); + safePrint('DataStore is cleared as the user has signed out.'); } on DataStoreException catch (e) { - print('Failed to clear DataStore: $e'); + safePrint('Failed to clear DataStore: $e'); } } }); diff --git a/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx b/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx index f86629c34e0..a1bc7cf8940 100644 --- a/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx +++ b/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx @@ -1,11 +1,17 @@ ## Selectively syncing a subset of your data -By default, DataStore downloads the entire contents of your cloud data source to your local device. The max number of records that will be stored is configurable [here](/gen1/[platform]/prev/build-a-backend/more-features/datastore/conflict-resolution/#custom-configuration). +By default, DataStore fetches all the records that you’re authorized to access from your cloud data source to your local device. The maximum number of records that will be stored locally is configurable [here](/gen1/[platform]/build-a-backend/more-features/datastore/conflict-resolution/). -You can utilize selective sync to only persist a subset of your data instead. +You can utilize selective sync to persist a subset of your data instead. Selective sync works by applying predicates to the base and delta sync queries, as well as to incoming subscriptions. + + +Note that selective sync is applied on top of authorization rules you’ve defined on your schema with the `@auth` directive. For more information see the [Setup authorization rules](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/) section. + + + ```dart void _configureAmplify() async { // Update AmplifyDataStore instance like below diff --git a/src/fragments/lib-v1/datastore/native_common/getting-started.mdx b/src/fragments/lib-v1/datastore/native_common/getting-started.mdx index 97f61720d6b..73877ece557 100644 --- a/src/fragments/lib-v1/datastore/native_common/getting-started.mdx +++ b/src/fragments/lib-v1/datastore/native_common/getting-started.mdx @@ -101,7 +101,7 @@ Now you will to convert the platform-agnostic `schema.graphql` into platform-spe Like the initial setup, models can be generated either using the IDE integration or Amplify CLI directly. - + ### Code generation: Platform integration @@ -113,10 +113,6 @@ import android13 from '/src/fragments/lib-v1/datastore/android/getting-started/4 -import flutter14 from '/src/fragments/lib-v1/datastore/flutter/getting-started/40_codegen.mdx'; - - - import codegenPlatformIntegration from '/src/fragments/lib-v1/datastore/native_common/codegen-platform-integration.mdx'; @@ -194,10 +190,6 @@ import android25 from '/src/fragments/lib-v1/datastore/android/getting-started/7 -import flutter26 from '/src/fragments/lib-v1/datastore/flutter/getting-started/80_saveSnippet.mdx'; - - - ### Reading from the database To read from the database, the simplest approach is to query for all records of a given model type. diff --git a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx index 4f6f601aedb..9fe3f614603 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx @@ -83,10 +83,6 @@ import android8 from '/src/fragments/lib-v1/graphqlapi/android/getting-started/4 -import flutter7 from '/src/fragments/lib-v1/graphqlapi/flutter/getting-started/40_codegen.mdx'; - - - ## Install Amplify Libraries import ios9 from '/src/fragments/lib-v1/graphqlapi/ios/getting-started/20_installLib.mdx'; From 9af0e40a34ca5266b3e66a0d92855eb1374c35b0 Mon Sep 17 00:00:00 2001 From: Muhammed Salih Guler Date: Wed, 24 Apr 2024 23:08:27 +0200 Subject: [PATCH 08/88] [Gen 2][Bugfix] Update the flutter mobile support doc. (#7245) * Update the flutter mobile support doc. * Add file name. * Add Safe Area. --- src/pages/gen2/start/mobile-support/index.mdx | 1268 +++++++++++++++++ 1 file changed, 1268 insertions(+) create mode 100644 src/pages/gen2/start/mobile-support/index.mdx diff --git a/src/pages/gen2/start/mobile-support/index.mdx b/src/pages/gen2/start/mobile-support/index.mdx new file mode 100644 index 00000000000..911f2a97d4f --- /dev/null +++ b/src/pages/gen2/start/mobile-support/index.mdx @@ -0,0 +1,1268 @@ +export const meta = { + title: 'Mobile support', + description: + 'Learn more about working with Android, iOS, Flutter, and React Native apps.' +}; + +export function getStaticProps(context) { + return { + props: { + meta + } + }; +} + +Amplify Gen 2 enables backend code sharing between web and mobile apps. The initial focus is on a TypeScript-first experience optimized for web developers. Mobile developers can also leverage the Gen 2 capabilities to build a unified backend for Android, iOS, Flutter and React Native apps. Expect ongoing improvements to streamline mobile workflows as we gather feedback from developers. + +## Prerequisites + +Before you get started, make sure you have the following installed: + +- [Node.js](https://nodejs.org/) v18.17 or later +- [npm](https://www.npmjs.com/) v9 or later +- [git](https://git-scm.com/) v2.14.1 or later +- You will also need to [create an AWS Account](https://portal.aws.amazon.com/billing/signup). Note that AWS Amplify is part of the [AWS Free Tier](https://aws.amazon.com/amplify/pricing/). +- Configure your AWS account to use with Amplify [instructions](/gen2/start/account-setup/). + +## Build a mobile app + +Here is how you can build a To Do application on each platform with CRUD operations: + + + + + + +You need to have [Android Studio and SDK](https://developer.android.com/studio) installed on your machine. + + +**Open Android Studio.** Select **+ Create New Project.** + +![Shows the Android studio welcome window](/images/lib/getting-started/android/set-up-android-studio-welcome.png) + +In **Select a Project Template**, select **Empty Activity** or **Empty Compose Activity**. Press **Next**. + +![Shows Android studio new project window](/images/lib/getting-started/android/set-up-android-studio-select-project-template.png) + +- Enter _MyAmplifyApp_ in the **Name** field +- Select either _Java_ or _Kotlin_ from the **Language** dropdown menu +- Select _API 24: Android 7.0 (Nougat)_ from the **Minimum SDK** dropdown menu +- Press **Finish** + +![Shows Android studio configure project window](/images/lib/getting-started/android/set-up-android-studio-configure-your-project.png) + + + This guide will expect you to use Kotlin DSL for Gradle. If you are using + Groovy DSL, you will need to make some changes to the Gradle files. + + +### Create Amplify Project + +The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. + +```bash +npm create amplify@latest +? Where should we create your project? (.) # press enter +``` + +Running this command will scaffold a lightweight Amplify project in your current project with the following files added: + +```text +├── amplify/ +│ ├── auth/ +│ │ └── resource.ts +│ ├── data/ +│ │ └── resource.ts +│ ├── backend.ts +│ └── package.json +├── node_modules/ +├── .gitignore +├── package-lock.json +├── package.json +└── tsconfig.json +``` + +### Running Local Development Environment + +Amplify gen2 provides a new way to develop applications. Now you are able to run your application with a sandbox environment and generate the configuration files for your application. To run your application with a sandbox environment, you can run the following command: + + + Be sure to add a "raw" folder under app/src/main/res directory if it doesn't + exist. + + +```bash +npx amplify sandbox --config-format=json-mobile --config-out-dir=app/src/main/res/raw +``` + +### Adding Authentication + +After the Amplify creation process, you can see a resource.ts file in the amplify/auth folder. This file contains the configuration for the authentication resource. The base code will enable the authentication with the default configuration. You can change the configuration based on your needs. For more information about the configuration, you can check the [documentation](/gen2/build-a-backend/auth/enable-sign-up/). + +```typescript +import { defineAuth } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + email: true + } +}); +``` + +After you have configured the authentication resource, you can use the Amplify UI libraries to run your authentication flow. Amplify UI is a collection of accessible, themeable, performant ui components that can connect directly to the Amplify resources. + +To use the Amplify UI libraries, you need to add the following dependencies to your app/build.gradle file: + +Be sure to have compileSdk version as 34 or higher. + +```kotlin +dependencies { + implementation("androidx.compose.material3:material3:1.1.0") + implementation("com.amplifyframework.ui:authenticator:1.1.0") + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") +} +``` + +Afterwards create a `MyAmplifyApp` class that extends `Application` and add the following code: + +```kotlin +import android.app.Application +import android.util.Log +import com.amplifyframework.AmplifyException +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin +import com.amplifyframework.core.Amplify + +class MyAmplifyApp: Application() { + override fun onCreate() { + super.onCreate() + + try { + Amplify.addPlugin(AWSCognitoAuthPlugin()) + Amplify.configure(applicationContext) + Log.i("MyAmplifyApp", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error) + } + } +} +``` + +Next call this class in your `AndroidManifest.xml` file: + +```xml + +``` + +Lastly update your MainActivity.kt file to use the Amplify UI components: + +```kotlin +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.amplifyframework.core.Amplify +import com.amplifyframework.ui.authenticator.ui.Authenticator +import .ui.theme.MyAmplifyAppTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MyAmplifyAppTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Authenticator { state -> + Column { + Text( + text = "Hello ${state.user.username}!", + ) + Button(onClick = { + Amplify.Auth.signOut { } + }) { + Text(text = "Sign Out") + } + } + } + } + } + } + } +} +``` + +Now if you run the application on the Android emulator, you should see the authentication flow working. + + + +### Adding GraphQL API + +After the Amplify creation process, you can see a resource.ts file in the amplify/data folder. This file contains the configuration for the GraphQL API resource. + +The default code will create a Todo model with content and isDone property. The authorization rules below specify that owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records. + +```typescript +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + isDone: a.boolean() + }) + .authorization(allow => [allow.owner()]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'userPool' + } +}); +``` + +To generate the model classes out of GraphQL schema, you can run the following command: + +```bash +npx amplify generate graphql-client-code --format=modelgen --model-target=java --out=app/src/main/java +``` + +Now you can see that the model classes are generated under app/src/main/java/com/amplifyframework/datastore/generated/model folder. + + + The generated models are in Java but do not worry Java and Kotlin are + interoperable. You can use the generated models in your Kotlin code. + + +For using GraphQL API, you need to add the following dependencies to your app/build.gradle file: + +```kotlin +dependencies { + implementation("com.amplifyframework:core:2.14.4") + implementation("com.amplifyframework:aws-api:2.14.4") +} +``` + +Afterwards open the `MyAmplifyApp` class and add the following line before the `configure` call: + +```kotlin +Amplify.addPlugin(AWSApiPlugin()) +``` + +Now it is time to update the UI code a bit. Update the `MainActivity` class with the following code: + +```kotlin + +class MainActivity : ComponentActivity() { + + private val todoList = mutableStateListOf() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MyApplicationTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Authenticator { _ -> + Scaffold( + floatingActionButton = { + FloatingActionButton( + onClick = { + val date = Date() + val offsetMillis = TimeZone.getDefault().getOffset(date.time).toLong() + val offsetSeconds = TimeUnit.MILLISECONDS.toSeconds(offsetMillis).toInt() + val temporalDateTime = Temporal.DateTime(date, offsetSeconds) + val todo = Todo.builder() + .createdAt(temporalDateTime) + .updatedAt(temporalDateTime) + .content("My random todo ${System.currentTimeMillis()}") + .isDone(false) + .build() + + Amplify.API.mutate( + ModelMutation.create(todo), + { + Log.i("MyAmplifyApp", "Added Todo with id: ${it.data.id}") + todoList.add(todo) + }, + { Log.e("MyAmplifyApp", "Create failed", it) } + ) + }, + ) { + Icon(Icons.Filled.Add, "Add a random todo.") + } + } + ) { + Column(modifier = Modifier.padding(it)) { + Button(onClick = { + Amplify.Auth.signOut { } + }) { + Text(text = "Sign Out") + } + Text(text = "The list of items will come here.") + } + } + } + } + } + } + } +} +``` + +The `onClick` function of the `FloatingActionButton` will create a random Todo item. Now it is time to add a logic to see the added items. + +First let's add a `TodoScreen` composable function: + +```kotlin +@Composable +fun TodoScreen( + todoList: SnapshotStateList, + onItemUpdated: (Todo) -> Unit, + onItemDeleted: (Todo) -> Unit, +) { + LazyColumn { + todoList.forEach { todo -> + item { + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox( + checked = todo.isDone, + onCheckedChange = { } + ) + Text(todo.content) + } + } + } + } +} +``` + +Next let's update the `MainActivity` class to use the `TodoScreen` composable function. Update the `Authenticator` usage with the following code: + +```kotlin +Authenticator(modifier = Modifier.padding(it)) { _ -> + Column { + Button(onClick = { + Amplify.Auth.signOut { } + }) { + Text(text = "Sign Out") + } + if (todoList.isEmpty()) + Text(text = "The list is empty.\nAdd some items by clicking the Floating Action Button.") + else + TodoScreen( + todoList, + onItemUpdated = { todo -> + val foundItem = + todoList.firstOrNull { it.id == todo.id } + if (foundItem != null) { + val index = todoList.indexOf(foundItem) + todoList.removeAt(index) + Log.i("updated", todo.toString()) + todoList.add(index, todo) + } + }, + ) { todo -> todoList.remove(todo) } + } +} +``` + +Now add a `todoList` variable to the `MainActivity` class before the `onCreate` call and call the `refreshItems` function before the `setContent` call: + +```kotlin +override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + refreshItems() +... +} +``` + +Lastly create a function to fetch the items. Add the following code to the `MainActivity` class: + +```kotlin +private fun refreshItems() { + Amplify.API.query( + ModelQuery.list(Todo::class.java), + { response -> + val items = response.data + items.forEach { todo -> + val foundItem = todoList.firstOrNull { it.id == todo.id } + if (foundItem != null) { + val index = todoList.indexOf(foundItem) + todoList.removeAt(index) + todoList.add(index, todo) + } else { + todoList.add(todo) + } + } + Log.i("MyAmplifyApp", "Queried items: $items") + }, + { Log.e("MyAmplifyApp", "Query failure", it) } + ) +} +``` + +Now let's update and delete the items. For update, add the following code to the `onCheckedChange` method of the `Checkbox` widget: + +```kotlin +val newTodo = todo.copyOfBuilder().isDone(it).build() +Amplify.API.mutate( + ModelMutation.update(newTodo), + { + Log.i("MyAmplifyApp", "Updated Todo with id: ${todo.id}") + onItemUpdated(newTodo) + }, + { Log.e("MyAmplifyApp", "Update failed") } +) +``` + +For deleting add a long click behavior with the `Modifier.combinedClickable` modifier. To add it, update the Row composable call in the `TodoScreen` composable function with the following code: + +```kotlin +Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.combinedClickable( + onClick = { + val newTodo = todo.copyOfBuilder().isDone(!todo.isDone).build() + Amplify.API.mutate( + ModelMutation.update(newTodo), + { + Log.i("MyAmplifyApp", "Updated Todo with id: ${todo.id}") + onItemUpdated(newTodo) + }, + { Log.e("MyAmplifyApp", "Update failed") } + ) + }, + onLongClick = { + Amplify.API.mutate( + ModelMutation.delete(todo), + { + Log.i("MyAmplifyApp", "Deleted Todo with id: ${todo.id}") + onItemDeleted(todo) + }, + { Log.e("MyAmplifyApp", "Delete failed") } + ) + }, + ) +) +``` + +With the click, we update the checkbox but with the long click we remove it. Now if you run the application you should see the following flow. + + + +You can terminate the sandbox environment now to clean up the project. + +### Publishing changes to cloud + +For publishing the changes to cloud, you need to create a remote git repository. For a detailed guide, you can follow the link [here](/gen2/start/quickstart/#create-remote-git-repository). + + + + + For using Flutter with Amplify Gen2, you need to have a Flutter version higher than 3.3.0 and setup the editor of you + + + You can follow the [official documentation](https://flutter.dev/docs/get-started/install) to install Flutter on your machine and check the [editor documentation](https://docs.flutter.dev/get-started/editor) for setting up your editor. + + + Once you have installed Flutter, you can create a new Flutter project using the following command: + + ```bash + flutter create my_amplify_app + ``` + +### Create Amplify Project + +The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. + +```bash +npm create amplify@latest +? Where should we create your project? (.) # press enter +``` + +Running this command will scaffold a lightweight Amplify project in your current project with the following files added: + +```text +├── amplify/ +│ ├── auth/ +│ │ └── resource.ts +│ ├── data/ +│ │ └── resource.ts +│ ├── backend.ts +│ └── package.json +├── node_modules/ +├── .gitignore +├── package-lock.json +├── package.json +└── tsconfig.json +``` + +### Running Local Development Environment + +Amplify gen2 provides a new way to develop applications. Now you are able to run your application with a sandbox environment and generate the configuration files for your application. To run your application with a sandbox environment, you can run the following command: + +```bash +npx amplify sandbox --config-format dart --config-out-dir lib +``` + +### Adding Authentication + +After the Amplify creation process, you can see a resource.ts file in the amplify/auth folder. This file contains the configuration for the authentication resource. The base code will enable the authentication with the default configuration. You can change the configuration based on your needs. For more information about the configuration, you can check the [documentation](/gen2/build-a-backend/auth/enable-sign-up/). + +```typescript +import { defineAuth } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + email: true + } +}); +``` + +After you have configured the authentication resource, you can use the Amplify UI libraries to run your authentication flow. Amplify UI is a collection of accessible, themeable, performant ui components that can connect directly to the Amplify resources. + +To use the Amplify UI libraries, you need to add the following dependencies to your pubspec.yaml file: + +```yaml +dependencies: + amplify_flutter: ^1.0.0 + amplify_auth_cognito: ^1.0.0 + amplify_authenticator: ^1.0.0 +``` + +You will add: + +- `amplify_flutter` to connect your application with the Amplify resources. +- `amplify_auth_cognito` to connect your application with the Amplify Cognito resources. +- `amplify_authenticator` to use the Amplify UI components. + +After adding the dependencies, you need to run the following command to install the dependencies: + +```bash +flutter pub get +``` + +Lastly update your main.dart file to use the Amplify UI components: + +```dart title="main.dart" +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_authenticator/amplify_authenticator.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; + +import 'amplifyconfiguration.dart'; + +Future main() async { + try { + WidgetsFlutterBinding.ensureInitialized(); + await _configureAmplify(); + runApp(const MyApp()); + } on AmplifyException catch (e) { + runApp(Text("Error configuring Amplify: ${e.message}")); + } +} + +Future _configureAmplify() async { + try { + await Amplify.addPlugin(AmplifyAuthCognito()); + await Amplify.configure(amplifyConfig); + safePrint('Successfully configured'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override + Widget build(BuildContext context) { + return Authenticator( + child: MaterialApp( + builder: Authenticator.builder(), + home: const Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SignOutButton(), + Text('TODO Application'), + ], + ), + ), + ), + ), + ); + } +} +``` + +The **Authenticator** widget provides an authentication flow according to the resources that has been configured. If you run the application now (on Flutter you can run your applications on Web, Desktop and Mobile), you can see the authentication flow working. + + + +### Adding Data + +After the Amplify creation process, you can see a resource.ts file in the amplify/data folder. This file contains the configuration for the GraphQL API resource. + +The default code will create a Todo model with content and isDone property. The authorization rules below specify that owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records. + +```typescript +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + isDone: a.boolean(), + }) + .authorization(allow => [allow.owner()]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "userPool", + }, +}); +``` + +To generate the model classes out of GraphQL schema, you can run the following command: + +```bash +npx amplify generate graphql-client-code --format modelgen --model-target dart --out lib/models +``` + +This will generate dart models under lib/models folder. + +For using GraphQL API, you need to add the following dependencies to your pubspec.yaml file: + +```yaml +dependencies: + amplify_api: ^1.0.0 +``` + +You will add `amplify_api` to connect your application with the Amplify API. + +After adding the dependencies, update the `_configureAmplify` method in your main.dart file to use the Amplify API: + +```dart title="main.dart" +Future _configureAmplify() async { + try { + await Amplify.addPlugins( + [ + AmplifyAuthCognito(), + AmplifyAPI(modelProvider: ModelProvider.instance), + ], + ); + await Amplify.configure(amplifyConfig); + safePrint('Successfully configured'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } +} +``` + +Next create a new widget called `TodoScreen` and add the following code: + +```dart title="main.dart" + +class TodoScreen extends StatefulWidget { + const TodoScreen({super.key}); + + @override + State createState() => _TodoScreenState(); +} + +class _TodoScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton.extended( + label: const Text('Add Random Todo'), + onPressed: () async { + final newTodo = Todo( + id: uuid(), + content: "Random Todo ${DateTime.now().toIso8601String()}", + isDone: false, + createdAt: TemporalDateTime(DateTime.now()), + updatedAt: TemporalDateTime(DateTime.now()), + ); + final request = ModelMutations.create(newTodo); + final response = await Amplify.API.mutate(request: request).response; + if (response.hasErrors) { + safePrint('Creating Todo failed.'); + } else { + safePrint('Creating Todo successful.'); + } + _refreshTodos(); + }, + ), + body: const Placeholder(), + ); + } +} +``` + +This will create a random Todo every time a user clicks on the floating action button. You can see the `ModelMutations.create` method is used to create a new Todo. + +And update the `MyApp` widget in your **main.dart** file like the following: + +```dart title="main.dart" +class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override + Widget build(BuildContext context) { + return Authenticator( + child: MaterialApp( + builder: Authenticator.builder(), + home: const SafeArea( + child: Scaffold( + body: Column( + children: [ + SignOutButton(), + Expanded(child: TodoScreen()), + ], + ), + ), + ), + ), + ); + } +} +``` + +Next add a `_todos` list in `_TodoScreenState` to add the results from the API and call the refresh function: + +```dart title="main.dart" +List _todos = []; + +@override +void initState() { + super.initState(); + _refreshTodos(); +} +``` + + +and create a new function called `_refreshTodos`: + +```dart title="main.dart" +Future _refreshTodos() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (response.hasErrors) { + safePrint('errors: ${response.errors}'); + return; + } + setState(() { + _todos = todos!.whereType().toList(); + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + } +} +``` + +and update the body with the following code: + +```dart title="main.dart" +body: _todos.isEmpty == true + ? const Center( + child: Text( + "The list is empty.\nAdd some items by clicking the floating action button.", + textAlign: TextAlign.center, + ), + ) + : ListView.builder( + itemCount: _todos.length, + itemBuilder: (context, index) { + final todo = _todos[index]; + return Dismissible( + key: UniqueKey(), + confirmDismiss: (direction) async { + return false; + }, + child: CheckboxListTile.adaptive( + value: todo.isDone, + title: Text(todo.content!), + onChanged: (isChecked) async { }, + ), + ); + }, + ), +``` + +Now let's add a update and delete functionality. + +For update, add the following code to the `onChanged` method of the `CheckboxListTile.adaptive` widget: + +```dart title="main.dart" +final request = ModelMutations.update( + todo.copyWith(isDone: isChecked!), +); +final response = + await Amplify.API.mutate(request: request).response; +if (response.hasErrors) { + safePrint('Updating Todo failed. ${response.errors}'); +} else { + safePrint('Updating Todo successful.'); + await _refreshTodos(); +} +``` + +This will call the `ModelMutations.update` method to update the Todo with a copied/updated version of the todo item. So now the checkbox will get an update as well. + +For delete functionality, add the following code to the `confirmDismiss` method of the `Dismissible` widget: + +```dart title="main.dart" +if (direction == DismissDirection.endToStart) { + final request = ModelMutations.delete(todo); + final response = + await Amplify.API.mutate(request: request).response; + if (response.hasErrors) { + safePrint('Updating Todo failed. ${response.errors}'); + } else { + safePrint('Updating Todo successful.'); + await _refreshTodos(); + return true; + } +} +return false; +``` + +This will delete the Todo item when the user swipes the item from right to left. Now if you run the application you should see the following flow. + + + +You can terminate the sandbox environment now to clean up the project. + +### Publishing changes to cloud + +For publishing the changes to cloud, you need to create a remote git repository. For a detailed guide, you can follow the link [here](/gen2/start/quickstart/#create-remote-git-repository). + + + + + + + You need to have [Xcode and Developer + Tooling](https://developer.apple.com/xcode/) installed on your machine. + + +Open Xcode and select **Create New Project...** + +![Shows the Xcode starter video to start project](/images/lib/getting-started/ios/set-up-swift-1.png) + +In the next step select the **App** template under **iOS**. Click on next. + +![Shows the template of apps for iOS](/images/lib/getting-started/ios/set-up-swift-2.png) + +Next steps are: + +- Adding a _Product Name_ (e.g. MyAmplifyApp) +- Select a _Team_ (e.g. None) +- Select a _Organization Identifier_ (e.g. com.example) +- Select **SwiftUI** an _Interface_. +- Press **Next** + +![Shows the project details dialog](/images/lib/getting-started/ios/set-up-swift-3.png) + +Now you should have your project created. + +![Shows the base project for SwiftUI](/images/lib/getting-started/ios/set-up-swift-4.png) + +### Create Amplify Project + +The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. + +```bash +npm create amplify@latest +? Where should we create your project? (.) # press enter +``` + +Running this command will scaffold a lightweight Amplify project in your current project with the following files added: + +```text +├── amplify/ +│ ├── auth/ +│ │ └── resource.ts +│ ├── data/ +│ │ └── resource.ts +│ ├── backend.ts +│ └── package.json +├── node_modules/ +├── .gitignore +├── package-lock.json +├── package.json +└── tsconfig.json +``` + +### Running Local Development Environment + +Amplify gen2 provides a new way to develop applications. Now you are able to run your application with a sandbox environment and generate the configuration files for your application. To run your application with a sandbox environment, you can run the following command: + +```bash +npx amplify sandbox --config-format=json-mobile +``` + +Once the sandbox environment is running, you would also generate the configuration files for your application. However, Xcode won't be able to recognize them. For recognizing the files, you need to drag and drop the generated files to your project. + + + +### Adding Authentication + +After the Amplify creation process, you can see a resource.ts file in the amplify/auth folder. This file contains the configuration for the authentication resource. The base code will enable the authentication with the default configuration. You can change the configuration based on your needs. For more information about the configuration, you can check the [documentation](/gen2/build-a-backend/auth/enable-sign-up/). + +```typescript +import { defineAuth } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + email: true + } +}); +``` + +After you have configured the authentication resource, you can use the Amplify UI libraries to run your authentication flow. Amplify UI is a collection of accessible, themeable, performant ui components that can connect directly to the Amplify resources. + +Open your project in Xcode and select **File > Add Packages...** and add the following dependencies: + +![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-5.png) + +- Amplify Library for Swift: Enter its GitHub URL (https://github.com/aws-amplify/amplify-swift), select **Up to Next Major Version** and click **Add Package Dependencies...** and select the following libraries: + + - Amplify + - AWSCognitoAuthPlugin + +![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-6.png) + +- Amplify UI Swift - Authenticator: Enter its GitHub URL (https://github.com/aws-amplify/amplify-ui-swift-authenticator), select **Up to Next Major Version** and click **Add Package Dependencies...** and select the following libraries: + - Authenticator + +![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-7.png) + +Now update the `MyAmplifyAppApp` class with the following code: + +```swift +import Amplify +import Authenticator +import AWSCognitoAuthPlugin +import SwiftUI + +@main +struct MyApp: App { + init() { + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.configure() + } catch { + print("Unable to configure Amplify \(error)") + } + } + + var body: some Scene { + WindowGroup { + Authenticator { state in + VStack { + Button("Sign out") { + Task { + await state.signOut() + } + } + Spacer() + Button(action: { + Task { + await createTodo() + await listTodos() + } + }) { + HStack { + Text("Add a New Todo") + Image(systemName: "plus") + } + } + .accessibilityLabel("New Todo") + } + } + } + } +} +``` + +This will add the authentication flow by using the Authenticator component and add a sign out button with a create todo button. + +If you run the application now, you can see that the authentication flow is working. + + + +### Adding Data + +After the Amplify creation process, you can see a resource.ts file in the amplify/data folder. This file contains the configuration for the GraphQL API resource. + +The default code will create a Todo model with content and isDone property. The authorization rules below specify that owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records. + +```typescript +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + isDone: a.boolean() + }) + .authorization(allow => [allow.owner()]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'userPool' + } +}); +``` + +To generate the model classes out of GraphQL schema, you can run the following command: + +```bash +npx amplify generate graphql-client-code --format=modelgen +``` + +Move the generated files to your project. You can do this by dragging and dropping the files to your project. + +![Shows the drag and drop phase](/images/lib/getting-started/ios/set-up-swift-8.png) + +Once you are done, add the API dependencies to your project. Select **File > Add Package Dependencies...** and add the `AWSAPIPlugin`. + +![Shows the Amplify API library for Swift selected](/images/lib/getting-started/ios/set-up-swift-9.png) + +Next, update the `init` part of your `MyAmplifyAppApp.swift` file with the following code: + +```swift +init() { + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) + try Amplify.configure() + } catch { + print("Unable to configure Amplify \(error)") + } +} +``` + +Now it is time to update the UI code a bit. Create a `createTodo` function in the `MyAmplifyAppApp.swift` file with the following code: + +```swift +func createTodo() async { + let creationTime = Temporal.DateTime.now() + let todo = Todo( + content: "Random Todo \(creationTime)", + isDone: false, + createdAt: creationTime, + updatedAt: creationTime + ) + do { + let result = try await Amplify.API.mutate(request: .create(todo)) + switch result { + case .success(let todo): + print("Successfully created todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to create todo: ", error) + } catch { + print("Unexpected error: \(error)") + } +} +``` + +The code above will create a random todo with the current time. + +Next create a `listTodos` function in the `MyAmplifyAppApp.swift` file with the following code to have the logic of listing the items: + +```swift +func listTodos() async { + let request = GraphQLRequest.list(Todo.self) + do { + let result = try await Amplify.API.query(request: request) + switch result { + case .success(let todos): + self.todos = todos.elements + print("Successfully retrieved list of todos: \(todos)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to query list of todos: ", error) + } catch { + print("Unexpected error: \(error)") + } +} +``` + +This will assign the value of the fetched todos into a State object. Be sure to create it before the `body` property: + +```swift +@State var todos: [Todo] = [] +``` + +Now let's update the UI code to show the todos. Update the `VStack` in the `MyAmplifyAppApp.swift` file with the following code: + +```swift +VStack { + Button("Sign out") { + Task { + await state.signOut() + } + } + List(todos, id: \.id) { todo in + Text(todo.content!) + } + Button(action: { + Task { + await createTodo() + await listTodos() + } + }) { + HStack { + Text("Add a New Todo") + Image(systemName: "plus") + } + } + .accessibilityLabel("New Todo") +}.task { + await listTodos() +} +``` + + + Throughout the Swift implementation, the async/await pattern has been used and + for using it easily, we take advantage of the Task structure. For more + information about the Task structure, you can check the + [documentation](https://developer.apple.com/documentation/swift/task). + + +The code above will fetch the todos once the VStack is shown. It will also create a todo and update the todo list each time a todo is created. + +Next step is to update and delete the todos. For that, create `updateTodo` and `deleteTodo` functions in the `MyAmplifyAppApp.swift` file with the following code: + +```swift +func deleteTodo(todo: Todo) async { + do { + let result = try await Amplify.API.mutate(request: .delete(todo)) + switch result { + case .success(let todo): + print("Successfully deleted todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to deleted todo: ", error) + } catch { + print("Unexpected error: \(error)") + } +} + +func updateTodo(todo: Todo) async { + do { + let result = try await Amplify.API.mutate(request: .update(todo)) + switch result { + case .success(let todo): + print("Successfully updated todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to updated todo: ", error) + } catch { + print("Unexpected error: \(error)") + } +} +``` + +Lastly, update the `List` in the `MyAmplifyAppApp.swift` file with the following code: + +```swift +List(todos, id: \.id) { todo in + @State var isToggled = todo.isDone! + Toggle(isOn: $isToggled + ) { + Text(todo.content!) + }.onTapGesture { + var updatedTodo = todos.first {$0.id == todo.id}! + updatedTodo.isDone = !todo.isDone! + Task { + await updateTodo(todo: updatedTodo) + await listTodos() + } + } + .onChange(of: isToggled) { oldValue, newValue in + var updatedTodo = todos.first {$0.id == todo.id}! + updatedTodo.isDone = newValue + Task { + await updateTodo(todo: updatedTodo) + await listTodos() + } + } + .toggleStyle(.switch) + .onLongPressGesture { + Task { + await deleteTodo(todo: todo) + await listTodos() + } + } +} +``` + +This will update the UI to show a toggle to update the todo and a long press gesture to delete the todo. Now if you run the application you should see the following flow. + + + +You can terminate the sandbox environment now to clean up the project. + +### Publishing changes to cloud + +For publishing the changes to cloud, you need to create a remote git repository. For a detailed guide, you can follow the link [here](/gen2/start/quickstart/#create-remote-git-repository). + + + + From 2b79263b4d1e4c8bae7aadf944072bcb544f6975 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:50:25 -0700 Subject: [PATCH 09/88] chore: change link and maintenance call out from v0 to v1 --- src/components/Breadcrumbs/index.tsx | 2 +- src/fragments/lib-v1/flutter-maintenance.mdx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Breadcrumbs/index.tsx b/src/components/Breadcrumbs/index.tsx index 3af5bfcce09..8b7d0f337d3 100644 --- a/src/components/Breadcrumbs/index.tsx +++ b/src/components/Breadcrumbs/index.tsx @@ -21,7 +21,7 @@ const overrides = { '/gen1/javascript/prev': 'V5', '/gen1/swift/prev': 'V1', '/gen1/android/prev': 'V1', - '/gen1/flutter/prev': 'V0', + '/gen1/flutter/prev': 'V1', '/gen1/react/prev': 'V5', '/gen1/react-native/prev': 'V5', '/gen1/angular/prev': 'V5', diff --git a/src/fragments/lib-v1/flutter-maintenance.mdx b/src/fragments/lib-v1/flutter-maintenance.mdx index ba3e0be8264..4b0f4ecf7f6 100644 --- a/src/fragments/lib-v1/flutter-maintenance.mdx +++ b/src/fragments/lib-v1/flutter-maintenance.mdx @@ -1,9 +1,9 @@ -Amplify Flutter v0 is now in **Maintenance Mode** until July 19th, 2024. This means that we will continue to include updates to ensure compatibility with backend services and security. No new features will be introduced in v0. +Amplify Flutter v1 is now in **Maintenance Mode** until April 30th, 2025. This means that we will continue to include updates to ensure compatibility with backend services and security. No new features will be introduced in v1. -Please use the latest version (v1) of [Amplify Flutter](/gen1/[platform]/tools/libraries/) to get started. +Please use the latest version (v2) of [Amplify Flutter](/gen1/[platform]/tools/libraries/) to get started. -If you are currently using v0, follow [these instructions](/gen1/[platform]/start/project-setup/upgrade-guide/) to upgrade to v1. +If you are currently using v1, follow [these instructions](/gen1/[platform]/start/project-setup/upgrade-guide/) to upgrade to v2. From 4813ea6cc9f5d383207f15ac5b043741a76c6269 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Fri, 26 Apr 2024 15:25:16 -0400 Subject: [PATCH 10/88] chore: update flutter storage apis (#7326) --- .../configureaccess/10_protected_upload.mdx | 25 -- .../configureaccess/20_protected_download.mdx | 33 -- .../configureaccess/30_private_upload.mdx | 26 -- .../configureaccess/40_private_download.mdx | 26 -- .../configureaccess/50_customization.mdx | 104 ------ src/fragments/lib/storage/flutter/copy.mdx | 25 +- .../lib/storage/flutter/download.mdx | 135 ++++++-- .../lib/storage/flutter/get-properties.mdx | 8 +- src/fragments/lib/storage/flutter/list.mdx | 26 +- src/fragments/lib/storage/flutter/move.mdx | 29 -- src/fragments/lib/storage/flutter/remove.mdx | 40 +-- src/fragments/lib/storage/flutter/upload.mdx | 323 ++++++++++-------- .../native_common/configureaccess/common.mdx | 20 -- .../storage/configure-access/index.mdx | 2 - .../build-a-backend/storage/move/index.mdx | 26 -- 15 files changed, 323 insertions(+), 525 deletions(-) delete mode 100644 src/fragments/lib/storage/flutter/configureaccess/10_protected_upload.mdx delete mode 100644 src/fragments/lib/storage/flutter/configureaccess/20_protected_download.mdx delete mode 100644 src/fragments/lib/storage/flutter/configureaccess/30_private_upload.mdx delete mode 100644 src/fragments/lib/storage/flutter/configureaccess/40_private_download.mdx delete mode 100644 src/fragments/lib/storage/flutter/configureaccess/50_customization.mdx delete mode 100644 src/fragments/lib/storage/flutter/move.mdx delete mode 100644 src/pages/gen1/[platform]/build-a-backend/storage/move/index.mdx diff --git a/src/fragments/lib/storage/flutter/configureaccess/10_protected_upload.mdx b/src/fragments/lib/storage/flutter/configureaccess/10_protected_upload.mdx deleted file mode 100644 index 39f717d7168..00000000000 --- a/src/fragments/lib/storage/flutter/configureaccess/10_protected_upload.mdx +++ /dev/null @@ -1,25 +0,0 @@ -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future uploadProtectedFile({ - required String filePath, - required String key, -}) async { - final awsFile = AWSFile.fromPath(filePath); - const options = StorageUploadFileOptions( - accessLevel: StorageAccessLevel.protected, - ); - - try { - final uploadResult = await Amplify.Storage.uploadFile( - localFile: awsFile, - key: key, - options: options, - ).result; - safePrint('Uploaded file: ${uploadResult.uploadedItem.key}'); - } on StorageException catch (e) { - safePrint('Something went wrong uploading file: ${e.message}'); - rethrow; - } -} -``` \ No newline at end of file diff --git a/src/fragments/lib/storage/flutter/configureaccess/20_protected_download.mdx b/src/fragments/lib/storage/flutter/configureaccess/20_protected_download.mdx deleted file mode 100644 index c5c4a0106a3..00000000000 --- a/src/fragments/lib/storage/flutter/configureaccess/20_protected_download.mdx +++ /dev/null @@ -1,33 +0,0 @@ -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -Future downloadProtectedFile({ - required String key, - required String targetIdentityId, - required String downloadPath, -}) async { - final awsFile = AWSFile.fromPath(downloadPath); - final options = StorageDownloadFileOptions( - // specify that the file has a protected access level - accessLevel: StorageAccessLevel.protected, - // specify the identity ID of the user who uploaded this file - pluginOptions: S3DownloadFilePluginOptions.forIdentity( - targetIdentityId, - ), - ); - - try { - final result = await Amplify.Storage.downloadFile( - key: key, - localFile: awsFile, - options: options, - ).result; - - safePrint('Downloaded file is located at: ${result.localFile.path}'); - } on StorageException catch (e) { - safePrint('Something went wrong downloading the file: ${e.message}'); - rethrow; - } -} -``` \ No newline at end of file diff --git a/src/fragments/lib/storage/flutter/configureaccess/30_private_upload.mdx b/src/fragments/lib/storage/flutter/configureaccess/30_private_upload.mdx deleted file mode 100644 index d0d2370a31e..00000000000 --- a/src/fragments/lib/storage/flutter/configureaccess/30_private_upload.mdx +++ /dev/null @@ -1,26 +0,0 @@ -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future uploadPrivateFile({ - required String filePath, - required String key, -}) async { - final awsFile = AWSFile.fromPath(filePath); - const options = StorageUploadFileOptions( - accessLevel: StorageAccessLevel.private, - ); - - try { - final uploadResult = await Amplify.Storage.uploadFile( - localFile: awsFile, - key: key, - options: options, - ).result; - - safePrint('Uploaded file: ${uploadResult.uploadedItem.key}'); - } on StorageException catch (e) { - safePrint('Something went wrong uploading file: ${e.message}'); - rethrow; - } -} -``` diff --git a/src/fragments/lib/storage/flutter/configureaccess/40_private_download.mdx b/src/fragments/lib/storage/flutter/configureaccess/40_private_download.mdx deleted file mode 100644 index 1cbd1a7ca6d..00000000000 --- a/src/fragments/lib/storage/flutter/configureaccess/40_private_download.mdx +++ /dev/null @@ -1,26 +0,0 @@ -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future downloadPrivateFile({ - required String key, - required String downloadPath, -}) async { - final awsFile = AWSFile.fromPath(downloadPath); - const options = StorageDownloadFileOptions( - accessLevel: StorageAccessLevel.private, - ); - - try { - final result = await Amplify.Storage.downloadFile( - key: key, - localFile: awsFile, - options: options, - ).result; - - safePrint('Downloaded file is located at: ${result.localFile.path}'); - } on StorageException catch (e) { - safePrint('Something went wrong downloading the file: ${e.message}'); - rethrow; - } -} -``` diff --git a/src/fragments/lib/storage/flutter/configureaccess/50_customization.mdx b/src/fragments/lib/storage/flutter/configureaccess/50_customization.mdx deleted file mode 100644 index 5b597f9af9c..00000000000 --- a/src/fragments/lib/storage/flutter/configureaccess/50_customization.mdx +++ /dev/null @@ -1,104 +0,0 @@ -## Customization - -### Customize Object Key Path - -You can customize your key path by defining a prefix resolver: - -```dart -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -// Define your own prefix resolver, which implements the `S3PrefixResolver`. -class MyPrefixResolver implements S3PrefixResolver { - const MyPrefixResolver(); - - @override - Future resolvePrefix({ - required StorageAccessLevel accessLevel, - String? identityId, - }) async { - if (accessLevel == StorageAccessLevel.guest) { - return 'myPublicPrefix/'; - } - - final String accessLevelPrefix; - - if (accessLevel == StorageAccessLevel.protected) { - accessLevelPrefix = 'myProtectedPrefix/'; - } else { - accessLevelPrefix = 'myPrivatePrefix/'; - } - - final targetIdentityId = identityId ?? await getCurrentUserIdentityId(); - - return '$accessLevelPrefix$targetIdentityId/'; - } - - Future getCurrentUserIdentityId() async { - final authPlugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); - final authSession = await authPlugin.fetchAuthSession(); - return authSession.identityIdResult.value; - } -} - -``` - -Then configure the storage plugin with the resolver. - -```dart -final storagePlugin = AmplifyStorageS3( - prefixResolver: const MyPrefixResolver(), -); -... -await Amplify.addPlugin(storagePlugin); -``` - -Add the IAM policy that corresponds with the prefixes defined above to enable read, write and delete operation for all the objects under path *myPublicPrefix/*: -```json -{ - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:DeleteObject" - ], - "Resource": ["arn:aws:s3:::your-s3-bucket/myPublicPrefix/*"] - } - ] -} - -``` -If you want to have custom *private* path prefix like *`myPrivatePrefix/`*, you need to add it into your IAM policy: -```json -{ - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::your-s3-bucket/myPrivatePrefix/${cognito-identity.amazonaws.com:sub}/*" - ] - } - ] -} -``` -#### Passthrough PrefixResolver - -If you would like no prefix resolution logic, such as performing S3 requests at the root of the bucket, you can use the `PassThroughPrefixResolver` provided by the `amplify_storage_s3` package. - -```dart -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -final storagePlugin = AmplifyStorageS3( - prefixResolver: const PassThroughPrefixResolver(), -); - -await Amplify.addPlugin(storagePlugin); -``` diff --git a/src/fragments/lib/storage/flutter/copy.mdx b/src/fragments/lib/storage/flutter/copy.mdx index 22804ff46d7..d82444d1768 100644 --- a/src/fragments/lib/storage/flutter/copy.mdx +++ b/src/fragments/lib/storage/flutter/copy.mdx @@ -1,28 +1,15 @@ You can copy an existing file to a different location in your S3 bucket. User who initiates a copy operation should have read permission on the copy source file. ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future copyGuestFileToPrivate({ - required String sourceKey, - required String destinationKey, -}) async { +Future copy() async { try { final result = await Amplify.Storage.copy( - source: StorageItemWithAccessLevel( - storageItem: StorageItem(key: sourceKey), - accessLevel: StorageAccessLevel.guest, - ), - destination: StorageItemWithAccessLevel( - storageItem: StorageItem(key: destinationKey), - accessLevel: StorageAccessLevel.private, - ), + source: const StoragePath.fromString('public/file-1.txt'), + destination: const StoragePath.fromString('public/file-2.txt'), ).result; - - safePrint('Copied file: ${result.copiedItem.key}'); + safePrint('Copied file: ${result.copiedItem.path}'); } on StorageException catch (e) { - safePrint('Error copying file: ${e.message}'); - rethrow; + safePrint(e.message); } } -``` \ No newline at end of file +``` diff --git a/src/fragments/lib/storage/flutter/download.mdx b/src/fragments/lib/storage/flutter/download.mdx index b6350bbbb22..fdc80d8a6bb 100644 --- a/src/fragments/lib/storage/flutter/download.mdx +++ b/src/fragments/lib/storage/flutter/download.mdx @@ -11,20 +11,17 @@ You can use the [path_provider](https://pub.dev/packages/path_provider) package ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:path_provider/path_provider.dart'; -Future downloadToLocalFile(String key) async { +Future downloadFile() async { final documentsDir = await getApplicationDocumentsDirectory(); - final filepath = documentsDir.path + '/example.txt'; + final filepath = '${documentsDir.path}/example.txt'; try { final result = await Amplify.Storage.downloadFile( - key: key, + path: const StoragePath.fromString('public/example.txt'), localFile: AWSFile.fromPath(filepath), - onProgress: (progress) { - safePrint('Fraction completed: ${progress.fractionCompleted}'); - }, ).result; - safePrint('Downloaded file is located at: ${result.localFile.path}'); } on StorageException catch (e) { safePrint(e.message); @@ -39,14 +36,15 @@ Future downloadToLocalFile(String key) async { On Web, the download process will be handled by the browser. You can provide the downloaded file name by specifying the `path` parameter of `AWSFile.fromPath`. E.g. this instructs the browser to download the file `download.txt`. ```dart -Future downloadToLocalFileOnWeb(String key) async { +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future downloadFile() async { try { final result = await Amplify.Storage.downloadFile( - key: key, + path: const StoragePath.fromString('public/example.txt'), localFile: AWSFile.fromPath('download.txt'), ).result; - - safePrint('Downloaded file: ${result.downloadedItem.key}'); + safePrint('Downloaded file: ${result.downloadedItem.path}'); } on StorageException catch (e) { safePrint(e.message); } @@ -62,15 +60,11 @@ Future downloadToLocalFileOnWeb(String key) async { You can download a file to in-memory buffer with `Amplify.Storage.downloadData`: ```dart -Future downloadToMemory(String key) async { +Future download() async { try { final result = await Amplify.Storage.downloadData( - key: key, - onProgress: (progress) { - safePrint('Fraction completed: ${progress.fractionCompleted}'); - }, + path: const StoragePath.fromString('public/example.txt'), ).result; - safePrint('Downloaded data: ${result.bytes}'); } on StorageException catch (e) { safePrint(e.message); @@ -78,9 +72,101 @@ Future downloadToMemory(String key) async { } ``` +## Download Progress + +To track progress of the download, use the progress listener callback. + + + + + +```dart +final operation = Amplify.Storage.downloadFile( + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + path: const StoragePath.fromString('public/example.txt'), + onProgress: (progress) { + safePrint('fraction totalBytes: ${progress.totalBytes}'); + safePrint('fraction transferredBytes: ${progress.transferredBytes}'); + safePrint('fraction completed: ${progress.fractionCompleted}'); + } +); +``` + + + + + +```dart +final operation = Amplify.Storage.downloadData( + path: const StoragePath.fromString('public/example.txt'), + onProgress: (progress) { + safePrint('fraction totalBytes: ${progress.totalBytes}'); + safePrint('fraction transferredBytes: ${progress.transferredBytes}'); + safePrint('fraction completed: ${progress.fractionCompleted}'); + }, +); +``` + + + + + +## Control of Download Operations + +A call to `Amplify.Storage.downloadFile` or `Amplify.Storage.downloadData` returns a reference to the operation that is performing the upload. + +To cancel the upload (for example, in response to the user pressing a Cancel button), simply call `.cancel()` on the returned upload operation. Download operations also allows for the operation to be paused and resumed. + + + + + +```dart +Future download() async { + final operation = Amplify.Storage.downloadFile( + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + path: const StoragePath.fromString('public/example.txt'), + ); + + // pause operation + await operation.pause(); + + // resume operation + await operation.resume(); + + // cancel operation + await operation.cancel(); +} +``` + + + + + +```dart +Future download() async { + final operation = Amplify.Storage.downloadData( + path: const StoragePath.fromString('public/example.txt'), + ); + + // pause operation + await operation.pause(); + + // resume operation + await operation.resume(); + + // cancel operation + await operation.cancel(); +} +``` + + + + + ## Generate a download URL -You can get a downloadable URL for the file in storage by its key and access level. +You can get a downloadable URL for the file in storage by its path. When creating a downloadable URL, you can choose to check if the file exists by setting `validateObjectExistence` to `true` in `S3GetUrlPluginOptions`. If the file is inaccessible or does not exist, a `StorageException` is thrown. @@ -88,25 +174,20 @@ This allows you to check if an object exists during generating the presigned URL that object. ```dart -Future getDownloadUrl({ - required String key, - required StorageAccessLevel accessLevel, -}) async { +Future getDownloadUrl() async { try { final result = await Amplify.Storage.getUrl( - key: key, + path: const StoragePath.fromString('public/example.txt'), options: const StorageGetUrlOptions( - accessLevel: accessLevel, pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, expiresIn: Duration(days: 1), ), ), ).result; - return result.url.toString(); + safePrint('url: ${result.url}'); } on StorageException catch (e) { - safePrint('Could not get a downloadable URL: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` diff --git a/src/fragments/lib/storage/flutter/get-properties.mdx b/src/fragments/lib/storage/flutter/get-properties.mdx index a2d601a3778..97169de98cc 100644 --- a/src/fragments/lib/storage/flutter/get-properties.mdx +++ b/src/fragments/lib/storage/flutter/get-properties.mdx @@ -1,18 +1,14 @@ You can get file properties and metadata without downloading the file using `Amplify.Storage.getProperties`. ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - Future getFileProperties() async { try { final result = await Amplify.Storage.getProperties( - key: 'example.txt', + path: const StoragePath.fromString('example.txt'), ).result; - safePrint('File size: ${result.storageItem.size}'); } on StorageException catch (e) { - safePrint('Could not retrieve properties: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` diff --git a/src/fragments/lib/storage/flutter/list.mdx b/src/fragments/lib/storage/flutter/list.mdx index 0b176d699d6..0d361e77f4a 100644 --- a/src/fragments/lib/storage/flutter/list.mdx +++ b/src/fragments/lib/storage/flutter/list.mdx @@ -4,23 +4,22 @@ This will list all files located under path `album` that: * have `private` access level * are in the root of `album/` (the result doesn't include files under any sub path) -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; +`excludeSubPaths` can be used to exclude nested paths. `/` is used by as the delimiter for nested paths. This can be customized with the `delimiter` option. +```dart Future listAlbum() async { try { String? nextToken; bool hasNextPage; do { final result = await Amplify.Storage.list( - path: 'album/', + path: const StoragePath.fromString('public/album/'), options: StorageListOptions( - accessLevel: StorageAccessLevel.private, pageSize: 50, nextToken: nextToken, pluginOptions: const S3ListPluginOptions( excludeSubPaths: true, + delimiter: '/', ), ), ).result; @@ -29,8 +28,7 @@ Future listAlbum() async { hasNextPage = result.hasNextPage; } while (hasNextPage); } on StorageException catch (e) { - safePrint('Error listing files: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` @@ -42,22 +40,18 @@ You can also list all files under a given path without pagination by using the ` This will list all public files (i.e. those with `guest` access level): ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -Future listAllWithGuestAccessLevel() async { +Future listAll() async { try { final result = await Amplify.Storage.list( + path: const StoragePath.fromString('public/'), options: const StorageListOptions( - accessLevel: StorageAccessLevel.guest, pluginOptions: S3ListPluginOptions.listAll(), ), ).result; - safePrint('Listed items: ${result.items}'); } on StorageException catch (e) { - safePrint('Error listing files: ${e.message}'); - rethrow; + safePrint(e.message); } } -``` \ No newline at end of file +``` +import { delimiter } from "path" diff --git a/src/fragments/lib/storage/flutter/move.mdx b/src/fragments/lib/storage/flutter/move.mdx deleted file mode 100644 index 030793cf718..00000000000 --- a/src/fragments/lib/storage/flutter/move.mdx +++ /dev/null @@ -1,29 +0,0 @@ -You can move an existing file to a different folder location in your S3 bucket. The user initiating the move operation must have read and write permission on both the source file and destination file. - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future movePrivateFile({ - required String sourceKey, - required String destinationKey, -}) async { - try { - final result = await Amplify.Storage.move( - source: StorageItemWithAccessLevel( - storageItem: StorageItem(key: sourceKey), - accessLevel: StorageAccessLevel.private, - ), - destination: StorageItemWithAccessLevel( - storageItem: StorageItem(key: destinationKey), - accessLevel: StorageAccessLevel.private, - ), - ).result; - - safePrint('Moved file to: ${result.movedItem.key}'); - } on StorageException catch (e) { - safePrint('Something went wrong moving the file: ${e.message}'); - rethrow; - } -} - -``` \ No newline at end of file diff --git a/src/fragments/lib/storage/flutter/remove.mdx b/src/fragments/lib/storage/flutter/remove.mdx index 48587ba1ed6..d742f7e4f31 100644 --- a/src/fragments/lib/storage/flutter/remove.mdx +++ b/src/fragments/lib/storage/flutter/remove.mdx @@ -1,50 +1,36 @@ ## Remove a file -You can remove a single file using `Amplify.Storage.remove` with the `key` and its associated access level: +You can remove a single file using `Amplify.Storage.remove`. ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future removeFile({ - required String key, - required StorageAccessLevel accessLevel, -}) async { +Future removeFile() async { try { final result = await Amplify.Storage.remove( - key: key, - options: StorageRemoveOptions( - accessLevel: accessLevel, - ), + path: const StoragePath.fromString('public/file.txt'), ).result; - safePrint('Removed file: ${result.removedItem.key}'); + safePrint('Removed file: ${result.removedItem.path}'); } on StorageException catch (e) { - safePrint('Error deleting file: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` ## Remove multiple files -You can remove multiple files using `Amplify.Storage.removeMany` with the `keys`, the files to be removed in a batch should have the same access level: +You can remove multiple files using `Amplify.Storage.removeMany`. ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future removePrivateFiles({ - required List keys, -}) async { +Future remove() async { try { final result = await Amplify.Storage.removeMany( - keys: keys, - options: const StorageRemoveManyOptions( - accessLevel: StorageAccessLevel.private, - ), + paths: [ + const StoragePath.fromString('public/file-1.txt'), + const StoragePath.fromString('public/file-2.txt'), + ], ).result; safePrint('Removed files: ${result.removedItems}'); } on StorageException catch (e) { - safePrint('Error deleting files: ${e.message}'); - rethrow; + safePrint(e.message); } } -``` \ No newline at end of file +``` diff --git a/src/fragments/lib/storage/flutter/upload.mdx b/src/fragments/lib/storage/flutter/upload.mdx index 533b7f458b6..91bf001e46d 100644 --- a/src/fragments/lib/storage/flutter/upload.mdx +++ b/src/fragments/lib/storage/flutter/upload.mdx @@ -1,6 +1,6 @@ ## Upload File -To upload to S3 from a file, specify the `key` and the `localFile` to be uploaded, where the `localFile` can be an instance of `AWSFile` created from either an OS platform `File` instance or the result of Flutter file picker plugins such as [file_picker](https://pub.dev/packages/file_picker). +To upload to S3 from a file, specify the `path` to upload the file to and the `localFile` to be uploaded. `localFile` can be an instance of `AWSFile` created from either an OS platform `File` instance or the result of Flutter file picker plugins such as [file_picker](https://pub.dev/packages/file_picker). ### Upload platform `File` @@ -13,50 +13,67 @@ by running: `flutter pub add aws_common` - + ```dart -import 'dart:io' as io; +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future uploadFile() async { + try { + final result = await Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + path: const StoragePath.fromString('public/file.txt'), + ).result; + safePrint('Uploaded file: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + + -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; +```dart +import 'dart:io' show File; + +import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:aws_common/vm.dart'; -Future uploadIOFile(io.File file) async { - final awsFile = AWSFilePlatform.fromFile(file); +Future uploadFile(File file) async { try { - final uploadResult = await Amplify.Storage.uploadFile( - localFile: awsFile, - key: 'upload/file.png', + final result = await Amplify.Storage.uploadFile( + localFile: AWSFilePlatform.fromFile(file), + path: const StoragePath.fromString('public/file.png'), ).result; - safePrint('Uploaded file: ${uploadResult.uploadedItem.key}'); + safePrint('Uploaded file: ${result.uploadedItem.path}'); } on StorageException catch (e) { - safePrint('Error uploading file: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` - + ```dart -import 'dart:html' as html; +import 'dart:html' show File; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:aws_common/web.dart'; -Future uploadHtmlFile(html.File file) async { +Future uploadFile(File file) async { final awsFile = AWSFilePlatform.fromFile(file); try { - final uploadResult = await Amplify.Storage.uploadFile( + final result = await Amplify.Storage.uploadFile( localFile: awsFile, - key: 'upload/file.png', + path: const StoragePath.fromString('public/file.png'), ).result; - safePrint('Uploaded file: ${uploadResult.uploadedItem.key}'); + safePrint('Uploaded file: ${result.uploadedItem.path}'); } on StorageException catch (e) { - safePrint('Error uploading file: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` @@ -88,7 +105,7 @@ Future uploadImage() async { return; } - // Upload file with its filename as the key + // Upload file using the filename final platformFile = result.files.single; try { final result = await Amplify.Storage.uploadFile( @@ -96,48 +113,39 @@ Future uploadImage() async { platformFile.readStream!, size: platformFile.size, ), - key: platformFile.name, + path: StoragePath.fromString('public/${platformFile.name}'), onProgress: (progress) { safePrint('Fraction completed: ${progress.fractionCompleted}'); }, ).result; - safePrint('Successfully uploaded file: ${result.uploadedItem.key}'); + safePrint('Successfully uploaded file: ${result.uploadedItem.path}'); } on StorageException catch (e) { - safePrint('Error uploading file: $e'); - rethrow; + safePrint(e.message); } } ``` ## Upload Data -To upload to S3 from a data object, specify the `key` and `data`, where `data` is an instance of `S3DataPayload` created from various data formats. +To upload to S3 from a data object, specify the `path` to upload the file to and the `data` to upload. `data` should be an instance of `StorageDataPayload` created from various data formats. ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -Future uploadStringData({ - required String dataString, - required String key, -}) async { +Future uploadData() async { try { final result = await Amplify.Storage.uploadData( - data: S3DataPayload.string( - dataString, + data: StorageDataPayload.string( + 'hello world', contentType: 'text/plain', ), - key: key, + path: const StoragePath.fromString('public/example.txt'), ).result; - - safePrint('Uploaded data: ${result.uploadedItem.key}'); + safePrint('Uploaded data: ${result.uploadedItem.path}'); } on StorageException catch (e) { - safePrint('Error uploading data: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` @@ -147,23 +155,21 @@ Future uploadStringData({ ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -Future uploadJsonObject({ - required Map json, - required String key, -}) async { +Future uploadData() async { try { final result = await Amplify.Storage.uploadData( - data: S3DataPayload.json(json), - key: key, + data: StorageDataPayload.json({ + 'title': 'example', + 'author': { + 'firstName': 'Jane', + 'lastName': 'Doe', + }, + }), + path: const StoragePath.fromString('public/example.json'), ).result; - - safePrint('Uploaded data: ${result.uploadedItem.key}'); + safePrint('Uploaded data: ${result.uploadedItem.path}'); } on StorageException catch (e) { - safePrint('Error uploading data: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` @@ -172,26 +178,19 @@ Future uploadJsonObject({ -`S3DataPayload.dataUrl()` parses the provided data URL string, and throws exception if the data URL is invalid. See more info about [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs). +`StorageDataPayload.dataUrl()` parses the provided data URL string, and throws exception if the data URL is invalid. See more info about [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs). ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -Future uploadDataUrl({ - required String dataUrl, - required String key, -}) async { +Future uploadData() async { + const dataUrl = 'data:text/plain;charset=utf-8;base64,aGVsbG8gd29ybGQ='; try { final result = await Amplify.Storage.uploadData( - data: S3DataPayload.dataUrl(dataUrl), - key: key, + data: StorageDataPayload.dataUrl(dataUrl), + path: const StoragePath.fromString('public/example.txt'), ).result; - - safePrint('Uploaded data: ${result.uploadedItem.key}'); + safePrint('Uploaded data: ${result.uploadedItem.path}'); } on StorageException catch (e) { - safePrint('Error uploading data: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` @@ -201,27 +200,19 @@ Future uploadDataUrl({ ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -Future uploadBytes({ - required List bytes, - required String key, - required String contentType, -}) async { +Future uploadBytes() async { try { + final bytes = 'hello world'.codeUnits; final result = await Amplify.Storage.uploadData( - data: S3DataPayload.bytes( + data: StorageDataPayload.bytes( bytes, - contentType: contentType, + contentType: 'text/plain', ), - key: key, + path: const StoragePath.fromString('public/example.txt'), ).result; - - safePrint('Uploaded data: ${result.uploadedItem.key}'); + safePrint('Uploaded data: ${result.uploadedItem.path}'); } on StorageException catch (e) { - safePrint('Error uploading data: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` @@ -230,53 +221,90 @@ Future uploadBytes({ +## Upload Progress + +To track progress of the upload, use the progress listener callback. + + + + + +```dart +final operation = Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + path: const StoragePath.fromString('public/example.txt'), + onProgress: (progress) { + safePrint('fraction totalBytes: ${progress.totalBytes}'); + safePrint('fraction transferredBytes: ${progress.transferredBytes}'); + safePrint('fraction completed: ${progress.fractionCompleted}'); + } +); +``` + + + + + +```dart +final operation = Amplify.Storage.uploadData( + data: StorageDataPayload.string('hello world'), + path: const StoragePath.fromString('public/example.txt'), + onProgress: (progress) { + safePrint('fraction totalBytes: ${progress.totalBytes}'); + safePrint('fraction transferredBytes: ${progress.transferredBytes}'); + safePrint('fraction completed: ${progress.fractionCompleted}'); + }, +); +``` + + + + + ## Upload Options You can attach metadata while uploading data or a file by specifying the `metadata` property in options. If you want the `metadata` to be included in the upload result, you can set the `getProperties` flag to `true` in options. + + + + ```dart -Future uploadWithOptions() async { - // When uploading data, use `StorageUploadDataOptions` - final uploadDataOperation = Amplify.Storage.uploadData( - data: S3DataPayload.string( - 'example', - contentType: 'text/plain', - ), - key: 'example.txt', - options: const StorageUploadDataOptions( - metadata: { - 'project': 'ExampleProject', - }, - pluginOptions: S3UploadDataPluginOptions( - getProperties: true, - ), +final result = await Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + path: const StoragePath.fromString('public/example.txt'), + options: const StorageUploadFileOptions( + metadata: {'key': 'value'}, + pluginOptions: S3UploadFilePluginOptions( + getProperties: true, ), - ); - final uploadDataResult = await uploadDataOperation.result; - safePrint( - 'Uploaded data with metadata: ${uploadDataResult.uploadedItem.metadata}', - ); + ), +).result +safePrint('metadata: ${result.uploadedItem.metadata}'); +``` - // When uploading a file, use `StorageUploadFileOptions` - final uploadFileOperation = Amplify.Storage.uploadFile( - localFile: AWSFile.fromPath('path/to/example.txt'), - key: 'example.txt', - options: const StorageUploadFileOptions( - metadata: { - 'project': 'ExampleProject', - }, - pluginOptions: S3UploadFilePluginOptions( - getProperties: true, - ), + + + + +```dart +final result = await Amplify.Storage.uploadData( + data: StorageDataPayload.string('example'), + path: const StoragePath.fromString('public/example.txt'), + options: const StorageUploadDataOptions( + metadata: {'key': 'value'}, + pluginOptions: S3UploadDataPluginOptions( + getProperties: true, ), - ); - final uploadFileResult = await uploadFileOperation.result; - safePrint( - 'Uploaded file with metadata: ${uploadFileResult.uploadedItem.metadata}', - ); -} + ), +).result; +safePrint('metadata: ${result.uploadedItem.metadata}'); ``` + + + + The [`Amplify.Storage.getProperties` API](/gen1/[platform]/build-a-backend/storage/get-properties/) allows you to retrieve metadata without downloading the file. @@ -291,33 +319,50 @@ A call to `Amplify.Storage.uploadFile` or `Amplify.Storage.uploadData` returns a To cancel the upload (for example, in response to the user pressing a Cancel button), simply call `.cancel()` on the returned upload operation. +`Amplify.Storage.uploadFile` also allows for the operation to be paused and resumed. + + + + + ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; +Future upload() async { + final operation = Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + path: const StoragePath.fromString('public/example.txt'), + ); -S3UploadFileOperation? uploadOperation; + // pause operation + await operation.pause(); -Future uploadFile(String path) async { - try { - final storagePlugin = Amplify.Storage.getPlugin(AmplifyStorageS3.pluginKey); - uploadOperation = storagePlugin.uploadFile( - localFile: AWSFile.fromPath(path), - key: 'example_file.txt', - ); - - final result = await uploadOperation!.result; - safePrint('Uploaded ${result.uploadedItem.key}'); - } on StorageException catch (e) { - safePrint('Error uploading file: ${e.message}'); - } + // resume operation + await operation.resume(); + + // cancel operation + await operation.cancel(); } +``` + + -void cancelUpload() { - uploadOperation?.cancel(); - uploadOperation = null; + + +```dart +Future upload() async { + final operation = Amplify.Storage.uploadData( + data: StorageDataPayload.string('example'), + path: const StoragePath.fromString('public/example.txt'), + ); + + // cancel operation + await operation.cancel(); } ``` + + + + ## Multipart Upload Amplify will automatically perform a S3 multipart upload for files larger than 5MB. For more information about S3's multipart upload support, see [Uploading and copying objects using multipart upload](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html). diff --git a/src/fragments/lib/storage/native_common/configureaccess/common.mdx b/src/fragments/lib/storage/native_common/configureaccess/common.mdx index ae97d196503..668359ed34f 100644 --- a/src/fragments/lib/storage/native_common/configureaccess/common.mdx +++ b/src/fragments/lib/storage/native_common/configureaccess/common.mdx @@ -36,10 +36,6 @@ import android1 from '/src/fragments/lib/storage/android/configureaccess/10_prot -import flutter2 from '/src/fragments/lib/storage/flutter/configureaccess/10_protected_upload.mdx'; - - - This will upload with the prefix `/protected/[IDENTITY_ID]/` followed by the `key`. For other users to read the file, you must specify the access level as `protected` and the identity ID of the user who uploaded it in the options. @@ -52,10 +48,6 @@ import android4 from '/src/fragments/lib/storage/android/configureaccess/20_prot -import flutter5 from '/src/fragments/lib/storage/flutter/configureaccess/20_protected_download.mdx'; - - - ## Private Access Create an options object specifying the private access level to only allow an object to be accessed by the uploading user @@ -68,10 +60,6 @@ import android7 from '/src/fragments/lib/storage/android/configureaccess/30_priv -import flutter8 from '/src/fragments/lib/storage/flutter/configureaccess/30_private_upload.mdx'; - - - This will upload with the prefix `/private/[IDENTITY_ID]/`, followed by the `key`. For the uploading user to read the file, specify the same access level (`private`) and key you used to upload: @@ -84,10 +72,6 @@ import android10 from '/src/fragments/lib/storage/android/configureaccess/40_pri -import flutter11 from '/src/fragments/lib/storage/flutter/configureaccess/40_private_download.mdx'; - - - import ios10 from '/src/fragments/lib/storage/ios/configureaccess/50_customization.mdx'; @@ -95,7 +79,3 @@ import ios10 from '/src/fragments/lib/storage/ios/configureaccess/50_customizati import android11 from '/src/fragments/lib/storage/android/configureaccess/50_customization.mdx'; - -import flutter12 from '/src/fragments/lib/storage/flutter/configureaccess/50_customization.mdx'; - - diff --git a/src/pages/gen1/[platform]/build-a-backend/storage/configure-access/index.mdx b/src/pages/gen1/[platform]/build-a-backend/storage/configure-access/index.mdx index 92d95900ab0..83003dfbdd4 100644 --- a/src/pages/gen1/[platform]/build-a-backend/storage/configure-access/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/storage/configure-access/index.mdx @@ -5,7 +5,6 @@ export const meta = { description: 'Learn about configuring different access levels in Amplify Storage. Objects can be public, protected, or private.', platforms: [ 'javascript', - 'flutter', 'swift', 'android', 'react-native', @@ -34,7 +33,6 @@ import common_configureaccess from '/src/fragments/lib/storage/native_common/con diff --git a/src/pages/gen1/[platform]/build-a-backend/storage/move/index.mdx b/src/pages/gen1/[platform]/build-a-backend/storage/move/index.mdx deleted file mode 100644 index 3988e173a4c..00000000000 --- a/src/pages/gen1/[platform]/build-a-backend/storage/move/index.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Move files', - description: "Learn more about how to move files using Amplify's storage category.", - platforms: ['flutter'] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -import flutter0 from '/src/fragments/lib/storage/flutter/move.mdx'; - - From 6e2ef0820d637d187755527a88a7c5beae09efc1 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Fri, 26 Apr 2024 17:07:32 -0400 Subject: [PATCH 11/88] chore: update flutter v2 migration guide (#7327) --- .../flutter/upgrade-guide/upgrade-guide.mdx | 389 ++++++------------ 1 file changed, 117 insertions(+), 272 deletions(-) diff --git a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx index b10fc254546..95f288679d5 100644 --- a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx +++ b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx @@ -1,328 +1,173 @@ -With the release of v1 Amplify Flutter now supports Web and Desktop for Auth, API, Analytics, and Storage use cases. Developers can build cross-platform Flutter apps with Amplify that target iOS, Android, Web, and Desktop (macOS, Windows, Linux) using a single codebase. +Amplify Flutter v2 has several changes that may require migration when upgrading from v1. -We have re-written our libraries in Dart. In some places, we have made breaking changes to improve ergonomics or enable features that had been missing from the v0 implementations. +## Auth -## Prerequisites +#### Time based OTP Exception Handling -- A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated +In Amplify Flutter v1, `Amplify.Auth.verifyTotpSetup()` throws an `EnableSoftwareTokenMfaException` if the provided code was incorrect. In Amplify Flutter v2 a `CodeMismatchException` is thrown. -Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/project-setup/platform-setup/) guide for more details on platform-specific setup. +#### Auth Flow in Amplify Config -## Platform Setup +In Amplify Flutter v1, the value of `“authenticationFlowType”` from the amplify configuration object is used as the default `AuthenticationFlowType` if none is provided when calling `Amplify.Auth.signIn()`. In Amplify Flutter v2 `AuthenticationFlowType.userSrpAuth` is the default if none is provided. To change the authentication flow type, provide a value in the sign in options. -The requirements for individual platforms have been modified in some cases. If you haven't done so already, go through the [project setup](/gen1/[platform]/start/project-setup/platform-setup/) guide -to make sure each platform has been configured correctly. +```dart +await Amplify.Auth.signIn( + username: 'username', + password: 'password', + options: const SignInOptions( + pluginOptions: CognitoSignInPluginOptions( + authFlowType: AuthenticationFlowType.userSrpAuth, + ), + ), +) +``` -## Auth -- `fetchAuthSession` has several changes. See the example below for how to migrate. - - `Amplify.Auth.getPlugin()` has been added to return plugin-specific subtypes. Casting to `CognitoAuthSession` is no longer needed when using this method. - - The `getAWSCredentials` flag was previously needed to retrieve AWS credentials. This flag has been deprecated and is no longer needed. AWS credentials will always be included in the response. - - `fetchAuthSession` will no longer throw exceptions. Instead, individual getters of type `AuthResult` will throw if the underlying operation fails. - - _Why?_ The previous behavior did not allow for partial successes in offline scenarios when user pool tokens were still valid, but AWS credentials were expired. See [amplify-flutter#760](https://github.com/aws-amplify/amplify-flutter/issues/760#issuecomment-1453559459) for more details. - - `CognitoAuthSession.credentials`, `CognitoAuthSession.userPoolTokens`, `CognitoAuthSession.userSub`, and `CognitoAuthSession.identityId` are deprecated and will be removed in a future release. Use `CognitoAuthSession.credentialsResult`, `CognitoAuthSession.userPoolTokensResult`, `CognitoAuthSession.userSubResult`, and `CognitoAuthSession.identityIdResult` instead. +## Analytics -```dart -// v0 -try { - final session = await Amplify.Auth.fetchAuthSession( - options: CognitoFetchAuthSessionOptions(getAWSCredentials: true), - ) as CognitoAuthSession; - - final accessToken = session.userPoolTokens?.accessToken; - safePrint('accessToken: $accessToken'); - - final identityId = session.identityId; - safePrint('identityId: $identityId'); -} on AuthException catch (e) { - // fetchAuthSession will fail if any of the underlying operations fail. - safePrint('The operation failed: ${e.message}'); -} -``` +#### Configuration of `autoFlushEventsInterval` -```dart -// v1 -final session = await Amplify.Auth.getPlugin( - AmplifyAuthCognito.pluginKey, -).fetchAuthSession(); - -// AuthResult.value will throw if the underlying operation failed. -try { - final accessToken = session.userPoolTokensResult.value.accessToken.toJson(); - safePrint('accessToken: $accessToken'); -} on SignedOutException { - // the user is not signed in. -} on SessionExpiredException { - // the users session has expired. -} on NetworkException { - // the access and/or id token is expired but cannot be refreshed because the - // user is offline. -} - -// `AuthResult.valueOrNull` will return the identityId if present or null, -// but will never throw even if the underlying operation failed. -final identityId = session.identityIdResult.valueOrNull; -safePrint('identityId: $identityId'); -``` +In Amplify Flutter v1, the autoFlushEventsInterval value was configured manually in the amplifyconfiguration.dart file. In Amplify Flutter v2, the autoFlushEventsInterval value is not read from amplifyconfiguration.dart but instead is passed directly to the `AmplifyAnalyticsPinpoint()` constructor under the `AnalyticsPinpointPluginOptions`. The default value for autoFlushEventsInterval remains at 30 seconds, consistent with the behavior in Amplify Flutter v1. -- The next step returned from auth operations are now enums. See the example below for how to migrate for `Auth.signUp()`. +## Storage -```dart -// v0 -final result = await Amplify.Auth.signUp(/* ... */); -switch (result.nextStep?.signUpStep) { - case 'CONFIRM_SIGN_UP': - // prompt user to confirm sign in using nextStep.codeDeliveryDetails - break; - case 'DONE': - // sign up is complete, user can be taken to an authenticated state. - break; -} -``` +#### `key` and `StorageAccessLevel` have been replaced by `StoragePath` -```dart -// v1 -final result = await Amplify.Auth.signUp(/* ... */); -switch (result.nextStep.signUpStep) { - case AuthSignUpStep.confirmSignUp: - // prompt user to confirm sign in using nextStep.codeDeliveryDetails - break; - case AuthSignUpStep.done: - // sign up is complete, user can be taken to an authenticated state. - break; -} -``` +In Amplify Flutter v1, files operations were performed based on a key and an AccessLevel. Amplify translated these values to a full file path. The AccessLevel (guest, protected, or private) determined what prefix to use (`“public/”`, `“protected//”`, or `“private//”`). Amplify Flutter v1 forms the full path using the prefix and the provided key. + +Amplify Flutter v2 allows for full control of the path of the storage object. Storage paths can be constructed either from a static string or from the current users identity id using the StoragePath class. + +To migrate from v1 to v2, replace all uses of `key` with `path` and remove uses of `StorageAccessLevel`. The path should include the prefix that was previously added by Amplify automatically. -- Auth hub events now have a specific subtype (`AuthHubEvent`) and contain a `type` that is an enum. See the example below for how to migrate. +##### Example for Public Files ```dart -// v0 -final hubSubscription = Amplify.Hub.listen([HubChannel.Auth], (hubEvent) { - switch(hubEvent.eventName) { - case 'SIGNED_IN': - safePrint('User is signed in.'); - break; - case 'SIGNED_OUT': - safePrint('User is signed out.'); - break; - case 'SESSION_EXPIRED': - safePrint('The session has expired.'); - break; - case 'USER_DELETED': - safePrint('The user has been deleted.'); - break; - } -}); +// upload file to "public/file.txt" in Amplify Flutter v1 +Amplify.Storage.uploadFile( + localFile: file, + key: 'file.txt', + options: StorageUploadFileOptions( + accessLevel: StorageAccessLevel.guest, + ), +); ``` ```dart -// v1 -final subscription = Amplify.Hub.listen(HubChannel.Auth, (AuthHubEvent event) { - switch (event.type) { - case AuthHubEventType.signedIn: - safePrint('User is signed in.'); - break; - case AuthHubEventType.signedOut: - safePrint('User is signed out.'); - break; - case AuthHubEventType.sessionExpired: - safePrint('The session has expired.'); - break; - case AuthHubEventType.userDeleted: - safePrint('The user has been deleted.'); - break; - } -}); +// upload file to "public/file.txt" in Amplify Flutter v2 +Amplify.Storage.uploadFile( + localFile: file, + path: const StoragePath.fromString("public/file.txt"), +); ``` -- Cognito-specific API options such as `CognitoSignInOptions` are deprecated in favor of `pluginOptions`. The classes will be removed in a future release. See below for an example of how to migrate for `Auth.signIn()`. +##### Example for Protected Files ```dart -// v0 -final result = await Amplify.Auth.signIn( - username: username, - password: password, - options: const CognitoSignInOptions( - clientMetadata: {'data': 'value'}, +// upload file to "protected//file.txt" in Amplify Flutter v1 +Amplify.Storage.uploadFile( + localFile: file, + key: 'file.txt', + options: StorageUploadFileOptions( + accessLevel: StorageAccessLevel.protected, ), ); ``` ```dart -// v1 -final result = await Amplify.Auth.signIn( - username: username, - password: password, - options: const SignInOptions( - pluginOptions: CognitoSignInPluginOptions( - clientMetadata: {'data': 'value'}, - ), - ), +// upload file to "protected//file.txt" in Amplify Flutter v2 +Amplify.Storage.uploadFile( + localFile: file, + path: StoragePath.fromIdentityId( + (identityId) => "protected/$identityId/file.txt", + ), ); ``` -### Social Sign-In (Hosted UI) - -Configuration for social sign-in (Hosted UI) varies slightly in v1 compared to v0. - - - - - -For Android, v0 required the following changes be made to your `AndroidManifest.xml`. - -```xml - - - - - - - ... - - - - - - - - - ... - -``` - -Start by removing this code, then update the `AndroidManifest.xml` as follows, replacing `myapp` with your custom URI scheme you configured in your backend. +##### Example for Private Files -```diff -+ -+ -+ -+ -+ - - ... - -+ -+ -+ -+ -+ -+ - -- -- -- -- -- -- -- -- - ... - +```dart +// upload file to "private//file.txt" in Amplify Flutter v1 +Amplify.Storage.uploadFile( + localFile: file, + key: 'file.txt', + options: StorageUploadFileOptions( + accessLevel: StorageAccessLevel.private, + ), +); ``` - +```dart +// upload file to "private//file.txt" in Amplify Flutter v2 +Amplify.Storage.uploadFile( + localFile: file, + path: StoragePath.fromIdentityId( + (identityId) => "private/$identityId/file.txt", + ), +); +``` - +#### Prefix Resolver has been removed +With the introduction of StoragePath, there is no need to override the libraries behavior for the prefix. -For iOS, v0 required the following changes be made to your `Info.plist`. +#### Delimiter has been moved to the List API Options -```xml - +In Amplify Flutter v1 `AmplifyStorageS3` accepted a `delimiter` option. This was used in list operations to determine sub paths. This option is now available as part of the List API options. - - +```dart +final result = Amplify.Storage.list( + path: const StoragePath.fromString('public/album/'), + options: const StorageListOptions( + pageSize: 50, + pluginOptions: S3ListPluginOptions( + excludeSubPaths: true, + delimiter: '/' + ), + ), +); +``` - CFBundleURLTypes - - - CFBundleURLSchemes - - myapp - - - +## DataStore - - -``` +#### Configuration -In v1, these changes are no longer required and can be safely removed without any effect on social sign-in (Hosted UI). +In Amplify Flutter v1, the `AmplifyDataStore()` constructor allows for the customization of the plugin through optional parameters such as errorHandler, conflictHandler, syncExpression, syncInterval, syncMaxRecords, syncPageSize, and authModeStrategy. In Amplify Flutter v2, these parameters have been relocated under DataStorePluginOptions, with default values that mirror those of Amplify Flutter v1. -```diff -- CFBundleURLTypes -- -- -- CFBundleURLSchemes -- -- myapp -- -- -- -``` +## Models & Query Predicates (GraphQL API & DataStore) - +#### Configuration - +In Amplify Flutter v1, the `AmplifyAPI()` constructor accepts optional parameters such as authProviders, baseHttpClient, modelProvider, and subscriptionOptions. In Amplify Flutter v2, these parameters have been relocated to `APIPluginOptions`. -Web and Desktop platforms were not supported in v0. +#### Generated Models -Follow the instructions [here](/gen1/[platform]/build-a-backend/auth/add-social-provider/#platform-setup) to configure Web and Desktop platforms for social sign-in (Hosted UI) in v1. +##### Model.fromJson() - +Amplify Flutter v2 now decodes AppSync GraphQL responses in their original shape (#816). In doing so, we’ve removed the previous transformations that happened after receiving an AppSync response. This requires data Models to be regenerated with the latest Amplify CLI. After upgrading to Amplify Flutter v2 please run the following: - +```bash +$ amplify upgrade +# from the root of your Flutter project +$ amplify codegen models +``` -## Analytics +##### Model.getId() -- `AnalyticsProperties` renamed to `CustomProperties` -- `AnalyticsUserProfile` renamed to `UserProfile` -- `AnalyticsUserProfileLocation` renamed to `UserProfileLocation` -- In v0, the `autoFlushEventsInterval` in `amplifyconfiguration.dart` was read as seconds on iOS and milliseconds on Android. v1 fixes this behavior by reading `autoFlushEventsInterval` as seconds across all platforms. -- In v1, auto-flushing of events occurs at 30 second intervals. In v0, the default auto flush iOS was 60 seconds and Android was 30 seconds. -- Automatic session reporting behavior has been aligned in v1. For all platforms, backgrounding and foregrounding the app will stop and start a new session. Previously on iOS, the session would pause and then resume. -- You can now save to the `UserAttributes` field of a Pinpoint Endpoint by passing an `AWSPinpointUserProfile` to `identifyUser`. -- Analytics cached event data is now stored differently. Existing cached analytics events will not be migrated. -- There are now typed `AnalyticsException`s for specific exception cases. - -## API - -- REST API methods have breaking changes. See [REST API docs](/gen1/[platform]/build-a-backend/restapi/set-up-rest-api/) for more details. -- `RestException` has been replaced with `HttpStatusException`. -- GraphQL model helpers have a few changes: - - Updated codegen models are required. Please upgrade to the latest version of the Amplify CLI and run `amplify codegen models`. - - `ModelQueries.get()` and `ModelMutations.deleteById()` have a breaking change where the ID is no longer a `String` but a `ModelIdentifier` that supports custom primary keys. See [GraphQL docs](/gen1/[platform]/build-a-backend/graphqlapi/query-data/) for examples. - - `ModelSubscriptions` helpers now take a `where` clause so users can get server-side subscription filters without a custom request. -- GraphQL subscriptions will attempt to automatically reconnect when the user's device loses and recovers internet access. Updates in connectivity status are available through new hub events on the channel `HubChannel.Api`. -- Server-side GraphQL errors on iOS no longer throw an exception, but instead return a `GraphQLResponse` with the errors in the `.errors` field (as has always been the case in Android). This applies to all platforms but is only a change for iOS. Note this is only true for errors that come from the AppSync server as part of the server-side GraphQL response. The client will still throw exceptions for cases when a valid response could not be successfully returned. -- Exceptions have been made more specific although they still extend abstract class `ApiException` so catch clauses that use `ApiException` will still be valid. +Amplify Flutter v2 removes all usage of the generated `Model.getId()` method. Replace all instances of `Model.getId()` and with `Model.modelIdentifier`. -## Storage +#### Nested Model Query Predicates -- All Storage S3 plugin APIs now return an operation object rather than the result object. The operation object provides more control over the in-flight request, such as cancellation, pause, and resume capabilities (varies by API). The result `Future` can be retrieved via the `.result` property of the operation object. Here is an example for `uploadFile`: +Query predicates for nested model relationships when passing Model.id for equal and not equal operations are no longer supported. Please update your query predicates to use Model.modelIdentifier, for example: -```dart -// v0 -final result = await Amplify.Storage.uploadFile( - local: exampleFile, - key: 'ExampleKey', -); -print('Uploaded file key: ${result.key}') - -// v1 -final result = await Amplify.Storage.uploadFile( - localFile: exampleFile, - key: 'ExampleKey', -).result; -print('Uploaded file key: ${result.uploadedItem.key}'); +```diff +- Post.BLOG.eq(blog1.id) ++ Post.BLOG.eq(blog1.modelIdentifier) + ``` +Or -See [storage docs](/gen1/[platform]/build-a-backend/storage/set-up-storage/) for more detailed examples of other storage methods. +```diff +- Post.BLOG.eq("1234") => ++ Post.BLOG.eq(BlogModelIdentifier(id: "1234")) + +``` From 7ee47f8b8b808420cac95a67dba97b7d816bcf5a Mon Sep 17 00:00:00 2001 From: NikaHsn Date: Fri, 26 Apr 2024 15:45:00 -0700 Subject: [PATCH 12/88] chore: update flutter v2 docs to use plugin options (#7336) --- .../lib/analytics/flutter/record.mdx | 29 ++----- .../lib/datastore/flutter/conflict.mdx | 51 +++++------ .../flutter/sync/50-selectiveSync.mdx | 85 ++++++++++--------- .../advanced-workflows/50_interceptor.mdx | 6 +- .../graphqlapi/flutter/authz/10_userpool.mdx | 4 +- .../flutter/authz/2X_add_plugin.mdx | 10 ++- .../flutter/getting-started/30_initapi.mdx | 4 +- .../lib/graphqlapi/flutter/subscribe-data.mdx | 9 +- 8 files changed, 104 insertions(+), 94 deletions(-) diff --git a/src/fragments/lib/analytics/flutter/record.mdx b/src/fragments/lib/analytics/flutter/record.mdx index a454009f2c9..4b57f62b977 100644 --- a/src/fragments/lib/analytics/flutter/record.mdx +++ b/src/fragments/lib/analytics/flutter/record.mdx @@ -28,27 +28,14 @@ However, it can take upwards of 30 minutes for the event to display in the Filte ## Flush events -Events have default configuration to flush out to the network every 30 seconds. If you would like to change this, update `amplifyconfiguration.dart` with the value in milliseconds you would like for `autoFlushEventsInterval`. This configuration will flush events every 10 seconds: - -```json -{ - "UserAgent": "aws-amplify-cli/2.0", - "Version": "1.0", - "analytics": { - "plugins": { - "awsPinpointAnalyticsPlugin": { - "pinpointAnalytics": { - "appId": "AppID", - "region": "Region" - }, - "pinpointTargeting": { - "region": "Region" - }, - "autoFlushEventsInterval": 10 - } - } - } -} +Events have default configuration to flush out to the network every 30 seconds. To modify the default value, assign the `autoFlushEventsInterval` property to your desired duration. The example below configures the plugin to flush events every 10 seconds. + +```dart +final plugin = AmplifyAnalyticsPinpoint( + options: const AnalyticsPinpointPluginOptions( + autoFlushEventsInterval: Duration(seconds: 10), + ), +); ``` > **Note** diff --git a/src/fragments/lib/datastore/flutter/conflict.mdx b/src/fragments/lib/datastore/flutter/conflict.mdx index 313d7fc3a7c..14d0b53bedf 100644 --- a/src/fragments/lib/datastore/flutter/conflict.mdx +++ b/src/fragments/lib/datastore/flutter/conflict.mdx @@ -1,7 +1,9 @@ -DataStore has a few optional configurations, such as the ability to specify a custom handler for error messages that take place in any part of the system. +DataStore has a few optional configurations, such as the ability to specify a custom handler for error messages that take place in any part of the system. You can also specify a custom conflict handler that runs if a mutation is rejected by AWS AppSync during one of the conflict resolution strategies. Finally you can configure the number of records to sync as an upper bound on items (per-Model) which will be stored locally on the device, as well as a custom interval in minutes which is an override of the default 24 hour "base query" which runs as part of the Delta Sync process. +To modify these settings, utilize the `options` parameter within the `AmplifyDataStore()` constructor. + ### Custom configuration fields - `errorHandler` - handler function executed when Datastore encounters an unhandled error during its background operations. - `conflictHandler` - handler function when there is a conflict between two local and remote model instances in a sync operation. @@ -18,29 +20,30 @@ Future initializeDataStoreWithConflictResolution() async { try { final datastorePlugin = AmplifyDataStore( modelProvider: ModelProvider.instance, - errorHandler: ((error) { - print('Custom ErrorHandler received: $error'); - }), - conflictHandler: (ConflictData data) { - final localData = data.local; - final remoteData = data.remote; - - if (localData is Post && remoteData is Post) { - final mergedPostData = Post( - // always favor the title from the local Post data - title: localData.title, - rating: remoteData.rating, - ); - - return ConflictResolutionDecision.retry(mergedPostData); - } - - return ConflictResolutionDecision.applyRemote(); - }, - // Sync configuration defaults: - syncInterval: 86400, - syncMaxRecords: 10000, - syncPageSize: 1000, + options: DataStorePluginOptions( + errorHandler: ((error) { + print('Custom ErrorHandler received: $error'); + }), + conflictHandler: (ConflictData data) { + final localData = data.local; + final remoteData = data.remote; + + if (localData is Post && remoteData is Post) { + final mergedPostData = Post( + // always favor the title from the local Post data + title: localData.title, + rating: remoteData.rating, + ); + return ConflictResolutionDecision.retry(mergedPostData); + } + + return const ConflictResolutionDecision.applyRemote(); + }, + // Sync configuration defaults: + syncInterval: 86400, + syncMaxRecords: 10000, + syncPageSize: 1000, + ), ); await Amplify.addPlugin(datastorePlugin); diff --git a/src/fragments/lib/datastore/flutter/sync/50-selectiveSync.mdx b/src/fragments/lib/datastore/flutter/sync/50-selectiveSync.mdx index 44ca569f74a..af3fe9d5bae 100644 --- a/src/fragments/lib/datastore/flutter/sync/50-selectiveSync.mdx +++ b/src/fragments/lib/datastore/flutter/sync/50-selectiveSync.mdx @@ -17,13 +17,15 @@ void _configureAmplify() async { // Update AmplifyDataStore instance like below final datastorePlugin = AmplifyDataStore( modelProvider: ModelProvider.instance, - syncExpressions: [ - DataStoreSyncExpression(Post.classType, () => Post.RATING.gt(5)), - DataStoreSyncExpression( - Comment.classType, - () => Comment.POST.beginsWith('This'), - ) - ], + options: DataStorePluginOptions( + syncExpressions: [ + DataStoreSyncExpression(Post.classType, () => Post.RATING.gt(5)), + DataStoreSyncExpression( + Comment.classType, + () => Comment.POST.beginsWith('This'), + ), + ], + ), ); ... } @@ -49,12 +51,14 @@ int rating = 5; Future initialize() async { final dataStorePlugin = AmplifyDataStore( modelProvider: ModelProvider.instance, - syncExpressions: [ - DataStoreSyncExpression( - Post.classType, - () => Post.RATING.gt(rating), - ), - ], + options: DataStorePluginOptions( + syncExpressions: [ + DataStoreSyncExpression( + Post.classType, + () => Post.RATING.gt(rating), + ), + ], + ), ); await Amplify.addPlugin(dataStorePlugin); @@ -113,20 +117,21 @@ You can also have your sync expression return `QueryPredicateConstant.all` in or int rating = 5; Future initialize() async { - var dataStorePlugin = AmplifyDataStore( + final dataStorePlugin = AmplifyDataStore( modelProvider: ModelProvider.instance, - syncExpressions: [ - DataStoreSyncExpression( - Post.classType, - () { - if (rating > 0) { - return QueryPredicate.all; - } - - return Post.RATING.gt(rating); - }, - ), - ], + options: DataStorePluginOptions( + syncExpressions: [ + DataStoreSyncExpression( + Post.classType, + () { + if (rating > 0) { + return QueryPredicate.all; + } + return Post.RATING.gt(rating); + }, + ), + ], + ), ); await Amplify.addPlugin(dataStorePlugin); @@ -173,12 +178,14 @@ Future initializeSingleEquals() async { // Using eq operator with the primary key of the GSI final singleEqualsStore = AmplifyDataStore( modelProvider: ModelProvider.instance, - syncExpressions: [ - DataStoreSyncExpression( - User.classType, - () => User.LASTNAME.eq("Doe"), - ), - ], + options: DataStorePluginOptions( + syncExpressions: [ + DataStoreSyncExpression( + User.classType, + () => User.LASTNAME.eq("Doe"), + ), + ], + ), ); await Amplify.addPlugin(singleEqualsStore); @@ -189,12 +196,14 @@ Future initializeChainedEquals() async { // chaining the gt operator with the sort key final chainedEqualGtStore = AmplifyDataStore( modelProvider: ModelProvider.instance, - syncExpressions: [ - DataStoreSyncExpression( - User.classType, - () => User.LASTNAME.eq("Doe").and(User.CREATEDAT.gt("2020-10-10")), - ), - ], + options: DataStorePluginOptions( + syncExpressions: [ + DataStoreSyncExpression( + User.classType, + () => User.LASTNAME.eq("Doe").and(User.CREATEDAT.gt("2020-10-10")), + ), + ], + ), ); await Amplify.addPlugin(chainedEqualGtStore); diff --git a/src/fragments/lib/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx b/src/fragments/lib/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx index af23e74869e..e590075b55c 100644 --- a/src/fragments/lib/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx +++ b/src/fragments/lib/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx @@ -30,6 +30,10 @@ class MyHttpRequestInterceptor extends AWSBaseHttpClient { // Then you can pass an instance of this client to `baseHttpClient` when you configure Amplify. await Amplify.addPlugins([ - AmplifyAPI(baseHttpClient: MyHttpRequestInterceptor()), + AmplifyAPI( + options: APIPluginOptions( + baseHttpClient: MyHttpRequestInterceptor(), + ), + ), ]); ``` diff --git a/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx b/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx index 87463a8cf3f..b9c5f7d3812 100644 --- a/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx +++ b/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx @@ -19,6 +19,8 @@ Afterwards add the following code to your app before you configure Amplify: ```dart await Amplify.addPlugins([ AmplifyAuthCognito(), - AmplifyAPI(modelProvider: ModelProvider.instance), + AmplifyAPI( + options: APIPluginOptions(modelProvider: ModelProvider.instance), + ), ]); ``` diff --git a/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx b/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx index 488197283df..55ca1bb10c3 100644 --- a/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx +++ b/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx @@ -3,10 +3,12 @@ Then, include it, along with any other auth providers, in the call to `addPlugin ```dart await Amplify.addPlugin( AmplifyAPI( - authProviders: const [ - CustomOIDCProvider(), - CustomFunctionProvider(), - ], + options: APIPluginOptions( + authProviders: const [ + CustomOIDCProvider(), + CustomFunctionProvider(), + ], + ), ), ); ``` diff --git a/src/fragments/lib/graphqlapi/flutter/getting-started/30_initapi.mdx b/src/fragments/lib/graphqlapi/flutter/getting-started/30_initapi.mdx index 28361a48898..4ca80abdf3c 100644 --- a/src/fragments/lib/graphqlapi/flutter/getting-started/30_initapi.mdx +++ b/src/fragments/lib/graphqlapi/flutter/getting-started/30_initapi.mdx @@ -25,7 +25,9 @@ class _MyAppState extends State { } Future _configureAmplify() async { - final api = AmplifyAPI(modelProvider: ModelProvider.instance); + final api = AmplifyAPI( + options: APIPluginOptions(modelProvider: ModelProvider.instance), + ); await Amplify.addPlugin(api); try { diff --git a/src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx b/src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx index 11ced1c1351..94f62434e52 100644 --- a/src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx +++ b/src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx @@ -93,10 +93,11 @@ Likewise, when disconnected from the internet unexpectedly, the subscription wil ```dart Future _configureAmplify() async { final apiPlugin = AmplifyAPI( - modelProvider: ModelProvider.instance, - // Optional config - subscriptionOptions: const GraphQLSubscriptionOptions( - retryOptions: RetryOptions(maxAttempts: 10), + options: APIPluginOptions( + modelProvider: ModelProvider.instance, + subscriptionOptions: const GraphQLSubscriptionOptions( + retryOptions: RetryOptions(maxAttempts: 10), + ), ), ); await Amplify.addPlugin(apiPlugin); From e2b528c99fd0ab3b31548cb402523ffd2388671c Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:08:33 -0700 Subject: [PATCH 13/88] chore: add multi auth back to v1 previous (#7335) --- .../signin_next_steps/common.mdx | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx b/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx index 01bb95e9eb9..c204a973298 100644 --- a/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx @@ -2,40 +2,54 @@ After a user has finished signup, they can proceed to sign in. Amplify Auth sign import ios0 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/10_signin.mdx'; - +import flutter0 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/10_signin.mdx'; + + ### Confirm signin with SMS MFA import ios1 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/20_confirm_sms_mfa.mdx'; - +import flutter1 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/20_confirm_sms_mfa.mdx'; + + ### Confirm signin with custom challenge import ios2 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/30_confirm_custom_challenge.mdx'; - +import flutter2 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx'; + + ### Confirm signin with new password import ios3 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/40_confirm_new_password.mdx'; - +import flutter3 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/40_confirm_new_password.mdx'; + + ### Reset password import ios4 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/50_reset_password.mdx'; - +import flutter4 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx'; + + ### Confirm Signup import ios5 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/60_confirm_signup.mdx'; - +import flutter5 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx'; + + ### Done import ios6 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/70_done.mdx'; - +import flutter6 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/70_done.mdx'; + + From 7d6d797eea3fa3c83a8468603c1325cbc54e37e9 Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Mon, 29 Apr 2024 14:26:19 -0400 Subject: [PATCH 14/88] StoragePath Documentation for Gen1 (#7279) * storage: update swift storage with storage path * chore: update swift storage docs with storage path * chore: update storage getting started content * chore: add callout warning to file access level * chore: update gen2 callout * chore: convert fragments to inline filter * update storage path example * Android Gen2 Storage * remove upload from getting started * Add Using Storagepath * add content to storage path page * Expand storagepath * improve doc * update callout message * modify file access level callout * add accidental remove * gramatical issues * Update src/pages/[platform]/build-a-backend/storage/download/index.mdx Co-authored-by: Jordan Nelson * Update src/pages/[platform]/build-a-backend/storage/upload/index.mdx Co-authored-by: Jordan Nelson * Update src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx Co-authored-by: Jordan Nelson * Update src/pages/[platform]/build-a-backend/storage/list/index.mdx Co-authored-by: Jordan Nelson * Update src/pages/[platform]/build-a-backend/storage/remove/index.mdx Co-authored-by: Jordan Nelson * Bump Android version --------- Co-authored-by: Tuan Pham Co-authored-by: Jordan Nelson --- .../storage/storagepath/index.mdx | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx diff --git a/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx b/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx new file mode 100644 index 00000000000..4b0927d0347 --- /dev/null +++ b/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx @@ -0,0 +1,107 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Using StoragePath', + description: "Learn more about constructing a StoragePath to use on Amplify Storage APIs", + platforms: [ + 'swift', + 'android' + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +You can use `StoragePath` to access, upload to, or download from to any path in your S3 bucket. The Amplify Gen 1 CLI automatically creates the following directories: +- `public/`: Accessible by all users of your application +- `protected//`: Readable by all users (you need to specify the identityID of the user who uploaded the file). Writable only by the creating user +- `private//`: Readable and writable only by the creating user + +If you are using Amplify Gen2 or an S3 bucket not created by Amplify, you can use StoragePath to access, upload to, or download from any directory in your bucket. + +You can specify the path to your S3 resource by creating a `StoragePath` directly from a String, or by constructing the path with the user's Cognito IdentityId. + +A `StoragePath` must: + +1. Not start with a '/' (leading slash) +2. Not be empty + +## Create a StoragePath from String +When constructing a StoragePath from a String, the provided string will be the path. + + + +```swift +// Resolves to "public/exampleFile.txt" +StoragePath.fromString("public/exampleFile.txt") +``` + + + + + + ```java + // Resolves to "public/exampleFile.txt" + StoragePath.fromString("public/exampleFile.txt") + ``` + + + + ```kotlin + // Resolves to "public/exampleFile.txt" + StoragePath.fromString("public/exampleFile.txt") + ``` + + + + + + +## Create a StoragePath with user’s IdentityId +You may want to construct a StoragePath that contains the Amplify Auth user’s IdentityId. We’ve created a helper function that injects the user’s IdentityId when a Storage API is called, since fetching an IdentityId from the Auth plugin is not synchronous. + + + +```swift +// If the user's identityId was "123", +// the StoragePath would resolve to "private/123/exampleFile.txt" +StoragePath.fromIdentityID { identityId in + return "private/\(identityId)/exampleFile.txt" +} +``` + + + + + + ```java + // If the user's identityId was "123", + // the StoragePath would resolve to "private/123/exampleFile.txt" + StoragePath.fromIdentityId(identityId -> + "private/" + identityId + "/exampleFile.txt" + ); + ``` + + + + ```kotlin + // If the user's identityId was "123", + // the StoragePath would resolve to "private/123/exampleFile.txt" + StoragePath.fromIdentityId { identityId -> + "private/${identityId}/exampleFile.txt" + } + ``` + + + From 25b4ab11fc55f001bb6351c3f8814ed7b89ebe23 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:36:36 -0700 Subject: [PATCH 15/88] chore: add kotlin update to docs and formatting changes for flutter (#7023) * chore: add kotlin update to docs and formatting changes for new flutter build.gradle creation * chore: remove steps for apps created with flutter 3.16 and earlier From c978ee857bb7b9012c2e8ecff7dbcfa5bc0c75aa Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Mon, 29 Apr 2024 16:55:14 -0400 Subject: [PATCH 16/88] chore: apply suggestions from code review Co-authored-by: Muhammed Salih Guler --- .../flutter/upgrade-guide/upgrade-guide.mdx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx index 95f288679d5..67fa078e953 100644 --- a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx +++ b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx @@ -8,7 +8,9 @@ In Amplify Flutter v1, `Amplify.Auth.verifyTotpSetup()` throws an `EnableSoftwar #### Auth Flow in Amplify Config -In Amplify Flutter v1, the value of `“authenticationFlowType”` from the amplify configuration object is used as the default `AuthenticationFlowType` if none is provided when calling `Amplify.Auth.signIn()`. In Amplify Flutter v2 `AuthenticationFlowType.userSrpAuth` is the default if none is provided. To change the authentication flow type, provide a value in the sign in options. +In Amplify Flutter v1, the value of `“authenticationFlowType”` from the Amplify configuration object is used as the default `AuthenticationFlowType` if none is provided when calling `Amplify.Auth.signIn()`. + +In Amplify Flutter v2, default type is `AuthenticationFlowType.userSrpAuth`. To change the authentication flow type, you can provide a value in the sign in options under `options` parameter.. ```dart await Amplify.Auth.signIn( @@ -27,19 +29,19 @@ await Amplify.Auth.signIn( #### Configuration of `autoFlushEventsInterval` -In Amplify Flutter v1, the autoFlushEventsInterval value was configured manually in the amplifyconfiguration.dart file. In Amplify Flutter v2, the autoFlushEventsInterval value is not read from amplifyconfiguration.dart but instead is passed directly to the `AmplifyAnalyticsPinpoint()` constructor under the `AnalyticsPinpointPluginOptions`. The default value for autoFlushEventsInterval remains at 30 seconds, consistent with the behavior in Amplify Flutter v1. +In Amplify Flutter v1, the `autoFlushEventsInterval` value was configured manually in the `amplifyconfiguration.dart` file. In Amplify Flutter v2, the `autoFlushEventsInterval` value is not read from `amplifyconfiguration.dart` but instead is passed directly to the `AmplifyAnalyticsPinpoint()` constructor under the `AnalyticsPinpointPluginOptions`. The default value for `autoFlushEventsInterval` remains at 30 seconds, consistent with the behavior in Amplify Flutter v1. ## Storage #### `key` and `StorageAccessLevel` have been replaced by `StoragePath` -In Amplify Flutter v1, files operations were performed based on a key and an AccessLevel. Amplify translated these values to a full file path. The AccessLevel (guest, protected, or private) determined what prefix to use (`“public/”`, `“protected//”`, or `“private//”`). Amplify Flutter v1 forms the full path using the prefix and the provided key. +In Amplify Flutter v1, files operations were performed based on a `key` and an `AccessLevel`. Amplify translated these values to a full file path. The `AccessLevel` (guest, protected, or private) determined what prefix to use (`“public/”`, `“protected//”`, or `“private//”`). Amplify Flutter v1 forms the full path using the prefix and the provided key. Amplify Flutter v2 allows for full control of the path of the storage object. Storage paths can be constructed either from a static string or from the current users identity id using the StoragePath class. To migrate from v1 to v2, replace all uses of `key` with `path` and remove uses of `StorageAccessLevel`. The path should include the prefix that was previously added by Amplify automatically. -##### Example for Public Files +##### Example Migration for Public Files ```dart // upload file to "public/file.txt" in Amplify Flutter v1 @@ -60,7 +62,7 @@ Amplify.Storage.uploadFile( ); ``` -##### Example for Protected Files +##### Example Migration for Protected Files ```dart // upload file to "protected//file.txt" in Amplify Flutter v1 @@ -83,7 +85,7 @@ Amplify.Storage.uploadFile( ); ``` -##### Example for Private Files +##### Example Migration for Private Files ```dart // upload file to "private//file.txt" in Amplify Flutter v1 From f6dc40cec71547c0d7a08c20d703123946fae73b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:00:49 -0700 Subject: [PATCH 17/88] Chore: Removed v0 flutter fragments (#7349) * chore: remove v0 flutter fragment * chore: remove move flutter fragment * chore: remove the move page from the directory and remove move from pages completely --- src/directory/directory.mjs | 3 --- .../graphqlapi/native_common/authz/common.mdx | 4 --- .../build-a-backend/storage/move/index.mdx | 26 ------------------- 3 files changed, 33 deletions(-) delete mode 100644 src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 5ebd48dc3dc..50eb1619f32 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -1737,9 +1737,6 @@ export const directory = { { path: 'src/pages/gen1/[platform]/build-ui/formbuilder/lifecycle/index.mdx' }, - { - path: 'src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx' - }, { path: 'src/pages/gen1/[platform]/build-ui/formbuilder/call-to-action/index.mdx' }, diff --git a/src/fragments/lib-v1/graphqlapi/native_common/authz/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/authz/common.mdx index e9b8cba1557..c6e245b3953 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/authz/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/authz/common.mdx @@ -275,10 +275,6 @@ You can now configure a single GraphQL API to deliver private and public data. P As discussed in the above linked documentation, certain fields may be protected by different authorization types. This can lead the same query, mutation, or subscription to have different responses based on the authorization sent with the request; Therefore, it is recommended to use the different `friendly_name_` as the `apiName` parameter in the `Amplify.API` call to reference each authorization type. -import flutter18 from '/src/fragments/lib-v1/graphqlapi/flutter/authz/auth_mode.mdx'; - - - The following snippets highlight the new values in the `amplifyconfiguration.json`/`.dart` and the client code configurations. The `friendly_name` illustrated here is created from Amplify CLI prompt. There are 4 clients in this configuration that connect to the same API except that they use different `AuthMode`. diff --git a/src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx deleted file mode 100644 index 3988e173a4c..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/storage/move/index.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Move files', - description: "Learn more about how to move files using Amplify's storage category.", - platforms: ['flutter'] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -import flutter0 from '/src/fragments/lib/storage/flutter/move.mdx'; - - From 6b05e171099da26483be278b6c5588eef3c6adf0 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:59:40 -0700 Subject: [PATCH 18/88] chore: fix broken links by adding inline filters (#7356) * chore: fix broken links by adding inline filters * chore: remove the line instead of use InlineFilter since flutter is the only platform with this page * chore: remove getting started link under storage subheading that was being used as a placeholder --- src/constants/feature-lists-data.ts | 6 ------ .../native_common/getting-started/common.mdx | 2 ++ .../native_common/getting-started/common.mdx | 2 ++ .../prev/build-a-backend/storage/import/index.mdx | 2 -- .../build-a-backend/storage/import/index.mdx | 13 +++++++++++++ 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/constants/feature-lists-data.ts b/src/constants/feature-lists-data.ts index f05d3579495..978eaee6a46 100644 --- a/src/constants/feature-lists-data.ts +++ b/src/constants/feature-lists-data.ts @@ -266,12 +266,6 @@ const featureListData = { 'Upload and download files to and from cloud storage with advanced controls like pausing and resuming upload operations.', linkText: 'Upload and Download files', link: 'build-a-backend/storage/upload/' - }, - { - content: - 'Manage content through APIs for listing, accessing, and manipulating files. Set file permission levels, configure automatic events and triggers, and more.', - linkText: 'Advanced file operations and access control', - link: 'build-a-backend/storage/configure-access/' } ], heading: 'Storage' diff --git a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx index 9fe3f614603..ea06a51a502 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx @@ -137,6 +137,8 @@ Congratulations! You've created a `Todo` object in your database. Check out the - [Update data](/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/) - [Subscribe to data](/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/) - [Concepts](/gen1/[platform]/prev/build-a-backend/graphqlapi/api-graphql-concepts/) + - [Configure authorization modes](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes) + {/* TODO: * [Authorizing API calls with Cognito User Pool] */} diff --git a/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx index 873eff2a373..9a2e55c8f1d 100644 --- a/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx @@ -141,6 +141,8 @@ Congratulations! You've created a `Todo` object in your database. Check out the - [Update data](/gen1/[platform]/build-a-backend/graphqlapi/mutate-data/) - [Subscribe to data](/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/) - [Concepts](/gen1/[platform]/build-a-backend/graphqlapi/api-graphql-concepts/) + - [Configure authorization modes](/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules) + {/* TODO: * [Authorizing API calls with Cognito User Pool] */} diff --git a/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx index ff7da184d50..8e3f7a1ed37 100644 --- a/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx +++ b/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx @@ -67,8 +67,6 @@ By default, Amplify Libraries assumes that S3 buckets are configured with the fo - `protected/{user_identity_id}/` - Readable by all users, but writable only by the creating user - `private/{user_identity_id}/` - Only accessible for the individual user -You can either configure your IAM role to use the Amplify-recommended policies or in your Amplify libraries configuration [overwrite the default storage path behavior](/[platform]/build-a-backend/storage/configure-access/#customize-object-key-path). - It is highly recommended to review your S3 bucket's CORS settings. Review the [recommendation guide here](/[platform]/build-a-backend/storage/set-up-storage/#amazon-s3-bucket-cors-policy-setup). ### Configuring IAM role to use Amplify-recommended policies diff --git a/src/pages/gen1/[platform]/build-a-backend/storage/import/index.mdx b/src/pages/gen1/[platform]/build-a-backend/storage/import/index.mdx index db7be1818c0..9486eaddaa3 100644 --- a/src/pages/gen1/[platform]/build-a-backend/storage/import/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/storage/import/index.mdx @@ -67,8 +67,21 @@ By default, Amplify Libraries assumes that S3 buckets are configured with the fo - `protected/{user_identity_id}/` - Readable by all users, but writable only by the creating user - `private/{user_identity_id}/` - Only accessible for the individual user + + You can either configure your IAM role to use the Amplify-recommended policies or in your Amplify libraries configuration [overwrite the default storage path behavior](/gen1/[platform]/build-a-backend/storage/configure-access/#customize-object-key-path). + + It is highly recommended to review your S3 bucket's CORS settings. Review the [recommendation guide here](/gen1/[platform]/build-a-backend/storage/set-up-storage/#amazon-s3-bucket-cors-policy-setup). ### Configuring IAM role to use Amplify-recommended policies From e7d8ffb0d4f73d83b5474c22e942e71d9c6caac7 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:28:46 -0700 Subject: [PATCH 19/88] chore: added filter to fix broken link (#7382) --- .../lib-v1/graphqlapi/existing-resources.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/fragments/lib-v1/graphqlapi/existing-resources.mdx b/src/fragments/lib-v1/graphqlapi/existing-resources.mdx index 6b0ac176068..34b1ee6b309 100644 --- a/src/fragments/lib-v1/graphqlapi/existing-resources.mdx +++ b/src/fragments/lib-v1/graphqlapi/existing-resources.mdx @@ -18,9 +18,27 @@ Existing AWS AppSync resources can be used with the Amplify Libraries by referen } ``` + - **API NAME**: Friendly name for the API (e.g., _api_) - **endpoint**: The HTTPS endpoint of the AWS AppSync API (e.g. `https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql`). [Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. `https://api.yourdomain.com/graphql`). Custom domain names can have any format, but must end with `/graphql` (see https://graphql.org/learn/serving-over-http/#uris-routes). - **region**: AWS Region where the resources are provisioned (e.g. _us-east-1_) - **authorizationType**: Authorization mode for accessing the API. This can be one of: `AMAZON_COGNITO_USER_POOLS`, `AWS_IAM`, `OPENID_CONNECT`, or `API_KEY`. Each mode requires additional configuration parameters. See [Configure authorization modes](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes) for details. + + + +- **API NAME**: Friendly name for the API (e.g., _api_) + - **endpoint**: The HTTPS endpoint of the AWS AppSync API (e.g. `https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql`). [Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. `https://api.yourdomain.com/graphql`). Custom domain names can have any format, but must end with `/graphql` (see https://graphql.org/learn/serving-over-http/#uris-routes). + - **region**: AWS Region where the resources are provisioned (e.g. _us-east-1_) + - **authorizationType**: Authorization mode for accessing the API. This can be one of: `AMAZON_COGNITO_USER_POOLS`, `AWS_IAM`, `OPENID_CONNECT`, or `API_KEY`. Each mode requires additional configuration parameters. See [Configure authorization modes](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes) for details. + Note that before you can add an AWS resource to your application, the application must have the Amplify libraries installed. If you need to perform this step, see [Install Amplify Libraries](/gen1/[platform]/prev/start/project-setup/create-application/#n2-install-amplify-libraries). From aec7c35e5adebd54901304d8480118781311585c Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 6 May 2024 10:19:36 -0700 Subject: [PATCH 20/88] chore: remove move from directory --- src/directory/directory.mjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 50eb1619f32..2a3914cc57f 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -1256,9 +1256,6 @@ export const directory = { { path: 'src/pages/gen1/[platform]/build-a-backend/storage/copy/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/build-a-backend/storage/move/index.mdx' - }, { path: 'src/pages/gen1/[platform]/build-a-backend/storage/remove/index.mdx' }, From a2de1ef5ba580385f875783c8ecd8d208825bbf5 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 6 May 2024 15:02:39 -0700 Subject: [PATCH 21/88] chore: fix gen1 v1 authentication section --- src/directory/directory.mjs | 19 +- .../auth/common/device_features/common.mdx | 2 + .../lib-v1/auth/common/sms/flows.mdx | 6 +- .../signin_next_steps/100_totp_setup.mdx | 29 ++ .../flutter/signin_next_steps/80_totp.mdx | 27 ++ .../signin_next_steps/90_mfa_selection.mdx | 34 ++ .../native_common/guest_access/common.mdx | 7 +- .../auth/native_common/signin/common.mdx | 4 + .../signin_next_steps/common.mdx | 18 + .../auth/native_common/signout/common.mdx | 28 ++ .../native_common/user_attributes/common.mdx | 4 + .../auth/admin-actions/index.mdx | 361 ++++++++++++++++++ .../auth/existing-resources-no-cli/index.mdx | 33 ++ .../auth/import-existing-resources/index.mdx | 111 ++++++ .../build-a-backend/auth/manage-mfa/index.mdx | 4 + .../auth/override-cognito/index.mdx | 185 +++++++++ .../auth/user-group-management/index.mdx | 117 ++++++ 17 files changed, 982 insertions(+), 7 deletions(-) create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/100_totp_setup.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/80_totp.mdx create mode 100644 src/fragments/lib-v1/auth/flutter/signin_next_steps/90_mfa_selection.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 2a3914cc57f..147afc99163 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -2175,10 +2175,10 @@ export const directory = { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/sign-in-with-web-ui/index.mdx' }, { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/enable-guest-access/index.mdx' + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/multi-step-sign-in/index.mdx' }, { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/multi-step-sign-in/index.mdx' + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/enable-guest-access/index.mdx' }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/add-social-provider/index.mdx' @@ -2210,9 +2210,24 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/delete-user-account/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/auth-events/index.mdx' }, diff --git a/src/fragments/lib-v1/auth/common/device_features/common.mdx b/src/fragments/lib-v1/auth/common/device_features/common.mdx index 576d81a3510..efcd509ceb0 100644 --- a/src/fragments/lib-v1/auth/common/device_features/common.mdx +++ b/src/fragments/lib-v1/auth/common/device_features/common.mdx @@ -97,8 +97,10 @@ import flutter11 from '/src/fragments/lib-v1/auth/flutter/device_features/30_fet import flutter13 from '/src/fragments/lib-v1/auth/flutter/device_features/40_trackDevice.mdx'; + ## Known Limitations When using the federated OAuth flow with Cognito User Pools, the [device tracking and remembering](https://aws.amazon.com/blogs/mobile/tracking-and-remembering-devices-using-amazon-cognito-your-user-pools/) features are currently not available within the library. + diff --git a/src/fragments/lib-v1/auth/common/sms/flows.mdx b/src/fragments/lib-v1/auth/common/sms/flows.mdx index ecc8ee7feac..d91a800a4c5 100644 --- a/src/fragments/lib-v1/auth/common/sms/flows.mdx +++ b/src/fragments/lib-v1/auth/common/sms/flows.mdx @@ -84,7 +84,7 @@ import all1 from "/src/fragments/lib-v1/auth/common/sms/add_verification.mdx"; -### MFA +### SMS MFA import all2 from "/src/fragments/lib-v1/auth/common/sms/add_mfa.mdx"; @@ -108,7 +108,7 @@ import all4 from "/src/fragments/lib-v1/auth/common/sms/update_verification.mdx" -### MFA +### SMS MFA import all5 from "/src/fragments/lib-v1/auth/common/sms/update_mfa.mdx"; @@ -156,4 +156,4 @@ If MFA is **ON** or enabled for the user, you must call `confirmSignIn` with the import flutter9 from "/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_in.mdx"; - \ No newline at end of file + diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/100_totp_setup.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/100_totp_setup.mdx new file mode 100644 index 00000000000..50232e21a50 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/100_totp_setup.mdx @@ -0,0 +1,29 @@ +If the next step is `continueSignInWithTOTPSetup`, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type `TOTPSetupDetails` which would be used for generating TOTP. `TOTPSetupDetails` provides a helper method called `getSetupURI` that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, `TOTPSetupDetails` also contains the `sharedSecret` that will be used to either generate a QR code or can be manually entered into an authenticator app. + +Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ··· + case AuthSignInStep.continueSignInWithTotpSetup: + final totpSetupDetails = result.nextStep.totpSetupDetails!; + final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); + safePrint('Open URI to complete setup: $setupUri'); + // ··· + } +} + +// Then, pass the TOTP code to `confirmSignIn` + +Future confirmTotpUser(String totpCode) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: totpCode, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming TOTP code: ${e.message}'); + } +} +``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/80_totp.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/80_totp.mdx new file mode 100644 index 00000000000..7ecf9e396a8 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/80_totp.mdx @@ -0,0 +1,27 @@ +If the next step is `confirmSignInWithTOTPCode`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. + +After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ··· + case AuthSignInStep.confirmSignInWithTotpMfaCode: + safePrint('Enter a one-time code from your registered authenticator app'); + // ··· + } +} + +// Then, pass the TOTP code to `confirmSignIn` + +Future confirmTotpUser(String totpCode) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: totpCode, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming TOTP code: ${e.message}'); + } +} +``` diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/90_mfa_selection.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/90_mfa_selection.mdx new file mode 100644 index 00000000000..334fea10d16 --- /dev/null +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/90_mfa_selection.mdx @@ -0,0 +1,34 @@ +If the next step is `continueSignInWithMFASelection`, the user must select the MFA method to use. Amplify Auth currently only supports SMS and TOTP as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. + +The MFA types which are currently supported by Amplify Auth are: + +- `MfaType.sms` +- `MfaType.totp` + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ··· + case AuthSignInStep.continueSignInWithMfaSelection: + final allowedMfaTypes = result.nextStep.allowedMfaTypes!; + final selection = await _promptUserPreference(allowedMfaTypes); + return _handleMfaSelection(selection); + // ··· + } +} + +Future _promptUserPreference(Set allowedTypes) async { + // ··· +} + +Future _handleMfaSelection(MfaType selection) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: selection.confirmationValue, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error resending code: ${e.message}'); + } +} +``` diff --git a/src/fragments/lib-v1/auth/native_common/guest_access/common.mdx b/src/fragments/lib-v1/auth/native_common/guest_access/common.mdx index e726fe990c9..e93be0150c8 100644 --- a/src/fragments/lib-v1/auth/native_common/guest_access/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/guest_access/common.mdx @@ -4,8 +4,11 @@ Follow these steps to enable guest access on your Auth category: ```console amplify update auth -What do you want to do? Walkthrough all the auth configurations -Select the authentication/authorization services that you want to use: [choose whatever you initially selected - default is User Sign-Up, Sign-In, connected with AWS IAM controls] + +What do you want to do? +❯ Walkthrough all the auth configurations +Select the authentication/authorization services that you want to use: +❯ [choose whatever you initially selected - default is User Sign-Up, Sign-In, connected with AWS IAM controls] Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) ❯ Yes No diff --git a/src/fragments/lib-v1/auth/native_common/signin/common.mdx b/src/fragments/lib-v1/auth/native_common/signin/common.mdx index 67ec2f0181e..070c81b3032 100644 --- a/src/fragments/lib-v1/auth/native_common/signin/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signin/common.mdx @@ -44,11 +44,13 @@ import flutter8 from '/src/fragments/lib-v1/auth/flutter/signin/20_confirmSignUp + You will know the sign up flow is complete if you see the following in your console window: ```console Confirm signUp succeeded ``` + ## Sign in a user @@ -66,11 +68,13 @@ import flutter11 from '/src/fragments/lib-v1/auth/flutter/signin/30_signIn.mdx'; + You will know the sign in flow is complete if you see the following in your console window: ```console Sign in succeeded ``` + You have now successfully registered a user and authenticated with that user's username and password with Amplify. The Authentication category supports other mechanisms for authentication such as web UI based sign in, sign in using other providers etc that you can explore in the other sections. diff --git a/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx b/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx index c204a973298..9c122d613df 100644 --- a/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx @@ -14,6 +14,24 @@ import flutter1 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/20_co +### Confirm signin with TOTP MFA + +import flutter7 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/80_totp.mdx'; + + + +### Continue signin with MFA Selection + +import flutter8 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/90_mfa_selection.mdx'; + + + +### Continue signin with TOTP Setup + +import flutter9 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/100_totp_setup.mdx'; + + + ### Confirm signin with custom challenge import ios2 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/30_confirm_custom_challenge.mdx'; diff --git a/src/fragments/lib-v1/auth/native_common/signout/common.mdx b/src/fragments/lib-v1/auth/native_common/signout/common.mdx index 134180c05f7..cd9e45bf6fc 100644 --- a/src/fragments/lib-v1/auth/native_common/signout/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signout/common.mdx @@ -1,4 +1,10 @@ + Invoke the `signOut` api to sign out a user from the Auth category. You can only have one user signed in at a given time. + + + +Invoke the `signOut` api to sign out a user from the Auth category. You can only have one user signed in at a given time. Calling signOut without any options will delete the local cache and keychain of the user and revoke the token if enabled on Amazon Cognito User Pools. If you would like to sign out of all devices, invoke the signOut api with advanced options. + import android0 from '/src/fragments/lib-v1/auth/android/signout/10_local_signout.mdx'; @@ -12,6 +18,7 @@ import flutter2 from '/src/fragments/lib-v1/auth/flutter/signout/10_local_signou + Calling signOut without any options will just delete the local cache and keychain of the user. If you would like to sign out of all devices, invoke the signOut api with advanced options. [Amazon Cognito now supports token revocation](https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-cognito-now-supports-targeted-sign-out-through-refresh-token-revocation/) and the latest Amplify version will revoke Amazon Cognito tokens if the application is online. This means that the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. @@ -21,6 +28,25 @@ Access and Id Tokens are short-lived (60 minutes by default but can be set from For limiting subsequent calls to these other services after invalidating tokens, we recommend lowering token expiration time for your app client in the Cognito User Pools console. If you are using the Amplify CLI this can be accessed by running `amplify console auth`. Token revocation is enabled automatically on new Amazon Cognito User Pools, however existing User Pools must enable this feature, [using the Cognito Console or AWS CLI](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html). + + + +## Token Revocation + +[Amazon Cognito now supports token revocation](https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-cognito-now-supports-targeted-sign-out-through-refresh-token-revocation/). This means that the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. + +Access and Id Tokens are short-lived (60 minutes by default but can be set from 5 minutes to 1 day). After revocation, these tokens cannot be used with Cognito User Pools anymore. However, they are still valid when used with other services like AppSync or API Gateway. + +For limiting subsequent calls to these other services after invalidating tokens, we recommend lowering token expiration time for your app client in the Cognito User Pools console. If you are using the Amplify CLI this can be accessed by running `amplify console auth`. + +Token revocation is enabled automatically on new Amazon Cognito User Pools, however existing User Pools must enable this feature, [using the Cognito Console or AWS CLI](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html). + + + +## Global Sign Out + +Calling signout with `globalSignOut = true` will invalidate all the Cognito User Pool tokens of the signed in user. If the user is signed into a device, they won't be authorized to perform a task that requires a valid token when a global signout is called from some other device. They need to sign in again to get valid tokens. + import android3 from '/src/fragments/lib-v1/auth/android/signout/20_global_signout.mdx'; @@ -34,9 +60,11 @@ import flutter5 from '/src/fragments/lib-v1/auth/flutter/signout/20_global_signo + Calling signout with `globalSignOut = true` will invalidate all the Cognito User Pool tokens of the signed in user. If the user is signed into a device, they won't be authorized to perform a task that requires a valid token when a global signout is called from some other device. They need to sign in again to get valid tokens. Global signout functionality does not work if you use one of the web UI sign in methods. + diff --git a/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx b/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx index 708833a35d0..68f816e8083 100644 --- a/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx @@ -61,3 +61,7 @@ import android10 from '/src/fragments/lib-v1/auth/android/user_attributes/40_res import flutter11 from '/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx'; + +import flutter12 from '/src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx new file mode 100644 index 00000000000..ea08192ffa6 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx @@ -0,0 +1,361 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Set up admin actions', + description: 'Learn how to expose administrative actions for your Cognito User Pool to your end user applications.', + platforms: [ + 'flutter', + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +Admin Actions allow you to execute queries and operations against users and groups in your Cognito user pool. + +For example, the ability to list all users in a Cognito User Pool may provide useful for the administrative panel of an app if the logged-in user is a member of a specific Group called "Admins". + +> This is an advanced feature that is not recommended without an understanding of the underlying architecture. The associated infrastructure which is created is a base designed for you to customize for your specific business needs. We recommend removing any functionality which your app does not require. + +The Amplify CLI can setup a REST endpoint with secure access to a Lambda function running with limited permissions to the User Pool if you wish to have these capabilities in your application, and you can choose to expose the actions to all users with a valid account or restrict to a specific User Pool Group. + +## Enable Admin Queries + + + + +```bash +amplify add auth +``` + +Select the option to go through Manual configuration. + +```console + Do you want to use the default authentication and security configuration? (Use arrow keys) + Default configuration + Default configuration with Social Provider (Federation) +❯ Manual configuration + I want to learn more. +``` + +Go through the rest of the configuration steps until you reach the following prompts: + +```console +? Do you want to add User Pool Groups? Yes +? Provide a name for your user pool group: Admins +? Do you want to add another User Pool Group No +✔ Sort the user pool groups in order of preference · Admins +? Do you want to add an admin queries API? Yes +? Do you want to restrict access to the admin queries API to a specific Group? Yes +? Select the group to restrict access with: (Use arrow keys) +❯ Admins + Enter a custom group +``` + +Continue with the rest of the prompts to finish the configuration. + + + + + +```bash +amplify update auth +``` + +Select the option to Create or update Admin queries API. + +```console +What do you want to do? Create or update Admin queries API +? Do you want to restrict access to the admin queries API to a specific Group Yes +? Select the group to restrict access with: (Use arrow keys) +❯ Admins + Enter a custom group +``` + + + + + + + +If you don't have any User Pool Groups, you will need to select `Enter a custom group`. + + + +When ready, run `amplify push` to deploy the changes. + +This will configure an API Gateway endpoint with a Cognito Authorizer that accepts an Access Token, which is used by a Lambda function to perform actions against the User Pool. The function is example code which you can use to remove, add, or alter functionality based on your business case by editing it in the `amplify/backend/function/AdminQueriesXXX/src` directory and running an `amplify push` to deploy your changes. If you choose to restrict actions to a specific Group, custom middleware in the function will prevent any actions unless the user is a member of that Group. + +## Admin Queries API + +The default routes and their functions, HTTP methods, and expected parameters are below + +- `addUserToGroup`: Adds a user to a specific Group. Expects `username` and `groupname` in the POST body. +- `removeUserFromGroup`: Removes a user from a specific Group. Expects `username` and `groupname` in the POST body. +- `confirmUserSignUp`: Confirms a users signup. Expects `username` in the POST body. +- `disableUser`: Disables a user. Expects `username` in the POST body. +- `enableUser`: Enables a user. Expects `username` in the POST body. +- `getUser`: Gets specific user details. Expects `username` as a GET query string. +- `listUsers`: Lists all users in the current Cognito User Pool. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. +- `listGroups`: Lists all groups in the current Cognito User Pool. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. +- `listGroupsForUser`: Lists groups to which current user belongs to. Expects `username` as a GET query string. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. +- `listUsersInGroup`: Lists users that belong to a specific group. Expects `groupname` as a GET query string. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. +- `signUserOut`: Signs a user out from User Pools, but only if the call is originating from that user. Expects `username` in the POST body. + +## Example + +To leverage this functionality in your app you would call the appropriate route from `Amplify.API` after signing in. The following example adds the user "richard" to the Editors Group and then list all members of the Editors Group with a pagination limit of 10: + + + + + +```js +import React from 'react' +import { Amplify } from 'aws-amplify'; +import { fetchAuthSession } from 'aws-amplify/auth'; +import { post } from 'aws-amplify/api' +import { withAuthenticator } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; + +import amplifyconfig from './amplifyconfiguration.json'; +Amplify.configure(amplifyconfig); + +const client = generateClient() + +async function addToGroup() { + let apiName = 'AdminQueries'; + let path = '/addUserToGroup'; + let options = { + body: { + "username" : "richard", + "groupname": "Editors" + }, + headers: { + 'Content-Type' : 'application/json', + Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` + } + } + return post({apiName, path, options}); +} + +async function listEditors(limit){ + let apiName = 'AdminQueries'; + let path = '/listUsersInGroup'; + let options = { + queryStringParameters: { + "groupname": "Editors", + "limit": limit, + }, + headers: { + 'Content-Type' : 'application/json', + Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` + } + } + const response = await get({apiName, path, options}); + return response; +} + +function App() { + return ( +
+ + +
+ ); +} + +export default withAuthenticator(App); +``` + +
+ + + +1. Initialize Amplify API. Refer to [Set up Amplify REST API](/gen1/[platform]/build-a-backend/restapi/set-up-rest-api/) for more details. + +You should have the initialization code including the imports: + +```swift +import Amplify +import AWSCognitoAuthPlugin +import AWSAPIPlugin +``` + +and code that adds `AWSCognitoAuthPlugin` and `AWSAPIPlugin` before configuring Amplify. + +```swift +try Amplify.add(plugin: AWSCognitoAuthPlugin()) +try Amplify.add(plugin: AWSAPIPlugin()) +try Amplify.configure() +``` + +2. Sign in using `Amplify.Auth`. See [Amplify.Auth](/gen1/[platform]/build-a-backend/auth/set-up-auth/) to learn more about signing up and signing in a user. + +3. Use the following in your app to add a user to the Group. + +```swift +func addToGroup(username: String, groupName: String) async { + let path = "/addUserToGroup" + let body = "{\"username\":\"\(username)\",\"groupname\":\"\(groupName)\"}".data(using: .utf8) + let request = RESTRequest(path: path, body: body) + do { + let data = try await Amplify.API.post(request: request) + print("Response Body: \(String(decoding: data, as: UTF8.self))") + } catch { + if case let .httpStatusError(statusCode, response) = error as? APIError, + let awsResponse = response as? AWSHTTPURLResponse, + let responseBody = awsResponse.body { + print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") + } + } +} + +await addToGroup(username: "richard", groupName: "Editors") +``` + +4. Use the following to list the users in the Group. + +```swift +func listEditors(groupName: String, limit: Int, nextToken: String? = nil) async { + let path = "/listUsersInGroup" + var query = [ + "groupname": groupName, + "limit": String(limit) + ] + if let nextToken = nextToken { + query["token"] = nextToken + } + + let request = RESTRequest(path: path, queryParameters: query, body: nil) + do { + let data = try await Amplify.API.get(request: request) + print("Response Body: \(String(decoding: data, as: UTF8.self))") + } catch { + if case let .httpStatusError(statusCode, response) = error as? APIError, + let awsResponse = response as? AWSHTTPURLResponse, + let responseBody = awsResponse.body { + print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") + } + } +} + +await listEditors(groupName: "Editors", limit: 10) +``` + +**Note: Cognito User Pool with HostedUI** + +The Admin Queries API configuration in **amplifyconfiguration.json** will have the endpoint's authorization type set to `AMAZON_COGNITO_USER_POOLS`. With this authorization type, `Amplify.API` will perform the request with the access token. However, when using HostedUI, the app may get unauthorized responses despite being signed in, and will require using the ID Token. Set the authorizationType to "NONE" and add a custom interceptor to return the ID Token. + +```json +{ + "awsAPIPlugin": { + "[YOUR-RESTENDPOINT-NAME]": { + "endpointType": "REST", + "endpoint": "[YOUR-REST-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "NONE" + } + } +} +``` + + +If you perform additional updates to your resources using Amplify CLI, the authorizationType will be reverted back to `AMAZON_COGNITO_USER_POOLS`. Make sure to update this back to `NONE`. + + + +Add a custom interceptor to the API +```swift +try Amplify.configure() +try Amplify.API.add(interceptor: MyCustomInterceptor(), for: "[YOUR-RESTENDPOINT-NAME]") +``` + +Set up the custom interceptor to return the ID token for the request. + +```swift +import Amplify +import AWSPluginsCore + +class MyCustomInterceptor: URLRequestInterceptor { + func latestAuthToken() async throws -> String { + guard let session = try await Amplify.Auth.fetchAuthSession() as? AuthCognitoTokensProvider else { + throw AuthError.unknown("Could not retrieve Cognito token") + } + + let tokens = try session.getCognitoTokens().get() + return tokens.idToken + } + + func intercept(_ request: URLRequest) async throws -> URLRequest { + var request = request + do { + let token = try await latestAuthToken() + request.setValue(token, forHTTPHeaderField: "authorization") + } catch { + throw APIError.operationError("Failed to retrieve Cognito UserPool token.", "", error) + } + return request + } +} +``` + +
+ +## Adding Admin Actions + +To add additional admin actions that are not included by default but are enabled by Amazon Cognito, you will need to update the Lambda function code that is generated for you. The change will include adding a route handler for the action and creating a route for it. You will then associate the route handler to the route within the [Express](https://expressjs.com/) app. + +Below is an example of how to add an admin action that will allow you to [update a user's attributes](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminUpdateUserAttributes.html). +```js +async function updateUserAttributes(username, attributes) { + + const params = { + Username: username, + UserAttributes: attributes, + UserPoolId: 'STRING_VALUE', + }; + + console.log(`Attempting to update ${username} attributes`); + + try { + await cognitoIdentityServiceProvider.adminUpdateUserAttributes(params).promise(); + console.log(`Success updating ${username} attributes`); + return { + message: `Success updating ${username} attributes`, + }; + } catch (err) { + console.log(err); + throw err; + } +} +``` +Once the route handler is defined, you will then add a route with the correct HTTP method to the Express app and associate the route handler to the route. Be sure to make the route unique. + +Below is an example of how you can add a `POST` route named `/updateUserAttributes` and associate the above route handler to it. +```js +app.post('/updateUserAttributes', async (req, res, next) => { + if (!req.body.username || !req.body.attributes) { + const err = new Error('username and attributes are required'); + err.statusCode = 400; + return next(err); + } + + try { + const response = await updateUserAttributes(req.body.username, req.body.attributes); + res.status(200).json(response); + } catch (err) { + next(err); + } +}); +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx new file mode 100644 index 00000000000..c178748008e --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx @@ -0,0 +1,33 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Use existing resources without the CLI', + description: + 'Configure the Amplify Libraries to use existing Amazon Cognito resources by referencing them in your configuration.', + platforms: ['flutter', 'swift', 'android'] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import android0 from '/src/fragments/lib/auth/existing-resources.mdx'; + + + +import ios1 from '/src/fragments/lib/auth/existing-resources.mdx'; + + + +import flutter2 from '/src/fragments/lib/auth/existing-resources.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx new file mode 100644 index 00000000000..ecf4d878240 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx @@ -0,0 +1,111 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Use an existing Cognito User Pool and Identity Pool', + description: 'Configure the Amplify CLI to use existing Amazon Cognito User Pool and Identity Pool resources as an authentication and authorization mechanism for other Amplify categories (API, Storage, and more).', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/auth/import-existing-resources/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +Import existing Amazon Cognito resources into your Amplify project. Get started by running `amplify import auth` command to search for & import an existing Cognito User Pool & Identity Pool in your account. + +```bash +amplify import auth +``` + +The `amplify import auth` command will: + +- automatically populate your Amplify Library configuration files (aws-exports.js, amplifyconfiguration.json) with your chosen Amazon Cognito resource information +- provide your designated existing Cognito resource as the authentication & authorization mechanism for all auth-dependent categories (API, Storage and more) +- enable Lambda functions to access the chosen Cognito resource if you permit it + +Make sure to run `amplify push` to complete the import process and deploy this backend change to the cloud. + +This feature is particularly useful if you're trying to: + +- enable Amplify categories (such as API, Storage, and function) for your existing user base; +- incrementally adopt Amplify for your application stack; +- independently manage Cognito resources while working with Amplify. + +> Note: Amplify does not manage the lifecycle of an imported resource. + +## Import an existing Cognito User Pool + +Select the "Cognito User Pool only" option when you've run `amplify import auth`. In order to successfully import your User Pool, your User Pools require at least one app client with the following conditions: + +- *A "Web app client"*: an app client **without** a client secret + +Run `amplify push` to complete the import procedure. + +import attributesCallout from "/src/fragments/common/writable-vs-mutable-attributes.mdx"; + + + + + +Ensure that the hosted UI for an app client has a sign-out URL defined as omitting this may cause the Amplify CLI to not generate the OAuth `scopes`, `redirectSignIn`, `redirectSignOut` and `responseType` in the `aws-exports.js` file. + +If the Cognito user pool has native and web client defined ensure the clients have matching OAuth properties. + + + +## Import an existing Identity Pool + +Select the "Cognito User Pool and Identity Pool" option when you've run `amplify import auth`. In order to successfully import your Identity Pool, it must have both of the User Pool app clients fulfilling [these requirements](#import-an-existing-cognito-user-pool) associated as an authentication provider. + +Your Identity Pool needs: + +- an Authenticated Role with a trust relationship to your Identity Pool +- an Unauthenticated Role with a trust relationship to your Identity Pool + +These roles are usually automatically configured when you create a new Identity Pool enabling "Unauthenticated" access and have a Cognito User Pool as an authentication provider. + +Amplify CLI will update the policies attached to the roles to ensure Amplify categories function correctly. For example, enabling Storage for authenticated & guest users will add private, protected, public, read and upload permissions for the S3 bucket to the unauthenticated & authenticated role. + +Run `amplify push` to complete the import procedure. + +## Multi-environment support + +When you create a new environment through `amplify env add`, Amplify CLI will assume by default that you're managing your app's Cognito resources outside of an Amplify project. You'll be asked to either import a different Cognito resource or maintain the same Cognito resource for your app's auth category. + +If you want to have Amplify manage your auth resources in a new environment, run `amplify remove auth` to unlink the imported Cognito resource and `amplify add auth` to create new Amplify-managed auth resources in the new environment. + +## Unlink an existing Cognito User Pool or Identity Pool + +In order to unlink your existing Cognito resource run `amplify remove auth`. This will only unlink the Cognito resource referenced from the Amplify project. It will not delete the Cognito resource itself. + +Run `amplify push` to complete the unlink procedure. + +## Add Environmental Variables to Amplify Console Build + +In order to successfully build your application with Amplify Console add the following environmental variables to your build environment: + +|Environment Variable|Description| +|-|-| +|AMPLIFY_USERPOOL_ID|The ID for the Amazon Cognito user pool imported for auth| +|AMPLIFY_WEBCLIENT_ID|The ID for the app client to be used by web applications. The app client must be configured with access to the Amazon Cognito user pool specified by the AMPLIFY_USERPOOL_ID environment variable.| +|AMPLIFY_NATIVECLIENT_ID|The ID for the app client to be used by native applications. The app client must be configured with access to the Amazon Cognito user pool specified by the AMPLIFY_USERPOOL_ID environment variable.| +|AMPLIFY_IDENTITYPOOL_ID|The ID for the Amazon Cognito identity pool| diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx index 0a856800ddf..e76b28341fe 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx @@ -824,3 +824,7 @@ Now that you completed setting up multi-factor authentication you may also want
+ +import flows from '/src/fragments/lib/auth/common/mfa/flows.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx new file mode 100644 index 00000000000..6b6303d0afd --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx @@ -0,0 +1,185 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Override Amplify-generated Cognito resources', + description: "The 'amplify override auth' command generates a developer-configurable 'overrides' TypeScript file that provides Amplify-generated Cognito resources as CDK constructs. For example, developers can set auth settings that are not directly available in the Amplify CLI workflow, such as the number of valid days for a temporary password.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter' + ], + canonicalPath: '/javascript/build-a-backend/auth/override-cognito/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +```bash +amplify override auth +``` + +Run the command above to override Amplify-generated auth resources including Amazon Cognito user pool, identity pool, user pool groups, and more. + +The command creates a new `overrides.ts` file under `amplify/backend/auth//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). + +## Customize Amplify-generated Cognito auth resources + +Apply all the overrides in the `override(...)` function. For example, to update the temporary password validity days for your Cognito user pool: + +```ts +import { AmplifyAuthCognitoStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyAuthCognitoStackTemplate) { + resources.userPool.policies = { // Set the user pool policies + passwordPolicy: { + ...resources.userPool.policies["passwordPolicy"], // Carry over existing settings + temporaryPasswordValidityDays: 3 // Add new setting not provided Amplify's default + } + } +} +``` + +Or add a custom attribute to your Cognito user pool: + + + +Removing or adding an attribute on a Cognito userpool schema including default attributes (e.g. `email`) will cause errors such as +`Invalid AttributeDataType input, consider using the provided AttributeDataType enum` as CloudFormation interprets this as schema change. + + + + + +Custom attributes can not be renamed or deleted after you create them. + + + +```ts +import { AmplifyAuthCognitoStackTemplate } from '@aws-amplify/cli-extensibility-helper' + +export function override(resources: AmplifyAuthCognitoStackTemplate) { + const myCustomAttribute = { + attributeDataType: 'String', + developerOnlyAttribute: false, + mutable: true, + name: 'my_custom_attribute', + required: false, + } + resources.userPool.schema = [ + ...(resources.userPool.schema as any[]), // Carry over existing attributes (example: email) + myCustomAttribute, + ] +} +``` + +You can override the following auth resources that Amplify generates: + +|Amplify-generated resource|Description| +|-|-| +|[customMessageConfirmationBucket](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html)|S3 bucket used for custom message triggers| +|[snsRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|SNS role for sending authentication-related messages| +|[userPool](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html)|The Cognito user pool that enables user sign-up and sign-in| +|[userPoolClientWeb](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)|A Cognito user pool client for web apps| +|[userPoolClient](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)|A Cognito user pool client for mobile apps| +|[identityPool](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypool.html)|A Cognito identity pool to federate identities| +|[identityPoolRoleMap](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypoolroleattachment.html)|Role mapping for authenticated and unauthenticated user roles| +|[lambdaConfigPermissions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html)|Permissions for Lambda function to access Cognito user pool and identity pool | +|[lambdaTriggerPermissions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM policy attached to Cognito Lambda triggers| +|[userPoolClientLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to fetch app client secret from user pool client| +|[userPoolClientRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|IAM Role for Lambda function to fetch app client secret from user pool client| +|[userPoolClientLambdaPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to fetch app client secret from user pool client| +|[userPoolClientLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to fetch app client secret from user pool client| +|[userPoolClientInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to fetch app client secret from user pool client| +|[hostedUICustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable Cognito user pool Hosted UI login| +|[hostedUICustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to enable Cognito user pool Hosted UI login| +|[hostedUICustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to enable Cognito user pool Hosted UI login| +|[hostedUICustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable Cognito user pool Hosted UI login| +|[hostedUIProvidersCustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to configure Hosted UI with 3rd party identity providers| +|[hostedUIProvidersCustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to configure Hosted UI with 3rd party identity provider| +|[hostedUIProvidersCustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to configure Hosted UI with 3rd party identity provider| +|[hostedUIProvidersCustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to configure Hosted UI with 3rd party identity provider| +|[oAuthCustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable OAuth| +|[oAuthCustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for OAuth custom CloudFormation resource| +|[oAuthCustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for OAuth Lambda function| +|[oAuthCustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable OAuth| +|[mfaLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable multi-factor authentication function| +|[mfaLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for multi-factor authentication Lambda function| +|[mfaLambdaPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for multi-factor authentication Lambda function| +|[mfaLambdaInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable multi-factor authentication| +|[mfaLambdaRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|IAM Execution Role for multi-factor authentication Lambda function| +|[openIdLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable OpenID Connect| +|[openIdLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for OpenID Connect Lambda function| +|[openIdLambdaIAMPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable OpenID Connect Lambda function| +|[openIdLambdaInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable OpenID Connect| +|[openIdLambdaRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|Lambda Execution Role for OpenID Connect Lambda function| + +## Customize Amplify-generated Cognito user group resources + +Apply all the overrides in the `override(...)` function. For example to add a path to the lambda execution role that facilitates the user pool group to role mapping: +```ts +import { AmplifyUserPoolGroupStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyUserPoolGroupStackTemplate) { + resources.lambdaExecutionRole.path = "//" // Note: CFN does not allow you to modify the path after creation +} +``` + +You can override the following user pool group resources that Amplify generates: + +|Amplify-generated resource|Description| +|-|-| +|[userPoolGroup](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolgroup.html)|The map of user pool groups| +|[userPoolGroupRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|The map of user pool group roles| +|[roleMapCustomResource](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|A custom CloudFormation resource to map user pool groups to their roles| +|[lambdaExecutionRole](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html)|Lambda execution role for the "user pool group"-to-role mapping function| +|[roleMapLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|The Lambda function that facilitates the user pool group to role mapping| + + +## Customize Amplify-generated Cognito auth resources with social providers + +Apply all the overrides in the `override(...)` function. For example to add social providers to your Cognito user pool: + +```ts +import { AmplifyAuthCognitoStackTemplate } from "@aws-amplify/cli-extensibility-helper"; + +export function override(resources: AmplifyAuthCognitoStackTemplate) { + resources.addCfnResource( + { + type: "AWS::Cognito::UserPoolIdentityProvider", + properties: { + AttributeMapping: { + preferred_username: "email", + email: "email" + }, + ProviderDetails: { + client_id: "test", + client_secret: "test", + authorize_scopes: "test", + }, + ProviderName: "LoginWithAmazon", + ProviderType: "LoginWithAmazon", + UserPoolId: { + Ref: "UserPool", + }, + }, + }, + "amazon-social-provider" + ); +} +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx new file mode 100644 index 00000000000..a8cfbbb90d4 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx @@ -0,0 +1,117 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Set up user group management', + description: 'Create logical groups in Cognito User Pools and assign permissions to access resources in Amplify categories with the Amplify CLI.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/auth/user-group-management/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +You can create logical groups in Cognito User Pools and assign permissions to access resources in Amplify categories with the CLI, as well as define the relative precedence of one group to another. This can be useful for defining which users should be part of "Admins" vs "Editors", and if the users in a Group should be able to just write or write & read to a resource (AppSync, API Gateway, S3 bucket, etc). [You can also use these with `@auth` Static Groups in the GraphQL Transformer](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules#user-group-based-data-access). Precedence helps remove any ambiguity on permissions if a user is in multiple Groups. + +## Create user groups + +```bash +amplify add auth +``` + +```console +❯ Manual configuration + +Do you want to add User Pool Groups? (Use arrow keys) +❯ Yes + +? Provide a name for your user pool group: Admins +? Do you want to add another User Pool Group Yes +? Provide a name for your user pool group: Editors +? Do you want to add another User Pool Group No +? Sort the user pool groups in order of preference … (Use + to change the order) + Admins + Editors +``` + +When asked as in the example above, you can press `Shift` on your keyboard along with the **LEFT** and **RIGHT** arrows to move a Group higher or lower in precedence. Once complete you can open `amplify/backend/auth/userPoolGroups/user-pool-group-precedence.json` to manually set the precedence. + +## Group access controls + +For certain Amplify categories you can restrict access with CRUD (Create, Read, Update, and Delete) permissions, setting different access controls for authenticated users vs Guests (e.g. Authenticated users can read & write to S3 buckets while Guests can only read). You can further restrict this to apply different permissions conditionally depending on if a logged-in user is part of a specific User Pool Group. + +```bash +amplify add storage # Select content +``` + +```console +? Restrict access by? (Use arrow keys) + Auth/Guest Users + Individual Groups +❯ Both + Learn more + +Who should have access? +❯ Auth and guest users + +What kind of access do you want for Authenticated users? +❯ create/update, read + +What kind of access do you want for Guest users? +❯ read + +Select groups: +❯ Admins + +What kind of access do you want for Admins users? +❯ create/update, read, delete +``` + +The above example uses a combination of permissions where users in the "Admins" Group have full access, "Guest" users can only read, and "Authenticated" users who are not a part of any group have create, update, and read access. Amplify will configure the corresponding IAM policy on your behalf. Advanced users can additionally set permissions by adding a `customPolicies` key to `amplify/backend/auth/userPoolGroups/user-pool-group-precedence.json` with custom IAM policy for a Group. This will attach an inline policy on the IAM role associated to this Group during deployment. **Note** this is an advanced feature and only suitable if you have an understanding of AWS resources. For instance perhaps you wanted users in the "Admins" group to have the ability to Create an S3 bucket: + +```json +[ + { + "groupName": "Admins", + "precedence": 1, + "customPolicies": [ + { + "PolicyName": "admin-group-policy", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "statement1", + "Effect": "Allow", + "Action": ["s3:CreateBucket"], + "Resource": ["arn:aws:s3:::*"] + } + ] + } + } + ] + }, + { + "groupName": "Editors", + "precedence": 2 + } +] +``` From 0e453edca870e598ad19206c6cfc53f0cf9c616a Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 6 May 2024 16:47:41 -0700 Subject: [PATCH 22/88] chore: fixed flutter v1 gen1 docs --- src/directory/directory.mjs | 47 + .../lib-v1/graphqlapi/flutter/mutate-data.mdx | 29 +- .../advanced-workflows/common.mdx | 28 + .../native_common/getting-started/common.mdx | 4 + .../batch-put-custom-resolver/index.mdx | 155 ++ .../graphqlapi/best-practice/index.mdx | 40 + .../query-with-sorting/index.mdx | 136 ++ .../warehouse-management/index.mdx | 556 ++++++++ .../client-code-generation/index.mdx | 245 ++++ .../index.mdx | 1018 +++++++++++++ .../index.mdx | 266 ++++ .../custom-business-logic/index.mdx | 958 +++++++++++++ .../customize-authorization-modes/index.mdx | 53 + .../customize-authorization-rules/index.mdx | 937 ++++++++++++ .../graphqlapi/data-modeling/index.mdx | 1266 +++++++++++++++++ .../index.mdx | 504 +++++++ .../graphqlapi/schema-evolution/index.mdx | 145 ++ .../search-and-result-aggregations/index.mdx | 483 +++++++ .../graphqlapi/troubleshooting/index.mdx | 96 ++ 19 files changed, 6964 insertions(+), 2 deletions(-) create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 147afc99163..f6073310f69 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -2251,12 +2251,24 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/index.mdx' }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/api-graphql-concepts/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx' }, @@ -2269,6 +2281,12 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/working-with-files/index.mdx' }, @@ -2281,14 +2299,43 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/offline/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx' }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/existing-resources/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/upgrade-guide/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx', + children: [ + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx' + } + ] } ] }, diff --git a/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx b/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx index 3472dce68f8..fed67caca55 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx @@ -1,11 +1,23 @@ -## Run a mutation +In this guide, you will learn how to create, update, and delete your data using Amplify Libraries' GraphQL client. -Now that the client is set up, you can run a GraphQL mutation with `Amplify.API.mutate` to create, update, and delete your data. +Before you begin, you will need: + +- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/) + +## Run mutations to create, update, and delete application data + +In GraphQL, mutations are APIs that are used to create, update, or delete data. This is different than queries, which are used to read the data but not change it. The following examples demonstrate how you can create, update, and delete items using the Amplify GraphQL client. + +### Create an item + +You can run a GraphQL mutation with `Amplify.API.mutate` to create an item. import createTodo from "/src/fragments/lib/graphqlapi/flutter/getting-started/50_createtodo.mdx"; +### Update an item + To update the `Todo` with a new name: ```dart @@ -18,6 +30,8 @@ Future updateTodo(Todo originalTodo) async { } ``` +### Delete an item + To delete the `Todo`: ```dart @@ -28,6 +42,7 @@ Future deleteTodo(Todo todoToDelete) async { } ``` +Or you can delete by ID, which is ideal if you do not have the instance in memory yet: ```dart // or delete by ID, ideal if you do not have the instance in memory, yet @@ -41,3 +56,13 @@ Future deleteTodoById(Todo todoToDelete) async { } ``` +## Conclusion + +Congratulations! You have finished the **Create, update, and delete application data** guide. In this guide, you created, updated, and deleted your app data through the GraphQL API. + +## Next steps + +Our recommended next steps include using the API to query data and subscribe to real-time events to look for mutations in your data. Some resources that will help with this work include: + +- [Read application data](/gen1/[platform]/build-a-backend/graphqlapi/query-data/) +- [Subscribe to real-time events](/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/) diff --git a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx index 04102c0d678..632403335d4 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx @@ -138,9 +138,21 @@ import flutter5 from '/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflow + ## Combining Multiple Operations + + +## Combining multiple GraphQL operations in a single request + + + When you want to perform more than one operation in a single request, you can place them within the same document. For example, to retrieve a Post and a Todo + + + +GraphQL allows you to run multiple GraphQL operations (queries/mutations) as part of a single network request from the client code. To perform multiple operations in a single request, you can place them within the same GraphQL document. For example, to retrieve a Post and a Todo: + import ios6 from '/src/fragments/lib-v1/graphqlapi/ios/advanced-workflows/40_multiple.mdx'; @@ -154,10 +166,26 @@ import flutter7 from '/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflow + + + +Combining multiple GraphQL requests on the client-side is different than server-side transaction support. To run multiple transactions as a batch operation refer to the [Batch Put Custom Resolver](/gen1/[platform]/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/) example. + + + + ## Adding Headers to Outgoing Requests By default, the API plugin includes appropriate authorization headers on your outgoing requests. However, you may have an advanced use case where you wish to send additional request headers to AppSync. + + + +If your API does not require any authorization or if you would like manipulate the request yourself, please refer to the [Set authorization mode to NONE](/gen1/[platform]/build-a-backend/graphqlapi/customize-authz-modes/#none) + + + + import ios8 from '/src/fragments/lib-v1/graphqlapi/ios/advanced-workflows/50_interceptor.mdx'; diff --git a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx index ea06a51a502..825344b080e 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx @@ -83,6 +83,10 @@ import android8 from '/src/fragments/lib-v1/graphqlapi/android/getting-started/4 +import flutter18 from '/src/fragments/lib-v1/graphqlapi/flutter/getting-started/40_codegen.mdx'; + + + ## Install Amplify Libraries import ios9 from '/src/fragments/lib-v1/graphqlapi/ios/getting-started/20_installLib.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx new file mode 100644 index 00000000000..f5cffd36ab9 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx @@ -0,0 +1,155 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Batch put custom resolver', + description: 'Leverage GraphQL mutations to efficiently create multiple objects in one request rather than making sequential requests to create each object individually.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +Sometimes you need to create objects in bulk, rather than creating individual objects sequentially and waiting for all the requests to complete. + +1. Define your schema with a custom mutation. The custom mutation should not be deployed to AppSync beforehand if following these steps, the CLI will attach its own resolver preventing you from attaching a custom resource this way. +```graphql +type Todo @model { + id: ID! + name: String! + description: String +} + +type Mutation { + batchCreateTodo(todos: [BatchCreateTodo]): [Todo] +} + +input BatchCreateTodo { + id: ID + name: String! + description: String +} +``` + +2. [Create a custom resource for your resolver](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) and use the following code snippets as a guide to get started + +2. Follow the steps for creating a custom resolver: +```bash +amplify add custom +``` +```console +? How do you want to define this custom resource? +❯ AWS CDK +? Provide a name for your custom resource +❯ MyCustomResolvers +``` + +Next, install the AppSync dependencies for your custom resource: +```bash +cd amplify/backend/custom/MyCustomResolvers +npm i @aws-cdk/aws-appsync@~1.124.0 +``` + +Use the following template as a starting point for your custom CDK stack, the resolvers must be templated with environment references + +```ts +import * as cdk from '@aws-cdk/core'; +import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; +import * as appsync from '@aws-cdk/aws-appsync'; +import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; + +export class cdkStack extends cdk.Stack { + constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) { + super(scope, id, props); + /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ + new cdk.CfnParameter(this, 'env', { + type: 'String', + description: 'Current Amplify CLI env name', + }); + + // Access other Amplify Resources + const retVal:AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this, + amplifyResourceProps.category, + amplifyResourceProps.resourceName, + [{ + category: "api", + resourceName: "" + }] + ); + + const requestVTL = ` + ## [Start] Initialization default values. ** + $util.qr($ctx.stash.put("defaultValues", $util.defaultIfNull($ctx.stash.defaultValues, {}))) + #set( $createdAt = $util.time.nowISO8601() ) + #set($todosArray = []) + #foreach($item in \${ctx.args.todos}) + $util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId()))) + $util.qr($item.put("createdAt", $util.defaultIfNull($item.createdAt, $createdAt))) + $util.qr($item.put("updatedAt", $util.defaultIfNull($item.updatedAt, $createdAt))) + $util.qr($item.put("__typename", "Todo")) + $util.qr($todosArray.add($util.dynamodb.toMapValues($item))) + #end + ## [End] Initialization default values. ** + $util.toJson( { + "version": "2018-05-29", + "operation": "BatchPutItem", + "tables": { + "-${apiIdRef}-${envRef}": $todosArray + } + } ) + ` + const responseVTL = ` + ## [Start] ResponseTemplate. ** + #if( $ctx.error ) + $util.error($ctx.error.message, $ctx.error.type) + #else + $util.toJson($ctx.result.data.-${apiIdRef}-${envRef}) + #end + ## [End] ResponseTemplate. ** + `; + + + const resolver = new appsync.CfnResolver(this, "custom-resolver", { + // apiId: retVal.api.new.GraphQLAPIIdOutput, + // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 + // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. + // Previously the ApiId is the variable Name which is wrong , it should be variable value as below + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + fieldName: "querySomething", + typeName: "Query", // Query | Mutation | Subscription + requestMappingTemplate: requestVTL, + responseMappingTemplate: responseVTL, + dataSourceName: "TodoTable" // DataSource name + }) + } +} +``` + +By using CloudFormation parameters, you contextualize your custom resolvers to the environment you're working with. + +3. Run `amplify push` and deploy your API + +The full documentation for custom resolvers [is available here](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx new file mode 100644 index 00000000000..b4b3efda726 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx @@ -0,0 +1,40 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; +import { getChildPageNodes } from '@/utils/getChildPageNodes'; + +export const meta = { + title: 'Best practice', + description: 'Best practices and examples for working with GraphQL.', + platforms: [ + 'flutter', + ], + route: '/[platform]/build-a-backend/graphqlapi/best-practice', + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + const childPageNodes = getChildPageNodes(meta.route); + return { + props: { + platform: context.params.platform, + meta, + childPageNodes + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx new file mode 100644 index 00000000000..188ef04376a --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx @@ -0,0 +1,136 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'GraphQL query with sorting by date', + description: 'How to implement sorting in a GraphQL query', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/query-with-sorting/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +In this guide you will learn how to implement sorting in a GraphQL API. In our example, you will implement sorting results by date in either an ascending or descending order by implementing an additional data access pattern leveraging a DynamoDB Global Secondary Index using the `@index` GraphQL Transformer directive. + +### Overview + +To get started, let's start with a basic GraphQL schema for a Todo app: + +```graphql +type Todo @model { + id: ID! + title: String! +} +``` + +When the API is created with an `@model` directive, the following queries will automatically be created for you: + +```graphql +type Query { + getTodo(id: ID!): Todo + listTodos( + filter: ModelTodoFilterInput + limit: Int + nextToken: String + ): ModelTodoConnection +} +``` + +Next, take a look at the `ModelTodoConnection` type to get an idea of the data that will be returned when the `listTodos` query is run: + +```graphql +type ModelTodoConnection { + items: [Todo] + nextToken: String +} +``` + +By default, the `listTodos` query will return the `items` array **unordered**. Many times you will need these items to be ordered by title, by creation date, or in some other way. + +To enable this, you can use the [@index](/[platform]/build-a-backend/graphqlapi/data-modeling/) directive. This directive will allow you to set a custom `sortKey` on any field in your API. + +### Implementation + +In this example, you will enable sorting by the `createdAt` field. By default, Amplify will populate this `createdAt` field with a timestamp if none is passed in. + +To enable this, update your schema with the following: + +```graphql +type Todo @model { + id: ID! + title: String! + type: String! + @index( + name: "todosByDate" + queryField: "todosByDate" + sortKeyFields: ["createdAt"] + ) + createdAt: String! +} +``` + + + +When created a Todo, you must now populate the `type` field for this to work properly. + + + +Next, create a few todos being sure to populate the `type` field: + +```graphql +mutation createTodo { + createTodo(input: { title: "Todo 1", type: "Todo" }) { + id + title + } +} +``` + +Now, you can query for todos by date in an ascending or descending order using the new `todosByDate` query: + +```graphql +query todosByDate { + todosByDate(type: "Todo", sortDirection: ASC) { + items { + id + title + createdAt + } + } +} + +query todosByDateDescending { + todosByDate(type: "Todo", sortDirection: DESC) { + items { + id + title + createdAt + } + } +} +``` + +To learn more about the `@index` directive, check out the documentation [here](/[platform]/build-a-backend/graphqlapi/data-modeling/) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx new file mode 100644 index 00000000000..d931dfe4b94 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx @@ -0,0 +1,556 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Warehouse Management System', + description: 'Configure common access patters for your app following a warehouse management system example.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/warehouse-management/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +In this "Warehouse management system" example, you will learn how to configure common access patterns for your app. This example has the following types: + +- Warehouse +- Product +- Inventory +- Employee +- AccountRepresentative +- Customer + +These types have the following common access patterns: + +1. [Look up employee details by employee ID](#1-look-up-employee-details-by-employee-id) +2. [Query employee details by employee name](#2-query-employee-details-by-employee-name) +3. [Find an employee's phone number(s)](#3-find-an-employees-phone-number) +4. [Find a customer's phone number(s)](#4-find-a-customers-phone-number) +5. [Get orders for a given customer within a given date range](#5-get-orders-for-a-given-customer-within-a-given-date-range) +6. [Show all open orders within a given date range across all customers](#6-show-all-open-orders-within-a-given-date-range-across-all-customers) +7. [See all employees recently hired](#7-see-all-employees-hired-recently) +8. [Find all employees working in a given warehouse](#8-find-all-employees-working-in-a-given-warehouse) +9. [Get all items on order for a given product](#9-get-all-items-on-order-for-a-given-product) +10. [Get current inventories for a given product at all warehouses](#10-get-current-inventories-for-a-product-at-all-warehouses) +11. [Get customers by account representative](#11-get-customers-by-account-representative) +12. [Get orders by account representative and date](#12-get-orders-by-account-representative-and-date) +13. [Get all items on order for a given product](#13-get-all-items-on-order-for-a-given-product) +14. [Get all employees with a given job title](#14-get-all-employees-with-a-given-job-title) +15. [Get inventory by product and warehouse](#15-get-inventory-by-product-by-warehouse) +16. [Get total product inventory](#16-get-total-product-inventory) +17. [Get account representatives ranked by order total and sales period](#17-get-sales-representatives-ranked-by-order-total-and-sales-period) + +The following schema introduces the required indexes and relationships so that you can support these access patterns: + +```graphql +# This "input" configures a global authorization rule to enable public access to +# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/auth +input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY! + +type Order @model { + id: ID! + customerID: ID! @index(name: "byCustomerByStatusByDate", sortKeyFields: ["status", "date"]) @index(name: "byCustomerByDate", sortKeyFields: ["date"]) + accountRepresentativeID: ID! @index(name: "byRepresentativebyDate", sortKeyFields: ["date"]) + productID: ID! @index(name: "byProduct", sortKeyFields: ["id"]) + status: String! + amount: Int! + date: String! +} + +type Customer @model { + id: ID! + name: String! + phoneNumber: String + accountRepresentativeID: ID! @index(name: "byRepresentative", sortKeyFields: ["id"]) + ordersByDate: [Order] @hasMany(indexName: "byCustomerByDate", fields: ["id"]) + ordersByStatusDate: [Order] @hasMany(indexName: "byCustomerByStatusByDate", fields: ["id"]) +} + +type Employee @model { + id: ID! + name: String! @index(name: "byName", queryField: "employeeByName", sortKeyFields: ["id"]) + startDate: String! + phoneNumber: String! + warehouseID: ID! @index(name: "byWarehouse", sortKeyFields: ["id"]) + jobTitle: String! @index(name: "byTitle", queryField: "employeesByJobTitle", sortKeyFields: ["id"]) + newHire: String! @index(name: "newHire", queryField: "employeesNewHire", sortKeyFields: ["id"]) @index(name: "newHireByStartDate", queryField: "employeesNewHireByStartDate", sortKeyFields: ["startDate"]) +} + +type Warehouse @model { + id: ID! + employees: [Employee] @hasMany(indexName: "byWarehouse", fields: ["id"]) +} + +type AccountRepresentative @model { + id: ID! + customers: [Customer] @hasMany(indexName: "byRepresentative", fields: ["id"]) + orders: [Order] @hasMany(indexName: "byRepresentativebyDate", fields: ["id"]) + orderTotal: Int + salesPeriod: String @index(name: "bySalesPeriodByOrderTotal", queryField: "repsByPeriodAndTotal", sortKeyFields: ["orderTotal"]) +} + +type Inventory @model { + productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) + warehouseID: ID! @index(name: "byWarehouseID", queryField: "itemsByWarehouseID") + inventoryAmount: Int! +} + +type Product @model { + id: ID! + name: String! + orders: [Order] @hasMany(indexName: "byProduct", fields: ["id"]) + inventories: [Inventory] @hasMany(fields: ["id"]) +} +``` + +Now that you have the schema created, let's create the items in the database that you will be operating against: + +```graphql +# first +mutation createWarehouse { + createWarehouse(input: {id: "1"}) { + id + } +} + +# second +mutation createEmployee { + createEmployee(input: { + id: "amanda" + name: "Amanda", + startDate: "2018-05-22", + phoneNumber: "6015555555", + warehouseID: "1", + jobTitle: "Manager", + newHire: "true"} + ) { + id + jobTitle + name + newHire + phoneNumber + startDate + warehouseID + } +} + +# third +mutation createAccountRepresentative { + createAccountRepresentative(input: { + id: "dabit" + orderTotal: 400000 + salesPeriod: "January 2019" + }) { + id + orderTotal + salesPeriod + } +} + +# fourth +mutation createCustomer { + createCustomer(input: { + id: "jennifer_thomas" + accountRepresentativeID: "dabit" + name: "Jennifer Thomas" + phoneNumber: "+16015555555" + }) { + id + name + accountRepresentativeID + phoneNumber + } +} + +# fifth +mutation createProduct { + createProduct(input: { + id: "yeezyboost" + name: "Yeezy Boost" + }) { + id + name + } +} + +# sixth +mutation createInventory { + createInventory(input: { + productID: "yeezyboost" + warehouseID: "1" + inventoryAmount: 300 + }) { + productID + inventoryAmount + warehouseID + } +} + +# seventh +mutation createOrder { + createOrder(input: { + amount: 300 + date: "2018-07-12" + status: "pending" + accountRepresentativeID: "dabit" + customerID: "jennifer_thomas" + productID: "yeezyboost" + }) { + id + customerID + accountRepresentativeID + amount + date + customerID + productID + } +} +``` + +### 1. Look up employee details by employee ID + +This can simply be done by querying the employee model with an employee ID, no `@primaryKey` or `@index` need to be explicitly specified to make this work. + +```graphql +query getEmployee($id: ID!) { + getEmployee(id: $id) { + id + name + phoneNumber + startDate + jobTitle + } +} +``` + +### 2. Query employee details by employee name + +The `@index` `byName` on the `Employee` type makes this access-pattern feasible because under the hood an index is created and a query is used to match against the name field. You can use this query: + +```graphql +query employeeByName($name: String!) { + employeeByName(name: $name) { + items { + id + name + phoneNumber + startDate + jobTitle + } + } +} +``` + +### 3. Find an Employee’s phone number + +Either one of the previous queries would work to find an employee’s phone number as long as one has their ID or name. + +### 4. Find a customer’s phone number + +A similar query to those given above but on the Customer model would give you a customer’s phone number. + +```graphql +query getCustomer($customerID: ID!) { + getCustomer(id: $customerID) { + phoneNumber + } +} +``` + +### 5. Get orders for a given customer within a given date range + +There is a one-to-many relation that lets all the orders of a customer be queried. + +This relationship is created by having the `@index` name `byCustomerByDate` on the Order model that is queried by the `@hasMany` relationship on the orders field of the Customer model. + +A sort key with the date is used. What this means is that the GraphQL resolver can use predicates like `Between` to efficiently search the date range rather than scanning all records in the database and then filtering them out. + +The query one would need to get the orders to a customer within a date range would be: + +```graphql +query getCustomerWithOrdersByDate($customerID: ID!) { + getCustomer(id: $customerID) { + ordersByDate(date: { + between: [ "2018-01-22", "2020-10-11" ] + }) { + items { + id + amount + productID + } + } + } +} +``` + +### 6. Show all open orders within a given date range across all customers + +The `@index` `byCustomerByStatusByDate` enables you to run a query that would work for this access pattern. + +In this example, a composite sort key (combination of two or more keys) with the `status` and `date` is used. What this means is that the unique identifier of a record in the database is created by concatenating these two fields (status and date) together, and then the GraphQL resolver can use predicates like `between` or `contains` to efficiently search the unique identifier for matches rather than scanning all records in the database and then filtering them out. + +```graphql +query listCustomersWithOrdersByStatusDate { + listCustomers { + items { + ordersByStatusDate(statusDate: { + between: [ + { status: "pending", date: "2018-01-22" }, + { status: "pending", date: "2020-10-11" } + ]}) { + items { + id + amount + date + } + } + } + } +} +``` + +### 7. See all employees hired recently + +Having `@index(name: "newHire", fields: ["newHire", "id"])` on the `Employee` model allows one to query by whether an employee has been hired recently. + +```graphql +query employeesNewHire { + employeesNewHire(newHire: "true") { + items { + id + name + phoneNumber + startDate + jobTitle + } + } +} +``` + +You can also query and have the results returned by start date by using the `employeesNewHireByStartDate` query: + +```graphql +query employeesNewHireByDate { + employeesNewHireByStartDate(newHire: "true") { + items { + id + name + phoneNumber + startDate + jobTitle + } + } +} +``` + +### 8. Find all employees working in a given warehouse + +This needs a one to many relationship from warehouses to employees. As can be seen from the `@hasMany` relationship in the `Warehouse` model, this relationship uses the `byWarehouse` index on the `Employee` model. The relevant query would look like this: + +```graphql +query getWarehouse($warehouseID: ID!) { + getWarehouse(id: $warehouseID) { + id + employees{ + items { + id + name + startDate + phoneNumber + jobTitle + } + } + } +} +``` + +### 9. Get all items on order for a given product + +This access-pattern would use a one-to-many relation from products to orders. With this query you can get all orders of a given product: + +```graphql +query getProductOrders($productID: ID!) { + getProduct(id: $productID) { + id + orders { + items { + id + status + amount + date + } + } + } +} +``` + +### 10. Get current inventories for a product at all warehouses + +The query needed to get the inventories of a product in all warehouses would be: + +```graphql +query getProductInventoryInfo($productID: ID!) { + getProduct(id: $productID) { + id + inventories { + items { + warehouseID + inventoryAmount + } + } + } +} +``` + +### 11. Get customers by account representative + +This uses a has-many relationship between account representatives and customers: + +The query needed would look like this: + +```graphql +query getCustomersForAccountRepresentative($representativeId: ID!) { + getAccountRepresentative(id: $representativeId) { + customers { + items { + id + name + phoneNumber + } + } + } +} +``` + +### 12. Get orders by account representative and date + +As can be seen in the AccountRepresentative model this relationship uses the `byRepresentativebyDate` field on the `Order` model to create the connection needed. The query needed would look like this: + +```graphql +query getOrdersForAccountRepresentative($representativeId: ID!) { + getAccountRepresentative(id: $representativeId) { + id + orders(date: { + between: [ + "2010-01-22", "2020-10-11" + ] + }) { + items { + id + status + amount + date + } + } + } +} +``` + +### 13. Get all items on order for a given product + +This is the same as number 9. + +### 14. Get all employees with a given job title + +Using the `byTitle` `@index` makes this access pattern quite easy. + +```graphql +query employeesByJobTitle { + employeesByJobTitle(jobTitle: "Manager") { + items { + id + name + phoneNumber + jobTitle + } + } +} +``` + +### 15. Get inventory by product by warehouse + +Here having the inventories be held in a separate model is particularly useful since this model can have its own partition key and sort key such that the inventories themselves can be queried as is needed for this access-pattern. + +A query on this model would look like this: + +```graphql +query inventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { + getInventory(productID: $productID, warehouseID: $warehouseID) { + productID + warehouseID + inventoryAmount + } +} + +``` + +You can also get all inventory from an individual warehouse by using the `itemsByWarehouseID` query created by the `byWarehouseID` key: + +```graphql +query byWarehouseId($warehouseID: ID!) { + itemsByWarehouseID(warehouseID: $warehouseID) { + items { + inventoryAmount + productID + } + } +} +``` + +### 16. Get total product inventory + +How this would be done depends on the use case. If one just wants a list of all inventories in all warehouses, one could just run a list inventories on the Inventory model: + +```graphql +query listInventorys { + listInventorys { + items { + productID + warehouseID + inventoryAmount + } + } +} +``` + +### 17. Get sales representatives ranked by order total and sales period + +The sales period is either a date range or maybe even a month or week. Therefore you can set the sales period as a string and query using the combination of `salesPeriod` and `orderTotal`. You can also set the `sortDirection` in order to get the return values from largest to smallest: + +```graphql +query repsByPeriodAndTotal { + repsByPeriodAndTotal( + sortDirection: DESC, + salesPeriod: "January 2019", + orderTotal: { + ge: 1000 + }) { + items { + id + orderTotal + } + } +} +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx new file mode 100644 index 00000000000..0de45608999 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx @@ -0,0 +1,245 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'JavaScript, Android, Swift, and Flutter client code generation', + description: "Amplify's codegen capabilities generate native code for iOS and Android, as well as types for Flow and TypeScript. Codegen can also generate GraphQL statements (queries, mutations, and subscriptions).", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/client-code-generation/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + + +"Codegen" generates native code for Swift (iOS), Java (Android), and JavaScript that represent your GraphQL API's data models. It can also generate GraphQL statements (queries, mutations, and subscriptions) so that you don't have to hand code them. + +The design of codegen functionality provides mechanisms to run at different points in your app development lifecycle, including when you create or update an API as well as independently when you want to just update the data fetching requirements of your app but leave your API alone. It additionally allows you to work in a team where the schema is updated or managed by another person. Finally, you can also include the codegen in your build process so that it runs automatically (such as from in Xcode). + +## Generate GraphQL client helper code for GraphQL APIs deployed with Amplify GraphQL CDK construct + +The necessary GraphQL client helper code differ from platform to platform. For JavaScript GraphQL client code, you need to reference the API ID that you receive after you deploy your application. For Android, iOS, and Flutter you can reference the local GraphQL schema to generate models for your API client. + +### JavaScript / TypeScript GraphQL API client helper code + +Go to your frontend app's root directory and run the following command in the Terminal: + +```bash +npx @aws-amplify/cli codegen add --apiId <...> --region <...> +``` + +This will download your API's schema and by default generate client helper code into the `src/graphql` folder. After every API deployment, you can rerun the following command to generate updated GraphQL statement and types: + +``` +npx @aws-amplify/cli codegen +``` + +### Generate "models" for Android, Swift, Flutter, and JavaScript DataStore + +The Android, Swift, Flutter, and DataStore on JavaScript use the "modelgen" pattern to interact with the client library. To generate models, run the following command from your frontend application's root directory: + +```bash +npx @aws-amplify/cli codegen models \ + --model-schema \ + --target [android|ios|flutter|javascript|typescript] \ + --output-dir ./ +``` + +## Generate GraphQL Client code with Amplify CLI-deployed GraphQL API + +### Create API then automatically generate code + +```bash +amplify init +amplify add api (select GraphQL) +amplify push +``` + +You’ll see questions as before, but now it will also automatically ask you if you want to generate GraphQL statements and do codegen. It will also respect the `./app/src/main` directory for Android projects. After the AppSync deployment finishes the Swift file will be automatically generated (Android you’ll need to kick off a [Gradle Build step](#androiduse)) and you can begin using in your app immediately. + +When you deploy your GraphQL API to the cloud, you are prompted to configure codegen. When a project is configured to generate code with codegen, it stores all the configuration `.graphqlconfig.yml` file in the root folder of your project. To make changes to the configuration, use `amplify configure codegen`. + +### Modify GraphQL schema, push, then automatically generate code + +During development, you might wish to update your GraphQL schema and generated code as part of an iterative dev/test cycle. Modify & save your schema in `amplify/backend/api//schema.graphql` then run: + +```bash +amplify push +``` + +Each time you will be prompted to update the code in your API and also ask you if you want to run codegen again as well, including regeneration of the GraphQL statements from the new schema. + +### No API changes, just update GraphQL statements & generate code + +One of the benefits of GraphQL is the client can define it's data fetching requirements independently of the API. Amplify codegen supports this by allowing you to modify the selection set (e.g. add/remove fields inside the curly braces) for the GraphQL statements and running type generation again. This gives you fine-grained control over the network requests that your application is making. Modify your GraphQL statements (default in the `./graphql` folder unless you changed it) then save the files and run: + +```bash +amplify codegen types +``` + +A new updated Swift file will be created (or run Gradle Build on Android for the same). You can then use the updates in your application code. + +## Shared schema, modified elsewhere (e.g. console or team workflows) + +Suppose you are working in a team and the schema is updated either from the AWS AppSync console or on another system. Your types are now out of date because your GraphQL statement was generated off an outdated schema. The easiest way to resolve this is to regenerate your GraphQL statements, update them if necessary, and then generate your types again. Modify the schema in the console or on a separate system, then run: + +```bash +amplify codegen statements +amplify codegen types +``` + +You should have newly generated GraphQL statements and Swift code that matches the schema updates. If you ran the second command your types will be updated as well. Alternatively, if you run `amplify codegen` alone it will perform both of these actions. + +## Introspection Schema outside of an initialized project + +If you would like to generate statements and types without initializing an amplify project, you can do so by providing your introspection schema named `schema.json` in your project directory and adding codegen from the same directory. To download your introspection schema from an AppSync api, in the AppSync console go to the schema editor and under "Export schema" choose `schema.json`. + +```bash +amplify add codegen +``` + +Once codegen has been added you can update your introspection schema, then generate statements and types again without re-entering your project information. + +```bash +amplify codegen +``` + +You can update your project and codegen configuration if required. + +```bash +amplify configure codegen +amplify codegen +``` + +When generating types, codegen uses GraphQL statements as input. It will generate only the types that are being used in the GraphQL statements. + +## Codegen commands + +### amplify add codegen + +```bash +amplify add codegen +``` + +The `amplify add codegen` allows you to add AppSync API created using the AWS console. If you have your API is in a different region then that of your current region, the command asks you to choose the region. If you are adding codegen outside of an initialized amplify project, provide your introspection schema named `schema.json` in the same directory that you make the add codegen call from. **Note**: If you use the --apiId flag to add an externally created AppSync API, such as one created in the AWS console, you will not be able to manage this API from the Amplify CLI with commands such as amplify api update when performing schema updates. You cannot add an external AppSync API when outside of an initialized project. + +### amplify configure codegen + +```bash +amplify configure codegen +``` + +The `amplify configure codegen` command allows you to update the codegen configuration after it is added to your project. When outside of an initialized project, you can use this to update your project configuration as well as the codegen configuration. + +### amplify codegen statements + +```bash +amplify codegen statements [--nodownload] [--maxDepth ] +``` + +The `amplify codegen statements` command generates GraphQL statements(queries, mutation and subscription) based on your GraphQL schema. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. + +### amplify codegen types + +```bash +amplify codegen types +``` + +The `amplify codegen types [--nodownload]` command generates GraphQL `types` for Flow and typescript and Swift class in an iOS project. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. + +### amplify codegen + +```bash +amplify codegen [--maxDepth ] +``` + +The `amplify codegen [--nodownload]` generates GraphQL `statements` and `types`. This command downloads introspection schema every time it is run but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. If you are running codegen outside of an initialized amplify project, the introspection schema named `schema.json` must be in the same directory that you run amplify codegen from. This command will not download the introspection schema when outside of an amplify project - it will only use the introspection schema provided. + +## Statement depth + +In the below schema there are connections between `Comment` -> `Post` -> `Blog` -> `Post` -> `Comments`. When generating statements codegen has a default limit of 2 for depth traversal. But if you need to go deeper than 2 levels you can change the `maxDepth` parameter either when setting up your codegen or by passing `--maxDepth` parameter to `codegen` + +```graphql +type Blog @model { + id: ID! + name: String! + posts: [Post] @hasMany +} +type Post @model { + id: ID! + title: String! + blog: Blog @belongsTo + comments: [Comment] @hasMany +} +type Comment @model { + id: ID! + content: String + post: Post @belongsTo +} +``` + +```graphql +query GetComment($id: ID!) { + getComment(id: $id) { + # depth level 1 + id + content + post { + # depth level 2 + id + title + blog { + # depth level 3 + id + name + posts { + # depth level 4 + items { + # depth level 5 + id + title + } + nextToken + } + } + comments { + # depth level 3 + items { + # depth level 4 + id + content + post { + # depth level 5 + id + title + } + } + nextToken + } + } + } +} +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx new file mode 100644 index 00000000000..c1568912191 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx @@ -0,0 +1,1018 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Connect API to existing MySQL or PostgreSQL database', + description: 'Learn how to connect your API to an existing MySQL or PostgreSQL database.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/connect-api-to-existing-database/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + + +The following content requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + +In this section, you'll learn how to: + +- Connect Amplify GraphQL API to an existing MySQL or PostgreSQL database +- Execute SQL statements with custom GraphQL queries and mutations using the new `@sql` directive +- Generate create, read, update, and delete API operations based on your SQL database schema + +## Connect your API with an existing MySQL or PostgreSQL database + + + + +Pre-requisites: + +- Have an existing [MySQL database](https://aws.amazon.com/getting-started/hands-on/create-mysql-db/) or [PostgreSQL database](https://aws.amazon.com/getting-started/hands-on/create-connect-postgresql-db/) deployed +- The [AWS CDK CLI is installed](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install) +- Have an [AWS CDK application initialized](https://docs.aws.amazon.com/cdk/v2/guide/hello_world.html) + + + +This feature is not yet available in the Asia Pacific (Hong Kong, ap-east-1) or Europe (Milan, eu-south-1) regions. + + + + + +First, place your database connection information (hostname, username, password, port, and database name) into Systems Manager, each as a `SecureString`. + +Go to the Systems Manager console, navigate to Parameter Store, and click "Create Parameter". Create five different SecureStrings: one each for the hostname of your database server, the username and password to connect, the database port, and the database name. + +Your Systems Manager configuration should look something like this: + +![A screenshot of an AWS Systems Manager console page titled "Parameter Store". The page shows a list of parameters with names like "/amplify-cdk-app/username", "/amplify-cdk-app/password", and "/amplify-cdk-app/hostname" indicating database connection details. Each parameter is of Tier "Standard" and typed as "SecureString". The last modified date is displayed for each parameter.](/images/storing-db-creds-in-ssm.png) + + +First, place your database connection information (hostname, username, password, port, and database name) into Secrets Manager. + +Go to the Secrets Manager console, navigate to Secrets, and click "Store a new secret". You may create the secret in any manner as long as there are `username` and `password` keys defined. + +![A screenshot of a page in the Secrets Manager console titled "Secret value info". The screenshot shows an example of a secret's keys and values in a table including "username", "password", "engine", "host", "port", and "dbClusterIdentifier".](/images/storing-db-creds-in-secrets-manager.png) + +Optionally, you can decide whether to encrypt the secret using the KMS key that Secrets Manager creates or a customer managed KMS key that you create. + +You can also configure a rotation schedule and create a Lambda function or choose an existing Lambda function from your account to rotate the database credentials automatically. + + + +Install the following package to add the Amplify GraphQL API construct to your dependencies: + +```sh +npm install @aws-amplify/graphql-api-construct +``` + +Create a new `schema.sql.graphql` file within your CDK app’s `lib/` folder that includes the APIs you want to expose. Define your GraphQL object types, queries, and mutations to match the APIs you wish to expose. For example, define object types for database tables, queries to fetch data from those tables, and mutations to modify those tables. + +```graphql +type Post { + id: Int! + title: String! + content: String! + published: Boolean + publishedDate: AWSDate @refersTo(name: "published_date") +} + +type Query { + searchPosts(contains: String!): [Post] + @sql( + statement: "SELECT * FROM posts WHERE title LIKE CONCAT('%', :contains, '%');" + ) + @auth(rules: [{ allow: public }]) +} + +type Mutation { + createPost(title: String! content: String!): AWSJSON + @sql(statement: "INSERT INTO posts (title, content) VALUES (:title, :content);") + @auth(rules: [{ allow: public }]) +} +``` + +You can use the `:variable` notation to reference input variables from the query request. + + +Amplify’s GraphQL API operates on a deny-by-default basis. The `{ allow: public }` auth rule in the example schema above designates that anyone using an API Key is authorized to execute the query. + +Review [Authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) to limit access to these queries and mutations based on API Key, Amazon Cognito User Pool, OpenID Connect, AWS Identity and Access Management (IAM), or a custom Lambda function. + + + +Next, open the main stack file in your CDK project (usually located in `lib/-stack.ts`). Import the necessary constructs at the top of the file: + +```ts +import { + AmplifyGraphqlApi, + AmplifyGraphqlDefinition +} from '@aws-amplify/graphql-api-construct'; + +import path from 'path'; +``` + +In the main stack class, add the following code to define a new GraphQL API. Replace `stack` with the name of your stack instance (often referenced via `this`): + + + +```ts +new AmplifyGraphqlApi(stack, 'SqlBoundApi', { + apiName: 'MySqlBoundApi', + definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( + [path.join(__dirname, 'schema.sql.graphql')], + { + name: 'MySQLSchemaDefinition', + dbType: 'MYSQL', + vpcConfiguration: { + vpcId: 'vpc-123456', + securityGroupIds: ['sg-123', 'sg-456'], + subnetAvailabilityZoneConfig: [ + { subnetId: 'sn-123456', availabilityZone: 'us-east-1a' }, + { subnetId: 'sn-987654', availabilityZone: 'us-east-1b' } + ] + }, + dbConnectionConfig: { + hostnameSsmPath: + '/path/to/ssm/SecureString/containing/value/of/hostname', + portSsmPath: '/path/to/ssm/SecureString/containing/value/of/port', + usernameSsmPath: + '/path/to/ssm/SecureString/containing/value/of/username', + passwordSsmPath: + '/path/to/ssm/SecureString/containing/value/of/password', + databaseNameSsmPath: + '/path/to/ssm/SecureString/containing/value/of/databaseName' + } + } + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); +``` + + + + +```ts +new AmplifyGraphqlApi(this, 'SqlBoundApi', { + apiName: 'MySqlBoundApi', + definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( + [path.join(__dirname, 'schema.sql.graphql')], + { + name: 'MySQLSchemaDefinition', + dbType: 'MYSQL', + vpcConfiguration: { + vpcId: 'vpc-123456', + securityGroupIds: ['sg-123', 'sg-456'], + subnetAvailabilityZoneConfig: [ + { subnetId: 'sn-123456', availabilityZone: 'us-east-1a' }, + { subnetId: 'sn-987654', availabilityZone: 'us-east-1b' }, + ], + }, + dbConnectionConfig: { + databaseName: 'database', + port: 3306, + hostname: 'database-1-instance-1.id.region.rds.amazonaws.com', + secretArn: + 'arn:aws:secretsmanager:Region1:123456789012:secret:MySecret-a1b2c3', + }, + } + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30), + }, + }, +}); +``` + + + + +The API will have an API key enabled for authorization. + +Before deploying, make sure to: + +- Set a value for `name`. This will be used to name the AppSync DataSource itself, plus any associated resources like resolver Lambdas. This name must be unique across all schema definitions in a GraphQL API. + +- Change the `dbType` to match your database engine. This is the type of the SQL database used to process model operations for this definition. Supported engines are `"MYSQL"` or `"POSTGRES"`. + +- Update the SSM parameter paths within `dbConnectionConfig` to point to those existing in your AWS account. These are the parameters the SQL Lambda will use to connect to the database. + +- If your database instance exists within a VPC, update the `vpcConfiguration` properties - `vpcId`, `securityGroupIds`, and `subnetAvailabilityZoneConfig` with your vpc details. This is the configuration of the VPC into which to install the SQL Lambda. + + + +If your database exists within a VPC, the RDS instance must be configured to be `Publicly accessible`. This does not mean the instance needs to accessible from the internet. + +The target security group(s) must have two inbound rules set up: + +- A rule allowing traffic on port 443 from the security group. + +- An inbound rule allowing traffic on the database port from the security group. (Default: 3306 for MySQL. 5432 for PostgreSQL.) + +In addition, the target security group(s) must have two outbound rules set up: + +- An outbound rule allowing traffic on port 443 to the security group. + +- An outbound rule allowing traffic on the database port to the security group. (Default: 3306 for MySQL. 5432 for PostgreSQL.) + + + **NOTE:** Make sure to limit the type of inbound traffic your security group + allows according to your security needs and/or use cases. For information on + security group rules, please refer to the [Amazon EC2 Security Group Rules reference](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html?icmpid=docs_ec2_console). + + + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + + + +Consider adding an RDS Proxy in front of the cluster to manage database connections. + +When using Amplify GraphQL API with a relational database like Amazon RDS, each query from your application needs to open a separate connection to the database. + +If there are a large number of queries occurring concurrently, it can exceed the connection limit on the database and result in errors like "Too many connections". To avoid this, Amplify can use an RDS Proxy when connecting your GraphQL API to a database. + +The RDS Proxy acts as an intermediary sitting in front of your database. Instead of each application query opening a direct connection to the database, they will connect through the Proxy. The Proxy helps manage and pool these connections to avoid overwhelming your database cluster. This improves the availability of your API, allowing more queries to execute concurrently without hitting connection limits. + +However, there is a tradeoff of increased latency - queries may take slightly longer as they wait for an available connection from the Proxy pool. There are also additional costs associated with using RDS Proxy. Please refer to the [pricing page for RDS Proxy](https://aws.amazon.com/rds/proxy/pricing/) to learn more. + + + +## Create custom queries and mutations + +Amplify GraphQL API for SQL databases introduces the `@sql` directive, which allows you to define SQL statements in custom GraphQL queries and mutations. This provides more flexibility when the default, auto-generated GraphQL queries and mutations are not sufficient. + +There are two ways to specify the SQL statement - inline or by referencing a `.sql` file. + +### Inline SQL Statement + +For getting started, you can embed the SQL statement directly in the schema using the `statement` argument. + +The SQL statement can use parameters in the format `:variable`, which will be bound to the input variables passed when executing a custom GraphQL query or mutation. + +In the example below, a SQL statement is defined, accepting a `searchTerm` input variable. + +```graphql +type Query { + searchPosts(searchTerm: String): [Post] + @sql(statement: "SELECT * FROM posts WHERE title LIKE :searchTerm;") + @auth(rules: [{ allow: public }]) +} +``` + +{/* TODO: Add a NOTE: about proxy/connection pinning here. */} + +### SQL File Reference + +For longer, more complex SQL queries, you can specify the statement in separate `.sql` files rather than inline. Referencing a file keeps your schema clean and allows reuse of SQL statements across fields. + + + + +First, update your GraphQL schema file to reference a SQL file name without the `.sql` extension: + +```graphql +type Query { + getPublishedPosts(start: AWSDate, end: AWSDate): [Post] + @sql(reference: "getPublishedPostsByDateRange") + @auth(rules: [{ allow: public }]) +} +``` + +Next, create a new `lib/sql-statements` folder and add any custom queries or mutations as SQL files. For example, you could create different `.sql` files for different queries: + +```sql +-- lib/sql-statements/getPublishedPostsByDateRange.sql +SELECT p.id, p.title, p.content, p.published_date +FROM posts p +WHERE p.published = 1 + AND p.published_date > :startDate + AND p.published_date < :endDate +ORDER BY p.published_date DESC +LIMIT 10 +``` + +```sql +-- lib/sql-statements/getPostById.sql +SELECT * FROM posts WHERE id = :id; +``` + +Then, you can import the `SQLLambdaModelDataSourceStrategyFactory` which helps define the datasource strategy from the custom `.sql` files you've created. + +```js +import { SQLLambdaModelDataSourceStrategyFactory } from '@aws-amplify/graphql-api-construct'; +import path from 'path'; +import fs from 'fs'; +``` + +In your `lib/-stack.ts` file, read from the `sql-statements/` folder and add them as custom SQL statements to your Amplify GraphQL API: + +```js + +// Define custom SQL statements folder path +const sqlStatementsPath = path.join(__dirname, 'sql-statements'); + +// Use the Factory to define the SQL data source strategy +const sqlStrategy = SQLLambdaModelDataSourceStrategyFactory.fromCustomSqlFiles( + // File paths to all SQL statements + fs + .readdirSync(sqlStatementsPath) + .map((file) => path.join(sqlStatementsPath, file)), + // Move your connection information and VPC config into here + { + dbType: 'MYSQL', + name: 'MySQLSchemaDefinition', + dbConnectionConfig: { + //... + }, + vpcConfiguration: { + //... + } + } +); + + +const amplifyApi = new AmplifyGraphqlApi(this, 'SqlBoundApi', { + definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( + [path.join(__dirname, 'schema.sql.graphql')], + sqlStrategy + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); +``` + +The SQL statements defined in the `.sql` files will be executed as if they were defined inline in the schema. The same rules apply in terms of using parameters, ensuring valid SQL syntax, and matching the return type to row data. + + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + + +### Custom Query + +For reference, you define a GraphQL query by adding a new field under a `type Query` object: + +```graphql +type Query { + searchPostsByTitle(title: String): [Post] + @sql( + statement: "SELECT * FROM posts WHERE title LIKE CONCAT('%', :title, '%');" + ) + @auth(rules: [{ allow: public }]) +} +``` + +### Custom Mutation + +For reference, you define a GraphQL mutation by adding a new field under a `type Mutation` object: + +```graphql +type Mutation { + publishPostById(id: ID!): AWSJSON + @sql(statement: "UPDATE posts SET published = :published WHERE id = :id;") + @auth(rules: [{ allow: public }]) +} +``` + +### Returning row data from custom mutations + +SQL statements such as `INSERT`, `UPDATE` and `DELETE` return the number of rows affected. + +If you want to return the result of the SQL statement, you can use `AWSJSON` as the return type. + +```graphql +type Mutation { + publishPosts: AWSJSON @sql(statement: "UPDATE posts SET published = 1;") + @auth(rules: [{ allow: public }]) +} +``` + +This will return a JSON response similar to this: + +```json +{ + "data": { + "publishPosts": "{\"fieldCount\":0,\"affectedRows\":7,\"insertId\":0,\"info\":\"Rows matched: 7 Changed: 7 Warnings: 0\",\"serverStatus\":34,\"warningStatus\":0,\"changedRows\":7}" + } +} +``` + +However, you might want to return the actual row data instead. + + + + +In MySQL, you can create and call a stored procedure that performs both an UPDATE statement and SELECT query to return a single post. + +Create a stored procedure by running the following SQL statement in your MySQL database: + +```sql +CREATE PROCEDURE publish_post (IN postId VARCHAR(255)) + +BEGIN +UPDATE posts SET published = 1 WHERE id = postId; + +SELECT * FROM posts WHERE id = postId LIMIT 1; +END +``` + +Call the stored procedure from the custom mutation: + +```graphql +type Mutation { + publishPostById(id: String!): [Post] + @sql(statement: "CALL publish_post(:id);") + @auth(rules: [{ allow: public }]) +} +``` + + + + +In PostgreSQL, you can add a `RETURNING` clause to an `INSERT`, `UPDATE`, or `DELETE` statement and get the actual modified row data. + +Example: + +```graphql +type Mutation { + publishPostById(id: String!): [Post] + @sql(statement: "UPDATE posts SET price = :id RETURNING *;") + @auth(rules: [{ allow: public }]) +} +``` + + + + + + The return type for custom queries and mutations expecting row data must + be an array of the corresponding model. + + + +## Apply authorization rules + +### Model level authorization rules + +The `@auth` directive can be used to restrict access to data and operations by specifying authorization rules. It allows granular access control over the GraphQL API based on the user's identity and attributes. You can for example, limit a query or mutation to only logged-in users via an `@auth(rules: [{ allow: private }])` rule or limit access to only users of the "Admin" group via an `@auth(rules: [{ allow: groups, groups: ["Admin"] }])` rule. + +All model-level authorization rules are supported for Amplify GraphQL schemas generated from MySQL and PostgreSQL databases. + +In the example below, public users authorized via API Key are granted unrestricted access to all posts. + +Add the following auth rule to the `Post` model within the `schema.sql.graphql` file: + +```graphql +type Post @model @refersTo(name: "posts") @auth(rules: [{ allow: public }]) { + id: String! @primaryKey + title: String! + content: String! +} +``` + +For more information on each rule please refer to our documentation on [Authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules). + +### Field-level authorization rules + +Field level auth rules are also supported for Amplify GraphQL schemas generated from MySQL and PostgreSQL databases. + +In the example below, unauthenticated users can read post data but only the owner of the post can perform operations on the `published` field. + +```graphql +type Post + @model + @refersTo(name: "posts") + @auth(rules: [ + { allow: public, operations: [read] }, + { allow: owner } + ]) { + id: String! @primaryKey + title: String! + content: String! + published: Boolean + // highlight-start + @auth(rules: [{ allow: owner }]) + // highlight-end +} +``` + +For more information on field-level auth rules please refer to our documentation on [Field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules). + +## Deploy your API + + + +To deploy the API, you can use the `cdk deploy` command: + +```sh +cdk deploy +``` + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + +Now the API has been deployed and you can start using it! + +You can start querying from the AWS AppSync console or integrate it into your application using the AWS Amplify libraries! + +## Auto-generate CRUDL operations for existing tables + +You can generate common CRUDL operations for your database tables based on your database schema. This saves time from hand-authoring the GraphQL types, queries, and mutations and SQL statements for common CRUDL use cases. After you generate the operations, you can annotate the `@model` types with authorization rules. + +Create a `Ingredients` table in your database: + +```sql +CREATE TABLE Ingredients ( + id varchar(255) NOT NULL PRIMARY KEY, + name varchar(255) NOT NULL, + unit_of_measurement varchar(255) NOT NULL, + price decimal(10, 2) NOT NULL, + supplier_id int, +); +``` + +### Step 1 - Export database schema as CSV + +Execute the following SQL statement on your database using a MySQL, PostgreSQL Client, or CLI tool similar to `psql` and export the output to a CSV file: + + + You must include column headers when exporting the database schema output to a CSV file. + + +Replace `` with the name of your database/schema. + + + +```sql +SELECT DISTINCT + INFORMATION_SCHEMA.COLUMNS.TABLE_NAME, + INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME, + INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT, + INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION, + INFORMATION_SCHEMA.COLUMNS.DATA_TYPE, + INFORMATION_SCHEMA.COLUMNS.COLUMN_TYPE, + INFORMATION_SCHEMA.COLUMNS.IS_NULLABLE, + INFORMATION_SCHEMA.COLUMNS.CHARACTER_MAXIMUM_LENGTH, + INFORMATION_SCHEMA.STATISTICS.INDEX_NAME, + INFORMATION_SCHEMA.STATISTICS.NON_UNIQUE, + INFORMATION_SCHEMA.STATISTICS.SEQ_IN_INDEX, + INFORMATION_SCHEMA.STATISTICS.NULLABLE +FROM INFORMATION_SCHEMA.COLUMNS +LEFT JOIN INFORMATION_SCHEMA.STATISTICS ON INFORMATION_SCHEMA.COLUMNS.TABLE_NAME=INFORMATION_SCHEMA.STATISTICS.TABLE_NAME AND INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME=INFORMATION_SCHEMA.STATISTICS.COLUMN_NAME +WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA = ''; +-- Replace database name here ^^^^^^^^^^^^^^^ +``` + +Your exported SQL schema should look something like this: + +```csv +TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,ORDINAL_POSITION,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,CHARACTER_MAXIMUM_LENGTH,INDEX_NAME,NON_UNIQUE,SEQ_IN_INDEX,NULLABLE +Ingredients,id,,1,int,int,NO,,PRIMARY,0,1,"" +Ingredients,name,,2,varchar,varchar(100),NO,100,,,, +Ingredients,unit_of_measurement,,3,varchar,varchar(50),NO,50,,,, +Ingredients,price,,4,decimal,"decimal(10,2)",NO,,,,, +Ingredients,supplier_id,,6,int,int,YES,,,,, +Meals,id,,1,int,int,NO,,PRIMARY,0,1,"" +``` + + + +```sql +SELECT DISTINCT + INFORMATION_SCHEMA.COLUMNS.table_name, + enum_name,enum_values,column_name,column_default,ordinal_position,data_type,udt_name,is_nullable,character_maximum_length,indexname,constraint_type, + REPLACE(SUBSTRING(indexdef from '\((.*)\)'), '"', '') as index_columns +FROM INFORMATION_SCHEMA.COLUMNS +LEFT JOIN pg_indexes +ON + INFORMATION_SCHEMA.COLUMNS.table_name = pg_indexes.tablename + AND INFORMATION_SCHEMA.COLUMNS.column_name = ANY(STRING_TO_ARRAY(REPLACE(SUBSTRING(indexdef from '\((.*)\)'), '"', ''), ', ')) + LEFT JOIN ( + SELECT + t.typname AS enum_name, + ARRAY_AGG(e.enumlabel) as enum_values + FROM pg_type t JOIN + pg_enum e ON t.oid = e.enumtypid JOIN + pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE n.nspname = 'public' + GROUP BY enum_name + ) enums + ON enums.enum_name = INFORMATION_SCHEMA.COLUMNS.udt_name + LEFT JOIN information_schema.table_constraints + ON INFORMATION_SCHEMA.table_constraints.constraint_name = indexname + AND INFORMATION_SCHEMA.COLUMNS.table_name = INFORMATION_SCHEMA.table_constraints.table_name +WHERE INFORMATION_SCHEMA.COLUMNS.table_schema = 'public' + AND INFORMATION_SCHEMA.COLUMNS.TABLE_CATALOG = ''; +-- Replace database name here ^^^^^^^^^^^^^^^ +``` + +Your exported SQL schema should look something like this: + +```csv +"table_name","enum_name","enum_values","column_name","column_default","ordinal_position","data_type","udt_name","is_nullable","character_maximum_length","indexname","constraint_type","index_columns" +"Ingredients","","","id","","1","bigint","int8","NO","","Ingredients_pkey","PRIMARY KEY","id" +"Ingredients","","","name","","2","text","text","NO","","","","" +"Ingredients","","","unit_of_measurement","","3","text","text","NO","","","","" +"Ingredients","","","price","","4","text","text","NO","","","","" +"Ingredients","","","supplier_id","","5","bigint","int8","NO","","","","" +``` + + + + +### Step 2 - Generate GraphQL schema from database schema + +Next, generate an Amplify GraphQL API schema by running the following command, replacing the `--engine-type` value with your database engine of `mysql` or `postgres`, and the `--sql-schema` value with the path to the CSV file created in the previous step: + +```bash +npx @aws-amplify/cli api generate-schema --engine-type mysql --sql-schema schema.csv --out schema.sql.graphql +``` + + +Next, update the first argument of `AmplifyGraphqlDefinition.fromFilesAndStrategy` to include the `schema.sql.graphql` file generated in the previous step: + +```ts +new AmplifyGraphqlApi(stack, 'SqlBoundApi', { + apiName: 'MySqlBoundApi', + definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( + [path.join(__dirname, 'schema.sql.graphql')], // file path + { + // ...strategy options + } + ) +}); +``` + +### Step 3 - Apply authorization rules for your generated GraphQL API + +Open your **schema.sql.graphql** file, you should see something like this. The auto-generated schema automatically changes the casing to better match common GraphQL conventions. Amplify's GraphQL API's operate on a **deny-by-default principle**, this means you must explicitly add `@auth` authorization rules in order to make this API accessible to your users. Currently only model-level authorization is supported. + +```graphql +input AMPLIFY { + engine: String = "mysql" +} + + +type Ingredient @refersTo(name: "Ingredients") @model { + id: Int! @refersTo(name: "ingredient_id") @primaryKey + name: String! + unitOfMeasurement: String! @refersTo(name: "unit_of_measurement") + price: Float! + supplierId: Int @refersTo(name: "supplier_id") +} +``` + +In our example, we'll add a public authorization rule, meaning anyone with an API key can create, read, update, and delete records from the database. Review [Customize authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) to see the full scope of model-level authorization capabilities. + +```diff +input AMPLIFY { + engine: String = "mysql" +} + + +- type Ingredient @refersTo(name: "Ingredients") @model { ++ type Ingredient ++ @refersTo(name: "Ingredients") ++ @model ++ @auth(rules: [{ allow: public }]) { + id: Int! @refersTo(name: "ingredient_id") @primaryKey + name: String! + unitOfMeasurement: String! @refersTo(name: "unit_of_measurement") + price: Float! + supplierId: Int @refersTo(name: "supplier_id") +} +``` + +Finally, remember to deploy your API to the cloud: + + + +To deploy the API, you can use the `cdk deploy` command: + +```sh +cdk deploy +``` + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + +Now the API has been deployed and you can start using it! + +### Rename & map models to tables + +To rename models and fields, you can use the `@refersTo` directive to map the models in the GraphQL schema to the corresponding table or field by name. + +By default, the Amplify CLI singularizes each model name using PascalCase and field names that are either snake_case or kebab-case will be converted to camelCase. + +In the example below, the Post model in the GraphQL schema is now mapped to the posts table in the database schema. Also, the `isPublished` is now mapped to the `published` column on the posts table. + +```graphql +type Post @refersTo(name: "posts") @model { + id: String! @primaryKey + title: String! + content: String! + isPublished: Boolean @refersTo(name: "published") + publishedDate: AWSDate @refersTo(name: "published_date") +} +``` + +### Create relationships between models + +You can use the `@hasOne`, `@hasMany`, and `@belongsTo` relational directives to create relationships between models. The field named in the `references` parameter of the relational directives must exist on the child model. + + + +Relationships that query across DynamoDB and SQL data sources are currently not supported. However, you can create relationships across SQL data sources. + + + +Assume that you have `users`, `blogs`, and `posts` tables in your database schema. The following examples demonstrate how you might create different types of relationships between them. Use them as references for creating relationships between the models in your own schema. + +#### Has One relationship + +Create a one-directional one-to-one relationship between two models using the `@hasOne` directive. + +In the example below, a User has a single Blog. + +```graphql +type User + @refersTo(name: "users") + @model + @auth(rules: [{ allow: owner }, { allow: groups, groups: ["Admin"] }]) { + id: String! @primaryKey + name: String! + owner: String + blog: Blog @hasOne(references: ["userId"]) +} +``` + +#### Has Many relationship + +Create a one-directional one-to-many relationship between two models using the `@hasMany` directive. + +In the example below, a Blog has many Posts. + +```graphql +type Blog @model { + id: String! @primaryKey + title: String! + posts: [Post] @hasMany(references: ["blogId"]) +} + +type Post @model { + id: String! @primaryKey + title: String! + content: String! + blogId: String! @refersTo(name: "blog_id") +} +``` + +#### Belongs To relationship + +Make a "has one" or "has many" relationship bi-directional with the `@belongsTo` directive. + +In the example below, a Post belongs to a Blog. + +```graphql +type Post @model { + id: String! @primaryKey + title: String! + content: String! + blogId: String! @refersTo(name: "blog_id") + blog: Blog @belongsTo(references: ["blogId"]) +} +``` + +### Apply iterative changes from the database definition + + + + 1. Make any adjustments to your SQL statements such as: + +```sql +CREATE TABLE posts ( + id varchar(255) NOT NULL PRIMARY KEY, + title varchar(255) NOT NULL, + content varchar(255) NOT NULL, + published tinyint(1) DEFAULT 0 NOT NULL + published_date date NULL +); +``` + +2. Regenerate the database schema as a CSV file by following the instructions in [Generate GraphQL schema from database schema](#step-2---generate-graphql-schema-from-database-schema). + +3. Generate an updated schema by running the following command, replacing the `--engine-type` value with your database engine of `mysql` or `postgres`, and the `--sql-schema` value with the path to the CSV file created in the previous step: + +```sh +npx @aws-amplify/cli api generate-schema --engine-type mysql --sql-schema schema.csv --out schema.sql.graphql +``` + +4. Deploy your changes to the cloud: + +```sh +cdk deploy +``` + + + + + + +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). + + + + + + +## How does it work? + +The Amplify uses AWS Lambda functions to enable features like querying data from your database. To work properly, these Lambda functions need access to common logic and dependencies. + +Amplify provides this shared code in the form of Lambda Layers. You can think of Lambda Layers as a package of reusable runtime code that Lambda functions can reference. + +When you deploy an Amplify API, it will create two Lambda functions: + +### SQL Lambda + +This allows you to query and write data to your database from your API. + + + **NOTE:** If the database is in a VPC, this Lambda function will be deployed + in the same VPC as the database. The usage of Amazon Virtual Private Cloud + (VPC) or VPC peering, with AWS Lambda functions will incur additional charges + as explained, this comes with an additional cost as explained on the [Amazon + Elastic Compute Cloud (EC2) on-demand pricing + page](https://aws.amazon.com/ec2/pricing/on-demand/). + + +### Updater Lambda + +This automatically keeps the SQL Lambda up-to-date by managing its Lambda Layers. + +A Lambda layer that includes all the core SQL connection logic lives within the AWS Amplify service account but is executed within your AWS account, when invoked by the SQL Lambda. This allows the Amplify service team to own the ongoing maintenance and security enhancements of the SQL connection logic. + +This allows the Amplify team to maintain and enhance the SQL Layer without needing direct access to your Lambdas. If updates to the Layer are needed, the Updater Lambda will receive a signal from Amplify and automatically update the SQL Lambda with the latest Layer. + +### Mapping of SQL data types to GraphQL types when auto-generating GraphQL schema + + + +**Note:** MySQL does not support time zone offsets in date time or timestamp fields. Instead, we will convert these values to `datetime`, without the offset. + +Unlike MySQL, PostgreSQL does support date time or timestamp values with an offset. + + + +| SQL | GraphQL | +|--------------------|--------------| +| **String** | | +| char | String | +| varchar | String | +| tinytext | String | +| text | String | +| mediumtext | String | +| longtext | String | +| **Geometry** | | +| geometry | String | +| point | String | +| linestring | String | +| geometryCollection | String | +| **Numeric** | | +| smallint | Int | +| mediumint | Int | +| int | Int | +| integer | Int | +| bigint | Int | +| tinyint | Int | +| float | Float | +| double | Float | +| decimal | Float | +| dec | Float | +| numeric | Float | +| **Date and Time** | | +| date | AWSDate | +| datetime | AWSDateTime | +| timestamp | AWSDateTime | +| time | AWSTime | +| year | Int | +| **Binary** | | +| binary | String | +| varbinary | String | +| tinyblob | String | +| blob | String | +| mediumblob | String | +| longblob | String | +| **Others** | | +| bool | Boolean | +| boolean | Boolean | +| bit | Int | +| json | AWSJSON | +| enum | ENUM | + +### Supported Amplify directives for auto-generated GraphQL schema + +| Name | Supported | Model Level | Field Level | Description | +|--------------|:---------:|:-----------:|:-----------:|-------------| +| `@model` | ✅ | ✅ | ❌ | Creates a datasource and resolver for a table. | +| `@auth` | ✅ | ✅ | ✅ | Allows access to data based on a set of authorization methods and operations. | +| `@primaryKey`| ✅ | ❌ | ✅ | Sets a field to be the primary key. | +| `@index` | ✅ | ❌ | ✅ | Defines an index on a table. | +| `@default` | ✅ | ❌ | ✅ | Sets the default value for a column. | +| `@hasOne` | ✅ | ❌ | ✅ | Defines a one-way 1:1 relationship from a parent to child model. | +| `@hasMany` | ✅ | ❌ | ✅ | Defines a one-way 1:M relationship between two models, the reference being on the child. | +| `@belongsTo` | ✅ | ❌ | ✅ | Defines bi-directional relationship with the parent model. | +| `@manyToMany`| ❌ | ❌ | ❌ | Defines a M:N relationship between two models. | +| `@refersTo` | ✅ | ✅ | ✅ | Maps a model to a table, or a field to a column, by name. | +| `@mapsTo` | ❌ | ❌ | ❌ | Maps a model to a DynamoDB table. | +| `@sql` | ✅ | ❌ | ✅ | Accepts an inline SQL statement or reference to a .sql file to be executed to resolve a Custom Query or Mutation. | + + +## Troubleshooting + +### Debug Mode + +To return the actual SQL error instead of a generic error from GraphQL responses, an environment variable `DEBUG_MODE` can be set to `true` on the Amplify-generated SQL Lambda function. You can find this Lambda function in the AWS Lambda console with the naming convention of: `--SQLLambdaFunction`. + +## Next steps + +Our recommended next steps include using the GraphQL API to mutate and query data on app clients or how to customize the authorization rules for your custom queries and mutations. Some resources that will help with this work include: + +- [Create, update, and delete application data](/[platform]/build-a-backend/graphqlapi/mutate-data/) +- [Read application data](/[platform]/build-a-backend/graphqlapi/query-data/) +- [Customize Authorization Rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx new file mode 100644 index 00000000000..9bf799a2f95 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx @@ -0,0 +1,266 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Incorporate machine learning', + description: 'Add AI/ML capabilities such as text recognition, image labeling, text-to-speech, and translation to your GraphQL API.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/connect-machine-learning-services/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + + +Amplify allows you to identify text on an image, identify labels on an image, translate text, and synthesize speech from text with the `@predictions` directive. + +> Note: The `@predictions` directive requires a S3 storage bucket configured via `amplify add storage` or set the `predictionsBucket` property when using CDK. + +## Identify text on an image + +To configure text recognition on an image use the `identifyText` action in the `@predictions` directive. + +```graphql +type Query { + recognizeTextFromImage: String @predictions(actions: [identifyText]) +} +``` + +In your GraphQL query, can pass in a S3 `key` for the image. At the moment, this directive works only with objects located within the `public/` folder of your S3 bucket. The `public/` prefix is automatically added to the `key` input. For instance, in the example below, `public/myimage.jpg` will be used as the input. + +```graphql +query RecognizeTextFromImage($input: RecognizeTextFromImageInput!) { + recognizeTextFromImage(input: { identifyText: { key: "myimage.jpg" } }) +} +``` + +## Identify labels on an image + +To configure label recognition on an image use the `identifyLabels` action in the `@predictions` directive. + +```graphql +type Query { + recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) +} +``` + +In your GraphQL query, you can pass in a S3 `key` for the image. At the moment, this directive works only with objects located within `public/` folder in your S3 bucket. The `public/` prefix is automatically added to the `key` input. For instance, in the example below, `public/myimage.jpg` will be used as the input. + +The query below will return a list of identified labels. Review [Detecting Labels](https://docs.aws.amazon.com/rekognition/latest/dg/labels.html) in the Amazon Rekognition documentation for the full list of supported labels. + +```graphql +query RecognizeLabelsFromImage($input: RecognizeLabelsFromImageInput!) { + recognizeLabelsFromImage(input: { identifyLabels: { key: "myimage.jpg" } }) +} +``` + +## Translate text + +To configure text translation use the `identifyLabels` action in the `@predictions` directive. + +```graphql +type Query { + translate: String @predictions(actions: [translateText]) +} +``` + +The query below will return the translated string. Populate the `sourceLanguage` and `targetLanguage` parameters with one of the [Supported Language Codes](https://docs.aws.amazon.com/translate/latest/dg/what-is.html#what-is-languages). Pass in the text to translate via the `text` parameter. + +```graphql +query TranslateText($input: TranslateTextInput!) { + translate( + input: { + translateText: { + sourceLanguage: "en" + targetLanguage: "de" + text: "Translate me" + } + } + ) +} +``` + +## Synthesize speech from text + +To configure Text-to-Speech synthesis use the `convertTextToSpeech` action in the `@predictions` directive. + +```graphql +type Query { + textToSpeech: String @predictions(actions: [convertTextToSpeech]) +} +``` + +The query below will return a presigned URL with the synthesized speech. Populate the `voiceID` parameter with one of the [Supported Voice IDs](https://docs.aws.amazon.com/polly/latest/dg/voicelist.htm). Pass in the text to synthesize via the `text` parameter. + +```graphql +query ConvertTextToSpeech($input: ConvertTextToSpeechInput!) { + textToSpeech( + input: { + convertTextToSpeech: { + voiceID: "Nicole" + text: "Hello from AWS Amplify!" + } + } + ) +} +``` + +## Combining Predictions actions + +You can also combine multiple Predictions actions together into a sequence. The following action sequences are supported: + +- `identifyText -> translateText -> convertTextToSpeech` +- `identifyLabels -> translateText -> convertTextToSpeech` +- `translateText -> convertTextToSpeech` + +In the example below, `speakTranslatedImageText` identifies text from an image, then translates it into another language, and finally converts the translated text to speech. + +```graphql +type Query { + speakTranslatedImageText: String + @predictions(actions: [identifyText, translateText, convertTextToSpeech]) +} +``` + +An example of that query will look like: + +```graphql +query SpeakTranslatedImageText($input: SpeakTranslatedImageTextInput!) { + speakTranslatedImageText( + input: { + identifyText: { key: "myimage.jpg" } + translateText: { sourceLanguage: "en", targetLanguage: "es" } + convertTextToSpeech: { voiceID: "Conchita" } + } + ) +} +``` + +A code example of this using the JS Library is shown below: + +```js +import React, { useState } from 'react'; +import { Amplify } from 'aws-amplify'; +import { uploadData, getUrl } from 'aws-amplify/storage'; +import { generateClient } from 'aws-amplify/api'; +import config from './amplifyconfiguration.json'; +import { speakTranslatedImageText } from './graphql/queries'; + +/* Configure Exports */ +Amplify.configure(config); + +const client = generateClient(); + +function SpeakTranslatedImage() { + const [src, setSrc] = useState(''); + const [img, setImg] = useState(''); + + function putS3Image(event) { + const file = event.target.files[0]; + uploadData({ + key: file.name, + data: file + }) + .result.then(async (result) => { + setSrc(await speakTranslatedImageTextOP(result.key)); + setImg((await getUrl({ key: result.key })).url.toString()); + }) + .catch((err) => console.log(err)); + } + + return ( +
+
+

Upload Image

+ { + putS3Image(event); + }} + /> +
+ {img && } + {src && ( +
+ +
+ )} +
+
+ ); +} + +async function speakTranslatedImageTextOP(key) { + const inputObj = { + translateText: { + sourceLanguage: 'en', + targetLanguage: 'es' + }, + identifyText: { key }, + convertTextToSpeech: { voiceID: 'Conchita' } + }; + const response = await client.graphql({ + query: speakTranslatedImageText, + variables: { input: inputObj } + }); + return response.data.speakTranslatedImageText; +} + +function App() { + return ( +
+

Speak Translated Image

+ +
+ ); +} +export default App; +``` + +## How it works + +Definition of the `@predictions` directive: + +```graphql +directive @predictions(actions: [PredictionsActions!]!) on FIELD_DEFINITION +enum PredictionsActions { + identifyText # uses Amazon Rekognition to detect text + identifyLabels # uses Amazon Rekognition to detect labels + convertTextToSpeech # uses Amazon Polly in a lambda to output a presigned url to synthesized speech + translateText # uses Amazon Translate to translate text from source to target language +} +``` + +`@predictions` creates resources to communicate with Amazon Rekognition, Translate, and Polly. For each action the following is created: + +- IAM Policy for each service (e.g. Amazon Rekognition `detectText` Policy) +- An AppSync VTL function +- An AppSync DataSource + +Finally, a pipeline resolver is created for the query or field. The pipeline resolver is composed of AppSync functions which are defined by the action list provided in the directive. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx new file mode 100644 index 00000000000..2950fd5f6ce --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx @@ -0,0 +1,958 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Set up custom queries and mutations', + description: 'Add authorization rules to your GraphQL schema to control access to your data.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/custom-business-logic/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + + +Define your custom business logic in a Lambda function resolver, HTTP resolver, or an AppSync JavaScript or VTL resolver and expose them in a GraphQL query or mutation. Extend or override Amplify-generated GraphQL resolvers to optimize for your specific use cases. + +## Create a custom query or mutation + +While `@model` automatically generates dedicated "create", "read", "update", "delete", and "subscription" queries or mutations for you, there are some cases where you want to define a stand-alone query or mutation. + +1. Define your custom query or mutation + +```graphql +type Mutation { + myCustomMutation(args: String): String # your custom mutations here +} + +type Query { + myCustomQuery(args: String): String # your custom queries here +} +``` + +2. Use one of these resolver choices to handle the query or mutation request: + + - [Lambda function resolver](#lambda-function-resolver): use a custom Lambda function to handle query or mutation + - [HTTP resolver](#http-resolver): call an HTTP endpoint upon a query or mutation + - [AppSync JavaScript or VTL resolver](#appsync-javascript-or-vtl-resolver) (most advanced): use AppSync's JavaScript resolver or AppSync's VTL mapping templates to customize the query and mutation logic + +3. Secure your custom query or mutation with [field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) + - Note: Dynamic authorization rules are not supported on a custom query or mutation. + +## Lambda function resolver + +The `@function` directive allows you to quickly & easily configure a AWS Lambda resolvers with your GraphQL API. You can use any AWS Lambda functions that was created with the Amplify CLI, AWS CDK or reference an existing AWS Lambda function created with any other means. + + + + + +For example, use `amplify add function` to add a Lambda function called "echofunction" with the following handler: + +```js +exports.handler = async function (event, context) { + return event.arguments.msg; +}; +``` + +To connect an AWS Lambda resolver to the GraphQL API, add the `@function` directive to a field in your schema. + +```graphql +type Query { + echo(msg: String): String @function(name: "echofunction-${env}") +} +``` + +The Amplify CLI provides support for maintaining multiple environments. When you deploy a function via `amplify add function`, it will automatically add the environment suffix to your Lambda function name. For example, if you create a function named `echofunction` using `amplify add function` in the `dev` environment, the deployed function will be named `echofunction-dev`. The `@function` directive allows you to use `${env}` to reference the current Amplify CLI environment. + + + + + +First, create your Lambda function in CDK with your logic and set the `functionName` parameter. + +```ts +const echoLambda = new lambda.Function(this, 'EchoLambda', { + functionName: 'echofunction', // MAKE SURE THIS MATCHES THE @function's "name" PARAMETER + code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_18_X +}); + +const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); +``` + +To connect an AWS Lambda resolver to the GraphQL API, add the `@function` directive to a field in your GraphQL schema. + +```graphql +type Query { + echo(msg: String): String @function(name: "echofunction") +} +``` + +Optionally, if you don't want to hard-code the function name into the GraphQL schema, you can also set an arbitrary name in the GraphQL schema and then map a function within CDK to the function name. + +```ts +const coolLambdaFunction = new lambda.Function(this, 'MyCoolLambdaFunction', { + code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_18_X +}); + +const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + }, + functionNameMap: { + echofunction: coolLambdaFunction // Remap the function name to any function you define or reference within CDK. + } +}); +``` + + + + + +If you deployed your Lambda function without Amplify CLI then you must provide the full Lambda function name in the `name` parameter. If you deployed the same function with the name echofunction then you would have: + +```graphql +type Query { + echo(msg: String): String @function(name: "echofunction") +} +``` + + + + + +### Structure of the function event + +When writing Lambda functions that are connected via the `@function` directive, you can expect the following structure for the AWS Lambda `event` object. + +| Key | Description | +| --- | --- | +| typeName | The name of the parent object type of the field being resolved. | +| fieldName | The name of the field being resolved. | +| arguments | A map containing the arguments passed to the field being resolved. | +| identity | A map containing identity information for the request. Contains a nested key 'claims' that will contains the JWT claims if they exist. | +| source | When resolving a nested field in a query, the source contains parent value at runtime. For example when resolving `Post.comments`, the source will be the Post object. | +| request | The AppSync request object. Contains header information. | +| prev | When using pipeline resolvers, this contains the object returned by the previous function. You can return the previous value for auditing use cases. | + +Your function should follow the Lambda handler guidelines for your specific language. See the developer guides from the [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) documentation for your chosen language. If you choose to use structured types, your type should serialize the AWS Lambda event object outlined above. For example, if using Golang, you should define a struct with the above fields. + +### Calling functions in different regions + +By default, you expect the function to be in the same region as the Amplify project. If you need to call a function in a different or a specific region, you can provide the **region** argument. + +```graphql +type Query { + echo(msg: String): String @function(name: "echofunction", region: "us-east-1") +} +``` + +Calling functions in different AWS accounts is not supported via the `@function` directive but is supported by AWS AppSync. + +### Chaining functions + +You can chain together multiple `@function` resolvers such that they are invoked in series when your field's resolver is invoked. To create a pipeline resolver that calls to multiple AWS Lambda functions in series, use multiple `@function` directives on the field. Similarly, `@function` can be combined with field-level `@auth`. When combining these field directives, the ordering in the schema matches the ordering in the pipeline resolver. You can choose to have functions before and/or after field level authorization is applied. + +> **Note:** Be careful when using @auth directives on a field in a root type. @auth directives on field definitions use the source object to perform authorization logic and the source will be an empty object for fields on root types. Static group authorization should perform as expected. + +```graphql +type Mutation { + doSomeWork(msg: String): String + @function(name: "worker-function") + @function(name: "audit-function") +} +``` + +In the example above when you run a mutation that calls the `Mutation.doSomeWork` field, the **worker-function** will be invoked first then the **audit-function** will be invoked with an event that contains the results of the **worker-function** under the **event.prev.result** key. The **audit-function** would need to return **event.prev.result** if you want the result of **worker-function** to be returned for the field. + +### How it works + +Definition of `@function` directive: + +```graphql +directive @function(name: String!, region: String) on FIELD_DEFINITION +``` + +Under the hood, Amplify creates an `AppSync::FunctionConfiguration` for each unique instance of `@function` in a document and a pipeline resolver containing a pointer to a function for each `@function` on a given field. + +The `@function` directive generates these resources as necessary: + +1. An AWS IAM role that has permission to invoke the function as well as a trust policy with AWS AppSync. +2. An AWS AppSync data source that registers the new role and existing function with your AppSync API. +3. An AWS AppSync pipeline function that prepares the lambda event and invokes the new data source. +4. An AWS AppSync resolver that attaches to the GraphQL field and invokes the new pipeline functions. + +## HTTP resolver + +The `@http` directive allows you to quickly configure HTTP resolvers within your GraphQL API. + +To connect to an endpoint, add the @http directive to a field in your GraphQL schema. The directive allows you to define URL path parameters, and specify a query string and/or specify a request body. For example, given the definition of a Post type: + +```graphql +type Post { + id: ID! + title: String + description: String + views: Int +} + +type Query { + listPosts: [Post] @http(url: "https://www.example.com/posts") +} +``` + +Amplify generates the definition below that sends a request to the url when the listPosts query is used. + +```graphql +type Query { + listPosts: [Post] +} +``` + +### Request headers + +The `@http` directive generates resolvers that can handle XML and JSON responses. If an HTTP method is not defined, `GET` is used. You can specify a list of static headers to be passed with the HTTP requests to your backend in your directive definition. + +```graphql +type Query { + listPosts: [Post] + @http( + url: "https://www.example.com/posts" + headers: [{ key: "X-Header", value: "X-Header-Value" }] + ) +} +``` + +### Path parameters + +You can create dynamic paths by specifying parameters in the directive URL by using the special `:` notation. Your set of parameters can then be specified in the params input object of the query. Note that path parameters are not added to the request body or query string. You can define multiple parameters. + +```graphql +type Query { + getPost: Post @http(url: "https://www.example.com/posts/:id") +} +``` + +In the example above, the `:id` parameter will generate the appropriate query input as shown below: + +```graphql +type Query { + getPost(params: QueryGetPostParamsInput!): Post +} + +input QueryGetPostParamsInput { + id: String! +} +``` + +You can fetch a specific post by enclosing the id in the params input object. + +```graphql +query post { + getPost(params: { id: "POST_ID" }) { + id + title + } +} +``` + +This executes the following request: + +```console +GET /posts/POST_ID +Host: www.example.com +``` + +### Query String + +You can send a query string with your request by specifying variables for your query. The query string is supported with all request methods. + +Given the definition + +```graphql +type Query { + listPosts(sort: String!, from: String!, limit: Int!): Post + @http(url: "https://www.example.com/posts") +} +``` + +Amplify generates + +```graphql +type Query { + listPosts(query: QueryListPostsQueryInput!): [Post] +} + +input QueryListPostsQueryInput { + sort: String! + from: String! + limit: Int! +} +``` + +You can query for posts using the `query` input object + +```graphql +query posts { + listPosts(query: { sort: "DESC", from: "last-week", limit: 5 }) { + id + title + description + } +} +``` + +which sends the following request: + +```text +GET /posts?sort=DESC&from=last-week&limit=5 +Host: www.example.com +``` + +### Request Body + +The `@http` directive also allows you to specify the body of a request, which is used for `POST`, `PUT`, and `PATCH` requests. To create a new post, you can define the following. + +```graphql +type Mutation { + addPost(title: String!, description: String!, views: Int): Post + @http(method: POST, url: "https://www.example.com/post") +} +``` + +Amplify generates the `addPost` query field with the `query` and `body` input objects since this type of request also supports a query string. The generated resolver verifies that non-null arguments (e.g.: the `title` and `description`) are passed in at least one of the input objects; if not, an error is returned. + +```graphql +type Mutation { + addPost(query: QueryAddPostQueryInput, body: QueryAddPostBodyInput): Post +} + +input QueryAddPostQueryInput { + title: String + description: String + views: Int +} + +input QueryAddPostBodyInput { + title: String + description: String + views: Int +} +``` + +You can add a post by using the `body` input object: + +```graphql +mutation add { + addPost(body: { title: "new post", description: "fresh content" }) { + id + } +} +``` + +which will send + +```text +POST /post +Host: www.example.com +{ + title: "new post" + description: "fresh content" +} +``` + +### Reference Amplify environment name + +The `@http` directive allows you to use `${env}` to reference the current Amplify CLI environment. + +```graphql +type Query { + listPosts: Post @http(url: "https://www.example.com/${env}/posts") +} +``` + +which, in the `DEV` environment, will send + +```text +GET /DEV/posts +Host: www.example.com +``` + +**Combining the different components** + +You can use a combination of parameters, query, body, headers, and environments in your `@http` directive definition. + +Given the definition + +```graphql +type Post { + id: ID! + title: String + description: String + views: Int + comments: [Comment] +} + +type Comment { + id: ID! + content: String +} + +type Mutation { + updatePost( + title: String! + description: String! + views: Int + withComments: Boolean + ): Post + @http( + method: PUT + url: "https://www.example.com/${env}/posts/:id" + headers: [{ key: "X-Header", value: "X-Header-Value" }] + ) +} +``` + +you can update a post with + +```graphql +mutation update { + updatePost( + body: { title: "new title", description: "updated description", views: 100 } + params: { id: "EXISTING_ID" } + query: { withComments: true } + ) { + id + title + description + comments { + id + content + } + } +} +``` + +which, in the `DEV` environment, will send + +```text +PUT /DEV/posts/EXISTING_ID?withComments=true +Host: www.example.com +X-Header: X-Header-Value +{ + title: "new title" + description: "updated description" + views: 100 +} +``` + +### Reference existing field data + +In some cases, you may want to send a request based on existing field data. Take a scenario where you have a post and want to fetch comments associated with the post in a single query. Let's use the previous definition of `Post` and `Comment`. + +```graphql +type Post { + id: ID! + title: String + description: String + views: Int + comments: [Comment] +} + +type Comment { + id: ID! + content: String +} +``` + +A post can be fetched at `/posts/:id` and a post's comments at `/posts/:id/comments`. You can fetch the comments based on the post id with the following updated definition. `$ctx.source` is a map that contains the resolution of the parent field (`Post`) and gives access to `id`. + +```graphql +type Post { + id: ID! + title: String + description: String + views: Int + comments: [Comment] + @http(url: "https://www.example.com/posts/${ctx.source.id}/comments") +} + +type Comment { + id: ID! + content: String +} + +type Query { + getPost: Post @http(url: "https://www.example.com/posts/:id") +} +``` + +You can retrieve the comments of a specific post with the following query and selection set. + +```graphql +query post { + getPost(params: { id: "POST_ID" }) { + id + title + description + comments { + id + content + } + } +} +``` + +Assuming that `getPost` retrieves a post with the id `POST_ID`, the comments field is resolved by sending this request to the endpoint + +```text +GET /posts/POST_ID/comments +Host: www.example.com +``` + +Note that there is no check to ensure that the reference variable (here the post ID) exists. When using this technique, it is recommended to make sure the referenced field is non-null. + +### How it works + +Definition of `@http` directive: + +```graphql +directive @http( + method: HttpMethod + url: String! + headers: [HttpHeader] +) on FIELD_DEFINITION +enum HttpMethod { + PUT + POST + GET + DELETE + PATCH +} +input HttpHeader { + key: String + value: String +} +``` + +The `@http` transformer will create one HTTP datasource for each identified base URL. For example, if multiple HTTP resolvers are created that interact with the "https://www.example.com" endpoint, only a single datasource is created. Each directive generates one resolver. Depending on the definition, the appropriate `body`, `params`, and `query` input types are created. Note that `@http` transformer does not support calling other AWS services where Signature Version 4 signing process is required. + +## AppSync JavaScript or VTL resolver + +You can use AWS Cloud Development Kit (CDK) to define custom AppSync resolvers for your GraphQL API. `@auth` directives are not supported for custom queries or mutations that are connected to a JavaScript or VTL resolver. This is because you are replacing Amplify's auto-generated capabilities for that particular query or mutation with a custom-defined cloud resources. + +```bash +amplify add custom +``` + +```console +? How do you want to define this custom resource? +❯ AWS CDK +? Provide a name for your custom resource +❯ MyCustomResolvers +``` + +Next, install the AppSync dependencies for your custom resource: + +```bash +cd amplify/backend/custom/MyCustomResolvers +npm i @aws-cdk/aws-appsync@~1.172.0 +``` + +> **Note:** Installations using the '\~' character do not modify the package.json. To use '\~' for default npm configurations, make sure your package.json reflects the right dependency to avoid compatibility errors in CDK. + +Finally, add your custom resolvers into the `cdk-stack.ts` file. You can either add the JavaScript or VTL inline into your `cdk-stack.ts` file. + +#### Unit Resolver + + + + + +Review the [AppSync JavaScript resolver tutorial](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) for JavaScript resolver examples for different data sources. + +```ts +import * as cdk from 'aws-cdk-lib'; +import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; +import * as appsync from 'aws-cdk-lib/aws-appsync'; +import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; +import { Construct } from 'constructs'; + +const jsResolverTemplate = ` +export function request(ctx) { + return { + payload: null + } +} + +export function response(ctx) { + return ctx.arguments.message +} +` + +export class cdkStack extends cdk.Stack { + constructor( + scope: Construct, + id: string, + props?: cdk.StackProps, + amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps + ) { + super(scope, id, props); + /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ + new cdk.CfnParameter(this, 'env', { + type: 'String', + description: 'Current Amplify CLI env name' + }); + + // Access other Amplify Resources + const retVal: AmplifyDependentResourcesAttributes = + AmplifyHelpers.addResourceDependency( + this, + amplifyResourceProps.category, + amplifyResourceProps.resourceName, + [ + { + category: 'api', + resourceName: '' + } + ] + ); + + const resolver = new appsync.CfnResolver(this, 'CustomResolver', { + // apiId: retVal.api.new.GraphQLAPIIdOutput, + // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 + // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. + // Previously the ApiId is the variable Name which is wrong , it should be variable value as below + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + fieldName: 'echo', + typeName: 'Query', // Query | Mutation | Subscription + code: jsResolverTemplate, + dataSourceName: 'NONE_DS', // DataSource name + runtime: { + name: 'APPSYNC_JS', + runtimeVersion: '1.0.0' + } + }); + } +} +``` + + + + + +Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. + +```ts +import * as cdk from 'aws-cdk-lib'; +import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; +import * as appsync from 'aws-cdk-lib/aws-appsync'; +import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; +import { Construct } from 'constructs'; + +const requestVTL = ` + +`; +const responseVTL = ` + +`; + +export class cdkStack extends cdk.Stack { + constructor( + scope: Construct, + id: string, + props?: cdk.StackProps, + amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps + ) { + super(scope, id, props); + /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ + new cdk.CfnParameter(this, 'env', { + type: 'String', + description: 'Current Amplify CLI env name' + }); + + // Access other Amplify Resources + const retVal: AmplifyDependentResourcesAttributes = + AmplifyHelpers.addResourceDependency( + this, + amplifyResourceProps.category, + amplifyResourceProps.resourceName, + [ + { + category: 'api', + resourceName: '' + } + ] + ); + + const resolver = new appsync.CfnResolver(this, 'custom-resolver', { + // apiId: retVal.api.new.GraphQLAPIIdOutput, + // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 + // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. + // Previously the ApiId is the variable Name which is wrong , it should be variable value as below + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + fieldName: 'querySomething', + typeName: 'Query', // Query | Mutation | Subscription + requestMappingTemplate: requestVTL, + responseMappingTemplate: responseVTL, + dataSourceName: 'TodoTable' // DataSource name + }); + } +} +``` + +#### Pipeline Resolver + +```ts +import * as cdk from 'aws-cdk-lib'; +import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; +import * as appsync from 'aws-cdk-lib/aws-appsync'; +import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; +import { Construct } from 'constructs'; + +const beforeMappingVTL = ` + +`; +const afterMappingVTL = ` + +`; +const function1requestVTL = ` + +`; +const function1responseVTL = ` + +`; +const function2requestVTL = ` + +`; +const function2responseVTL = ` + +`; +export class cdkStack extends cdk.Stack { + constructor( + scope: Construct, + id: string, + props?: cdk.StackProps, + amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps + ) { + super(scope, id, props); + /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ + new cdk.CfnParameter(this, 'env', { + type: 'String', + description: 'Current Amplify CLI env name' + }); + + // Access other Amplify Resources + const retVal: AmplifyDependentResourcesAttributes = + AmplifyHelpers.addResourceDependency( + this, + amplifyResourceProps.category, + amplifyResourceProps.resourceName, + [ + { + category: 'api', + resourceName: '' + } + ] + ); + + const function1 = new appsync.CfnFunctionConfiguration(this, 'function1', { + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + dataSourceName: 'NONE_DS', // DataSource name + functionVersion: '2018-05-29', + name: 'function1', + requestMappingTemplate: function1requestVTL, + responseMappingTemplate: function1responseVTL + }); + + const function2 = new appsync.CfnFunctionConfiguration(this, 'function2', { + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + dataSourceName: 'TodoTable', // DataSource name + functionVersion: '2018-05-29', + name: 'function2', + requestMappingTemplate: function2requestVTL, + responseMappingTemplate: function2responseVTL + }); + + const resolver = new appsync.CfnResolver(this, 'pipeline-resolver', { + apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), + fieldName: 'querySomething', + typeName: 'Query', // Query | Mutation | Subscription + kind: 'PIPELINE', + pipelineConfig: { + functions: [function1.attrFunctionId, function2.attrFunctionId] + }, + requestMappingTemplate: beforeMappingVTL, + responseMappingTemplate: afterMappingVTL + }); + } +} +``` + +> **Note:** Users moving from ElasticSearch to OpenSearch will need to change the datasource name from `ElasticSearchDomain` to `OpenSearchDataSource` if the upgrade process changes the source name. For new @searchable models the datasource name will default to `OpenSearchDataSource`. + +You can alternatively define the VTL templates in another file such as `Query.querySomething.req.vtl` or `Query.querySomething.res.vtl` in `amplify/backend/custom/MyCustomResolvers/`. Then use the following code snippets to retrieve them: + +```ts +requestMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.req.vtl")).renderTemplate(), +responseMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.res.vtl")).renderTemplate(), +``` + +> **Note:** the `..` is added to the path because the path is always relative to the `build` folder of the custom resource. + + + + + +## Add authorization rules to custom queries and mutations + +Authorization rules can be applied with the `@auth` directive in the same way as field-level authorization rules. See [Field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules) for details. + +In the example below, `myCustomMutation` can only be executed by signed-in customers who are authenticated with IAM: + +```graphql +type Mutation { + myCustomMutation(args: String): String + @auth(rules: [{ allow: private, provider: iam }]) +} +``` + +> **Known limitation:** You can't combine the `@auth` directive with a custom query or mutation that is using a VTL resolver. + +## Override Amplify-generated resolvers + +Amplify generates [AWS AppSync pipeline resolver](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) for your queries and mutations. The resolvers are listed the following API resource's folder `amplify/backend/api//build/resolvers/`. + +To override an Amplify-generated resolver: + +1. Find the resolver file name you want to override under `build/resolvers` +2. Place a `.vtl` with the same file name the resource's `resolvers/` (not under `build/`) +3. Upon the next `amplify api gql-compile` or `amplify push` the Amplify-generated resolver file will be replaced with your overwritten resolver file + +```console +amplify/backend/api/ +├── build +│   ├── ... +│   ├── resolvers +│   │   ├── ... +│   │   ├── Query.searchTodos.req.vtl # Find resolver file name +│   │   └── ... +| ... +├── resolvers +│   └── Query.searchTodos.req.vtl # Place resolver overrides with the same file name here +``` + +The example above shows how the `Query.searchTodos.req.vtl` is overwritten with a custom resolver. Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. + +## Extend Amplify-generated resolvers + +Amplify generates [AWS AppSync pipeline resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) for your queries and mutations. You can "slot" in your custom business logic between Amplify-generated resolvers. You can find Amplify-generated resolvers under your API resources' `build/resolvers/` folder. The resolver functions file name determines its placement within the slot sequence. + +```console +File name convention: + [Query|Mutation|Subscription].[field name].[slot name].[slot placement].[req|res].vtl +Example: + Mutation.createTodo.postAuth.1.req.vtl +``` + +To extend an Amplify-generated resolver: + +1. Find the [resolver slot](#supported-resolver-slots) you want to add your custom business logic to +2. Place a `.vtl` file with the correct the file naming convention into `resolvers/` (not under `build/`) +3. Upon the next `amplify api gql-compile` or `amplify push` the Amplify-generated resolver file will be replaced within the desired slot within the resolver sequence. + +```console +amplify/backend/api/ +├── build +│   ├── ... +│   ├── resolvers +│   │   ├── ... +│   │   ├── Mutation.createTodo.postAuth.1.req.vtl # Amplify-generated resolvers +│   │   └── ... +| ... +├── resolvers +│   └── Mutation.createTodo.postAuth.2.req.vtl # Custom resolver slotted in after postAuth.1 resolver +``` + +For example, the a resolver function file named `Mutation.createTodo.postAuth.2.req.vtl` will be slotted in right after the `Mutation.createTodo.postAuth.1.req.vtl` resolver. Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. + +### Supported resolver slots + +#### Query + +| Sequence | Slot name | Description | +| --- | --- | --- | +| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | +| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | +| 3 | auth | Resolvers that implement authorization rule checks. | +| 4 | postAuth | Resolvers that are run after authorization rule checks. | +| 5 | preDataLoad | Resolvers to configure values to make a request to the data source. | +| 6 | postDataLoad | Resolvers for post-processing after request to data source. | +| 7 | finish | Final set of resolvers before response is returned to client. Typically used for clean-up. | + +#### Mutation + +| Sequence | Slot name | Description | +| --- | --- | --- | +| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | +| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | +| 3 | auth | Resolvers that implement authorization rule checks. | +| 4 | postAuth | Resolvers that are run after authorization rule checks. | +| 5 | preUpdate | Resolvers to configure values to make a request to the data source. | +| 6 | postUpdate | Resolvers for post-processing after request to data source. | +| 7 | finish | Final set of resolvers before response is returned to client. Typically used for clean-up. | + +#### Subscription + +| Sequence | Slot name | Description | +| --- | --- | --- | +| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | +| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | +| 3 | auth | Resolvers that implement authorization rule checks. | +| 4 | postAuth | Resolvers that are run after authorization rule checks. | +| 5 | preSubscribe | Resolver slot that executes after auth but before the subscription returns | diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx new file mode 100644 index 00000000000..848c216c447 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx @@ -0,0 +1,53 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure authorization modes', + description: + "Learn more about how to configure authorization modes in Amplify's API category", + platforms: ['flutter'] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +import ios0 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; + + + +import android1 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; + + + +import js2 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; + + + +import reactnative0 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; + + + +import flutter3 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx new file mode 100644 index 00000000000..1cd3af95b35 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx @@ -0,0 +1,937 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Customize authorization rules', + description: 'Add authorization rules to your GraphQL schema to control access to your data.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/swift/build-a-backend/graphqlapi/customize-authorization-rules/' + }, + { + platforms: [ + 'angular', + 'nextjs', + 'javascript', + 'react', + 'vue', + 'react-native' + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/customize-authorization-rules/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + + +Use the `@auth` directive to configure authorization rules for public, sign-in user, per user, and per user group data access. **Authorization rules operate on the deny-by-default principle**. Meaning that if an authorization rule is not specifically configured, it is denied. + +```graphql +type Todo @model @auth(rules: [{ allow: owner }]) { + content: String +} +``` + +In the example above, each signed-in user, or also known as "owner", of a Todo can create, read, update, and delete their own Todos. + +Amplify also allows you to restrict the allowed operations, combine multiple authorization rules, and apply fine-grained field-level authorization. + +```graphql +type Todo + @model + @auth(rules: [{ allow: public, operations: [read] }, { allow: owner }]) { + content: String +} +``` + +In the example above, everyone (`public`) can read every Todo but owner (authenticated users) can create, read, update, and delete their own Todos. + +### Global authorization rule (only for getting started) + +To help you get started, there's a global authorization rule defined when you create a new GraphQL schema. For production environments, remove the global authorization rule and apply rules on each model instead. + + + + + +```graphql +input AMPLIFY { + globalAuthRule: AuthRule = { allow: public } +} +``` + + + + +In the CDK construct, we call this the "sandbox mode" that you need to explicitly enable via an input parameter. + +```ts +new AmplifyGraphqlApi(this, "MyNewApi", { + ..., + translationBehavior: { + sandboxModeEnabled: true + } +}); +``` + + + + + +The global authorization rule (in this case `{ allow: public }` - allows anyone to create, read, update, and delete) is applied to every data model in the GraphQL schema. + +**Note:** Amplify will always use the most specific authorization rule that's present. For example, a field-level authorization rule will be used in favor of a model-level authorization rule; similarly, a model-level authorization rule will be used in favor of a global authorization rule. + + + +Currently, only `{ allow: public }` is supported as a global authorization rule. + + + +## Authorization strategies + +Use the guide below to select the correct authorization strategy for your use case: + +| **Recommended use case** | **Strategy** | **Provider** | +| --- | --- | --- | +| Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access. | [`public`](#public-data-access) | `apiKey` | +| Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using AWS IAM controls. | [`public`](#public-data-access) | `iam` (or `identityPool` when using CDK construct) | +| Per user data access. Access is restricted to the "owner" of a record. Leverages `amplify add auth` Cognito user pool by default. | [`owner`](#per-user--owner-based-data-access) | `userPools` / `oidc` | +| Any signed-in data access. Unlike owner-based access, **any** signed-in user has access. | [`private`](#signed-in-user-data-access) | `userPools` / `oidc` / `iam` | +| Per user group data access. A specific or dynamically configured group of users have access | [`groups`](#user-group-based-data-access) | `userPools` / `oidc` | +| Define your own custom authorization rule within a Lambda function | [`custom`](#custom-authorization-rule) | `function` | + +### Public data access + +To grant everyone access, use the `public` authorization strategy. Behind the scenes, the API will be protected with an API Key. + +```graphql +type Todo @model @auth(rules: [{ allow: public }]) { + content: String +} +``` + +You can also override the authorization provider. In the example below, you can use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API Key. + + + + +When you run `amplify add auth`, the Amplify CLI generates scoped down IAM policies for the "Unauthenticated role" in Cognito identity pool automatically. + +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: public, provider: iam }]) { + id: ID! + title: String! +} +``` + + + + +Designate an Amazon Cognito identity pool's role for unauthenticated identities by setting the `identityPoolConfig` property: + +```ts +// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well +import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha'; + +const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', { + allowUnauthenticatedIdentities: true, + authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({ + userPool: , + userPoolClient: , + })] }, +}); + +new AmplifyGraphqlApi(this, "MyNewApi", { + definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + }, + identityPoolConfig: { + identityPoolId: identityPool.identityPoolId, + authenticatedUserRole: identityPool.authenticatedRole, + unauthenticatedUserRole: identityPool.unauthenticatedRole, + } + }, +}) +``` + +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: public, provider: identityPool }]) { + id: ID! + title: String! +} +``` + + + +In the Amplify Library's client configuration file (`amplifyconfiguration.json`) set `allowGuestAccess` to `true`. This lets the Amplify Library use the unauthenticated role from your Cognito identity pool when your user isn't logged in. + +```json +{ + "Auth": { + "Cognito": { + "userPoolId": "YOUR_USER_POOL_ID", + "userPoolClientId": "YOUR_USER_POOL_CLIENT_ID", + "identityPoolId": "YOUR_IDENTITY_POOL_ID", + "allowGuestAccess": true + }, + }, + "API": { + "GraphQL": { + "endpoint": "YOUR_API_ENDPOINT", + "region": "YOUR_API_REGION", + "defaultAuthMode": "YOUR_DEFAULT_AUTHORIZATION_MODE", + }, + }, +} +``` + + + + + + + + +### Per-user / owner-based data access + +To restrict a record's access to a specific user, use the `owner` authorization strategy. When `owner` authorization is configured, only the record's `owner` is allowed the specified operations. + +```graphql +# The "owner" of a Todo is allowed to create, read, update, and delete their own todos +type Todo @model @auth(rules: [{ allow: owner }]) { + content: String +} + +# The "owner" of a Todo record is only allowed to create, read, and update it. +# The "owner" of a Todo record is denied to delete it. +type Todo + @model + @auth(rules: [{ allow: owner, operations: [create, read, update] }]) { + content: String +} +``` + +Behind the scenes, Amplify will automatically add a `owner: String` field to each record which contains the record owner's identity information upon record creation. + +By default, the Cognito user pool's user information is populated into the `owner` field. The value saved includes `sub` and `username` in the format `::`. The API will authorize against the full value of `::` or `sub` / `username` separately and return `username`. You can alternatively configure [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). + +You can override the `owner` field to your own preferred field, by specifying a custom `ownerField` in the authorization rule. + + + +Do not set `ownerField` to your `@primaryKey` field or `id` field if no primary key is specified. If you want to query by the `ownerField`, use an `@index` on that `ownerField` to create a secondary index. + + + +```graphql +type Todo @model @auth(rules: [{ allow: owner, ownerField: "author" }]) { + content: String #^^^^^^^^^^^^^^^^^^^^ + author: String # record owner information now stored in "author" field +} +``` + + + +**By default, owners can reassign the owner of their existing record to another user.** + +To prevent an owner from reassigning their record to another user, protect the owner field (by default `owner: String`) with a [field-level authorization rule](#field-level-authorization-rules). For example, in a social media app, you would want to prevent Alice from being able to reassign Alice's Post to Bob. + +```graphql +type Todo @model @auth(rules: [{ allow: owner }]) { + id: ID! + description: String + owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }]) +} +``` + + + +### Multi-user data access + +If you want to grant a set of users access to a record, you can override the `ownerField` to a list of owners. Use this if you want a dynamic set of users to have access to a record. + +```graphql +type Todo @model @auth(rules: [{ allow: owner, ownerField: "authors" }]) { + content: String + authors: [String] +} +``` + +In the example above, upon record creation, the `authors` list is populated with the creator of the record. The creator can then update the `authors` field with additional users. Any user listed in the `authors` field can access the record. + +### Signed-in user data access + +To restrict a record's access to every signed-in user, use the `private` authorization strategy. + +> If you want to restrict a record's access to a specific user, see [Per-user / owner-based data access](#per-user--owner-based-data-access). `private` authorization applies the authorization rule to **every** signed-in user access. + +```graphql +type Todo @model @auth(rules: [{ allow: private }]) { + content: String +} +``` + +In the example above, anyone with a valid JWT token from Cognito user pool are allowed to access all Todos. + +You can also override the authorization provider. In the example below, you can use an "Authenticated Role" from the Cognito identity pool for granting access to signed-in users. + + + + +When you run `amplify add auth`, the Amplify CLI generates scoped down IAM policies for the "Authenticated role" in Cognito identity pool automatically. + +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: private, provider: iam }]) { + id: ID! + title: String! +} +``` + + + +Designate an Amazon Cognito identity pool role for authenticated identities by setting the `identityPoolConfig` property: + +```ts +// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well +import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha'; + +const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', { + allowUnauthenticatedIdentities: true, + authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({ + userPool: , + userPoolClient: , + })] }, +}); + +new AmplifyGraphqlApi(this, "MyNewApi", { + definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + }, + identityPoolConfig: { + identityPoolId: identityPool.identityPoolId, + authenticatedUserRole: identityPool.authenticatedRole, + unauthenticatedUserRole: identityPool.unauthenticatedRole, + } + }, +}) +``` + +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: private, provider: identityPool }]) { + id: ID! + title: String! +} +``` + + + + + +In addition, you can also use OpenID Connect with `private` authorization. See [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). + +**Note:** If you have a connected child model that allows `private` level access, any user authorized to fetch it from the parent model will be able to read the connected child model. For example, + +```graphql +type Todo @model @auth(rules: [{ allow: owner }]) { + id: ID! + name: String! + task: [Task] @hasMany +} + +type Task + @model + @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) { + id: ID! + description: String! +} +``` + +In the above relationship, the owner of a `Todo` record can query all the tasks connected to it, since the `Task` model allows `private` read access. + +### User group-based data access + +To restrict access based on user groups, use the `group` authorization strategy. + +**Static group authorization**: When you want to restrict access to a specific set of user groups, provide the group names in the `groups` parameter. + +```graphql +type Salary @model @auth(rules: [{ allow: groups, groups: ["Admin"] }]) { + id: ID! + wage: Int + currency: String +} +``` + +In the example above, only users that are part of the "Admin" user group are granted access to the Salary model. + + +**Dynamic group authorization**: When you want to restrict access to a set of user groups. + +```graphql +# Dynamic group authorization with multiple groups +type Post @model @auth(rules: [{ allow: groups, groupsField: "groups" }]) { + id: ID! + title: String + groups: [String] +} + +# Dynamic group authorization with a single group +type Post @model @auth(rules: [{ allow: groups, groupsField: "group" }]) { + id: ID! + title: String + group: String +} +``` + +With dynamic group authorization, each record contains an attribute specifying what Cognito groups should be able to access it. Use the `groupsField` argument to specify which attribute in the underlying data store holds this group information. To specify that a single group should have access, use a field of type `String`. To specify that multiple groups should have access, use a field of type `[String]`. + +By default, `group` authorization leverages Amazon Cognito user pool groups but you can also use OpenID Connect with `group` authorization. See [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). + +**Known limitations for real-time subscriptions when using dynamic group authorization**: + +1. If you authorize based on a single group per record, then subscriptions are only supported if the user is part of 5 or fewer user groups +2. If you authorize via an array of groups (`groups: [String]` example above), + +- subscriptions are only supported if the user is part of 20 or fewer groups +- you can only authorize 20 or fewer user groups per record + + +### Custom authorization rule + +You can define your own custom authorization rule with a Lambda function. + +```graphql +type Salary @model @auth(rules: [{ allow: custom }]) { + id: ID! + wage: Int + currency: String +} +``` + +The Lambda function of choice will receive an authorization token from the client and execute the desired authorization logic. The AppSync GraphQL API will receive a payload from Lambda after invocation to allow or deny the API call accordingly. + + + + +Configure the GraphQL API with the Lambda authorization mode, run the following command in your Terminal: + +```bash +amplify update api +``` + +``` +? Select a setting to edit: +> Authorization modes + +> Lambda + +? Choose a Lambda source: +> Create a new Lambda function +``` + + + + +To configure a Lambda function as the authorization mode, set the `lambdaConfig` in the CDK construct. Use the `ttl` to designate the toke expiry time. + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'AWS_LAMBDA', + lambdaConfig: { + function: new lambda.Function(this, 'MyAuthLambda', { + code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/auth')), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_18_X + }), + ttl: cdk.Duration.seconds(10) + } + } +}); +``` + +You can leverage this Lambda function code template as a starting point to author your authorization handler code: + +```js +// This is sample code. Please update this to suite your schema + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +exports.handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + const { + authorizationToken, + requestContext: { apiId, accountId } + } = event; + const response = { + isAuthorized: authorizationToken === 'custom-authorized', + resolverContext: { + // eslint-disable-next-line spellcheck/spell-checker + userid: 'user-id', + info: 'contextual information A', + more_info: 'contextual information B' + }, + deniedFields: [ + `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`, + `Mutation.createEvent` + ], + ttlOverride: 300 + }; + console.log(`response >`, JSON.stringify(response, null, 2)); + return response; +}; +``` + + + + +You can use the default Amplify provided template as a starting point for your custom authorization rule. The authorization Lambda function receives: + +```json +{ + "authorizationToken": "ExampleAuthToken123123123", # Authorization token specified by client + "requestContext": { + "apiId": "aaaaaa123123123example123", # AppSync API ID + "accountId": "111122223333", # AWS Account ID + "requestId": "f4081827-1111-4444-5555-5cf4695f339f", + "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", # GraphQL query + "operationName": "MyQuery", # GraphQL operation name + "variables": {} # any additional variables supplied to the operation + } +} +``` + +Your Lambda authorization function needs to return the following JSON: + +```json +{ + // required + "isAuthorized": true, // if "false" then an UnauthorizedException is raised, access is denied + "resolverContext": { "banana": "very yellow" }, // JSON object visible as $ctx.identity.resolverContext in VTL resolver templates + + // optional + "deniedFields": ["TypeName.FieldName"], // Forces the fields to "null" when returned to the client + "ttlOverride": 10 // The number of seconds that the response should be cached for. Overrides default specified in "amplify update api" +} +``` + +Review the Amplify Library documentation to set the custom authorization token for [GraphQL API](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules#aws-lambda) and [DataStore](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/#configure-custom-authorization-logic-with-aws-lambda). + +## Configure multiple authorization rules + +When combining multiple authorization rules, they are "logically OR"-ed. + +```graphql +type Post + @model + @auth( + rules: [ + { allow: public, operations: [read], provider: iam } + { allow: owner } + ] + ) { + title: String + content: String +} +``` + +```js +import { createPost } from './graphql/mutations'; +import { listPosts } from './graphql/queries'; + +// Creating a post is restricted to Cognito User Pools +const newPostResult = await client.graphql({ + query: queries.createPost, + variables: { input: { title: 'Hello World' } }, + authMode: 'userPool' +}); + +// Listing posts is available to all users (verified by IAM) +const listPostsResult = await client.graphql({ + query: queries.listPosts, + authMode: 'iam' +}); +``` + +In the example above: + +- any user (signed in or not, verified by IAM) is allowed to read all posts +- owners are allowed to create, read, update, and delete their own posts. + +If you are using DataStore and have multiple authorization rules, you can let DataStore automatically determine the best authorization mode client-side. Review how to [Configure Multiple Authorization Types](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/#configure-multiple-authorization-types) on DataStore for more details. + +## Field-level authorization rules + +When an authorization rule is added to a field, it'll strictly define the authorization rules applied on the field. Field-level authorization rules **do not** inherit model-level authorization rules. Meaning, only the specified field-level authorization rule is applied. + +```graphql +type Employee + @model + @auth(rules: [{ allow: private, operations: [read] }, { allow: owner }]) { + name: String + email: String + ssn: String @auth(rules: [{ allow: owner }]) +} +``` + +In the example above: + +- Owners are allowed to create, read, update, and delete Employee records they own +- Any signed in user has read access +- Any signed in user can read data with the exception of the `ssn` field. This field only has owner auth applied, the field-level auth rule means that model-level auth rules are not applied + + + +To prevent sensitive data from being sent over subscriptions, the GraphQL Transformer needs to alter the response of mutations for those fields by setting them to null. Therefore, to facilitate field-level authorization with subscriptions, you need to either apply field-level authorization rules to all **required** fields, make the other fields nullable, or disable subscriptions by setting it to public or off. + + + +In the example above: + +- **any signed in user** is allowed to read the list of employees' `name` and `email` fields +- **only the employee/owner themselves** have CRUD access to their `ssn` field + + + +To prevent unintended loss of data, the user or role that attempts to `delete` a record should have delete permissions on every field of the `@model` annotated GraphQL type. For example, in the schema below: + +```graphql +type Todo + @model + @auth( + rules: [ + { allow: private, provider: iam } + { allow: groups, groups: ["Admin"] } + ] + ) { + id: ID! + name: String! + @auth( + rules: [ + { allow: private, provider: iam } + { allow: groups, groups: ["Admin"] } + ] + ) + description: String @auth(rules: [{ allow: private, provider: iam }]) +} +``` + +Since the `description` field is not accessible by "Admin" Cognito group users, they cannot delete any `Todo` records. + + + +## Advanced + +### Review and print access control matrix + +Verify your API's access control matrix, by running the following command: + +```bash +amplify status api -acm Blog +``` + +```console +iam:public + ┌─────────┬────────┬──────┬────────┬────────┐ + │ (index) │ create │ read │ update │ delete │ + ├─────────┼────────┼──────┼────────┼────────┤ + │ title │ false │ true │ false │ false │ + │ content │ false │ true │ false │ false │ + └─────────┴────────┴──────┴────────┴────────┘ +userPools:owner:owner + ┌─────────┬────────┬──────┬────────┬────────┐ + │ (index) │ create │ read │ update │ delete │ + ├─────────┼────────┼──────┼────────┼────────┤ + │ title │ true │ true │ true │ true │ + │ content │ true │ true │ true │ true │ + └─────────┴────────┴──────┴────────┴────────┘ +``` + +### Use IAM authorization within the AppSync console + + + + + +IAM-based `@auth` rules are scoped down to only work with Amplify-generated IAM roles. To access the GraphQL API with IAM authorization within your AppSync console, you need to explicitly allow list the IAM user's name. Add the allow-listed IAM users by adding them to `amplify/backend/api//custom-roles.json`. (Create the `custom-roles.json` file if it doesn't exist). Append the `adminRoleNames` array with the IAM role or user names: + +```json +{ + "adminRoleNames": [""] +} +``` + + + + +To grant any IAM principal (AWS Resource, IAM role, IAM user, etc) access, **with the exception of Amazon Cognito identity pool roles**, to this GraphQL API in CDK, you need to enable IAM authorization mode via the `iamConfig` property of the CDK construct. + +```typescript +const userRole = Role.fromRoleName( + this, + 'MyUserRole', + '' +); + +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + }, + iamConfig: { + // Set this value to true. + enableIamAuthorizationMode: true + } + } +}); +``` + +These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. + + + +These "Admin Roles" have special access privileges that are scoped based on their IAM policy instead of any particular `@auth` rule. + +### Using OIDC authorization provider + +`private`, `owner`, and `group` authorization can be configured with an OpenID Connect (OIDC) authorization mode. Add `provider: oidc` to the authorization rule. + + + + +Upon the next `amplify push`, Amplify CLI prompts you for the _OpenID Connect provider domain_, _Client ID_, _Issued at TTL_, and _Auth Time TTL_. + + + + +Use the `oidcConfig` property to configure the _OpenID Connect provider domain_, _Client ID_, _Issued at TTL_, and _Auth Time TTL_. + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'OPENID_CONNECT', + oidcConfig: { + oidcIssuerUrl: '...', + oidcProviderName: '...', + tokenExpiryFromAuth: '...', + tokenExpiryFromIssue: '...', + clientId: '...' + } + } +}); +``` + + + + +```graphql +type Todo + @model + @auth( + rules: [ + { allow: owner, provider: oidc, identityClaim: "user_id" } + { allow: private, provider: oidc } + { allow: group, provider: oidc, groupClaim: "user_groups" } + ] + ) { + content: String +} +``` + +The example above highlights the supported authorization strategies with `oidc` authorization provider. For `owner` and `group` authorization, you also need to [specify a custom identity and group claim](#configure-custom-identity-and-group-claims). + +### Configure custom identity and group claims + +`@auth` supports using custom claims if you do not wish to use the default Amazon Cognito-provided "cognito:groups" or the double-colon-delimited claims, "sub::username", from your JWT token. This can be helpful if you are using tokens from a 3rd party OIDC system or if you wish to populate a claim with a list of groups from an external system, such as when using a [Pre Token Generation Lambda Trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html) which reads from a database. To use custom claims specify `identityClaim` or `groupClaim` as appropriate like in the example below: + +```graphql +type Post + @model + @auth( + rules: [ + { allow: owner, identityClaim: "user_id" } + { allow: groups, groups: ["Moderator"], groupClaim: "user_groups" } + ] + ) { + id: ID! + owner: String + postname: String + content: String +} +``` + +In this example the record owner will check against a `user_id` claim. Similarly, if the `user_groups` claim contains a "Moderator" string then access will be granted. + +### Grant Lambda function access to GraphQL API + +Lambda functions' IAM execution role do not immediately grant access to Amplify's GraphQL API because the API operates on a "deny-by-default"-basis. Access need to be explicitly granted. Depending on how your function is deployed, the workflow slightly differ + + + + + +If you grant a Lambda function in your Amplify project access to the GraphQL API via `amplify update function`, then the Lambda function's IAM execution role is allow-listed to honor the permissions granted on the `Query`, `Mutation`, and `Subscription` types. + +Therefore, these functions have special access privileges that are scoped based on their IAM policy instead of any particular `@auth` rule. + + + +Once you grant a function access to the GraphQL API, it is required to redeploy the API to apply the permissions. To do so, run the command `amplify api gql-compile --force` before deployment via `amplify push`. + + + + + + +To grant any IAM principal (AWS Resource, IAM role, IAM user, etc), **with the exception of Amazon Cognito identity pool roles**, to this GraphQL API in CDK, you need to enable IAM authorization mode on the CDK construct. + +```typescript +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + }, + iamConfig: { + // Must be set to `true`. Then grant your Lambda function's execution role access to the API + enableIamAuthorizationMode: true + } + } +}); +``` + +These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. + + + + + +To grant an external AWS Resource or an IAM role access to this GraphQL API, you need to explicitly list the IAM role's name or the AWS Resource's name by adding it to `amplify/backend/api//custom-roles.json`. (Create the `custom-roles.json` file if it doesn't exist). Append the `adminRoleNames` array with the IAM role name or AWS Resource name: + +```json +{ + "adminRoleNames": ["", ""] +} +``` + +You can use the symbol `${env}` to reference the current Amplify CLI environment. + +These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. + + + + + + + +Refer to the [sample code](/[platform]/build-a-backend/graphqlapi/connect-from-server-runtime/#iam-authorization) to learn how to sign the request to call the GraphQL API using IAM authorization. + + + +### How it works + +Definition of the `@auth` directive: + +```graphql +# When applied to a type, augments the application with +# owner and group-based authorization rules. +directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION +input AuthRule { + allow: AuthStrategy! + provider: AuthProvider + ownerField: String # defaults to "owner" when using owner auth + identityClaim: String # defaults to "sub::username" when using owner auth + groupClaim: String # defaults to "cognito:groups" when using Group auth + groups: [String] # Required when using Static Group auth + groupsField: String # defaults to "groups" when using Dynamic Group auth + operations: [ModelOperation] # Required for finer control +} + +enum AuthStrategy { + owner + groups + private + public + custom +} +enum AuthProvider { + apiKey + iam + oidc + userPools + function +} +enum ModelOperation { + create + update + delete + read # Short-hand to allow "get", "list", "sync", "listen", and "search" + get # Retrieves an individual item + list # Retrieves a list of items + sync # Enables ability to sync offline/online changes (including via DataStore) + listen # Subscribes to real-time changes + search # Enables ability to search using @searchable directive +} +``` + +Authorization rules consists of: + +- **authorization strategy** (`allow`): who the authorization rule applies to +- **authorization provider** (`provider`): which mechanism is used to apply the authorization rule (API Key, IAM, Amazon Cognito user pool, OIDC) +- **authorized operations** (`operations`): which operations are allowed for the given strategy and provider. If not specified, `create`, `read`, `update`, and `delete` operations are allowed. + - **`read` operation**: `read` operation can be replaced with `get`, `list`, `sync`, `listen`, and `search` for a more granular query access + + + +If you use DataStore instead of the API category to connect to your AppSync API, then you must allow `listen` and `sync` operations for your data model. + + + +**API Keys** are best used for public APIs (or parts of your schema which you wish to be public) or prototyping, and you must specify the expiration time before deploying. **IAM** authorization uses [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) to make request with policies attached to Roles. OIDC tokens provided by **Amazon Cognito user pool** or **3rd party OpenID Connect** providers can also be used for authorization, and enabling this provides a simple access control requiring users to authenticate to be granted top level access to API actions. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx new file mode 100644 index 00000000000..1d29c31e22b --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx @@ -0,0 +1,1266 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Customize your data model', + description: 'Customize your data model with primary keys, secondary indexes, and model relationships.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/data-modeling/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + + +Amplify automatically creates Amazon DynamoDB database tables for GraphQL types annotated with the `@model` directive in your GraphQL schema. You can create relations between the data models via the `@hasOne`, `@hasMany`, `@belongsTo`, and `@manyToMany` directives. + +## Setup database tables + +The following GraphQL schema automatically creates a database table for "Todo". `@model` will also automatically add an `id` field as a primary key to the database table. _See [Configure a primary key](#configure-a-primary-key) to learn how to customize the primary key._ + +```graphql +type Todo @model { + content: String +} +``` + +Upon `amplify push` or `cdk deploy`, Amplify deploys the Todo database table and a corresponding GraphQL API to perform create, read, update, delete, and list operations. + +In addition, `@model` also adds the helper fields `createdAt` and `updatedAt` to your type. The values for those fields are read-only by clients unless explicitly overwritten. See [Customize creation and update timestamps](#customize-creation-and-update-timestamps) to learn more. + +Try listing all the todos by executing the following query: + +```graphql +query QueryAllTodos { + listTodos() { + todos { + items { + id + content + createdAt + updatedAt + } + } + } +} +``` + + + +```js +import { Amplify } from 'aws-amplify'; +import { generateClient } from 'aws-amplify/api'; +import config from './amplifyconfiguration.json'; +import { listTodos } from './graphql/queries'; + +const client = generateClient(); + +Amplify.configure(config); + +try { + const result = await client.graphql({ query: listTodos }); + const todos = result.data.listTodos; +} catch (res) { + const { errors } = res; + console.error(errors); +} +``` + + + +### Configure a primary key + +Every GraphQL type with the `@model` directive will automatically have an `id` field set as the primary key. You can override this behavior by marking another required field with the `@primaryKey` directive. + +In the example below, `todoId` is the database's primary key and an `id` field will no longer be created automatically anymore by the `@model` directive. + +```graphql +type Todo @model { + todoId: ID! @primaryKey + content: String +} +``` + +Without any further configuration, you'll only be able to query for a Todo via an exact equality match of its primary key field. In the example above, this is the `todoId` field. + +> Note: After a primary key is configured and deployed, you can't change it without deleting and recreating your database table. + +You can also specify "sort keys" to use a combination of different fields as a primary key. This also allows you to apply more advanced sorting and filtering conditions on the specified "sort key fields". + +```graphql +type Inventory @model { + productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) + warehouseID: ID! + InventoryAmount: Int! +} +``` + +The schema above will allow you to pass different conditions to query the correct inventory item: + +```graphql +query QueryInventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { + getInventory(productID: $productID, warehouseID: $warehouseID) { + productID + warehouseID + inventoryAmount + } +} +``` + + + +```js +import { getInventory } from './graphql/queries'; + +const result = await client.graphql({ + query: getInventory, + variables: { + productID: 'product-id', + warehouseID: 'warehouse-id' + } +}); +const inventory = result.data.getInventory; +``` + + + +### Configure a secondary index + +Amplify uses Amazon DynamoDB tables as the underlying data source for @model types. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `@index` directive to configure a secondary index. + +> **Amazon DynamoDB** is a key-value and document database that delivers single-digit millisecond performance at any scale but making it work for your access patterns requires a bit of forethought. DynamoDB query operations may use at most two attributes to efficiently query data. The first query argument passed to a query (the hash key) must use strict equality and the second attribute (the sort key) may use gt, ge, lt, le, eq, beginsWith, and between. DynamoDB can effectively implement a wide variety of access patterns that are powerful enough for the majority of applications. + +A secondary index consists of a "hash key" and, optionally, a "sort key". Use the "hash key" to perform strict equality and the "sort key" for greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), equals (eq), begins with, and between operations. + +```graphql +type Customer @model { + id: ID! + name: String! + phoneNumber: String + accountRepresentativeID: ID! @index +} +``` + +The example client query below allows you to query for "Customer" records based on their `accountRepresentativeID`: + +```graphql +query QueryCustomersForAccountRepresentative($accountRepresentativeID: ID!) { + customersByAccountRepresentativeID( + accountRepresentativeID: $accountRepresentativeID + ) { + customers { + items { + id + name + phoneNumber + } + } + } +} +``` + + + + +```js +import { customersByAccountRepresentativeID } from './graphql/queries'; + +const result = await client.graphql({ + query: customersByAccountRepresentativeID, + variables: { + accountRepresentativeID: 'account-rep-id' + } +}); +const customers = result.data.customersByAccountRepresentativeID; +``` + + + +You can also overwrite the `queryField` or `name` to customize the GraphQL query name, or secondary index name respectively: + +```graphql +type Customer @model { + id: ID! + name: String! + phoneNumber: String + accountRepresentativeID: ID! + @index(name: "byRepresentative", queryField: "customerByRepresentative") +} +``` + +```graphql +query QueryCustomersForAccountRepresentative($representativeId: ID!) { + customerByRepresentative(accountRepresentativeID: $representativeId) { + customers { + items { + id + name + phoneNumber + } + } + } +} +``` + + + +```js +import { customerByRepresentative } from './graphql/queries'; + +const result = await client.graphql({ + query: customerByRepresentative, + variables: { + accountRepresentativeID: 'account-rep-id' + } +}); +const customer = result.data.customerByRepresentative; +``` + + + +To optionally configure sort keys, provide the additional fields in the `sortKeyFields` parameter: + +```graphql +type Customer @model @auth(rules: [{ allow: public }]) { + id: ID! + name: String! @index(name: "byNameAndPhoneNumber", sortKeyFields: ["phoneNumber"], queryField: "customerByNameAndPhone") + phoneNumber: String + accountRepresentativeID: ID! @index +``` + +The example client query below allows you to query for "Customer" based on their `name` and filter based on `phoneNumber`: + +```graphql +query MyQuery { + customerByNameAndPhone(phoneNumber: { beginsWith: "+1" }, name: "Rene") { + items { + id + name + phoneNumber + } + } +} +``` + + + +```js +import { customerByNameAndPhone } from './graphql/queries'; + +const result = await client.graphql({ + query: customerByNameAndPhone, + variables: { + phoneNumber: { beginsWith: '+1' }, + name: 'Rene' + } +}); + +const customer = result.data.customerByNameAndPhone; +``` + + + + + +## Setup relationships between models + +Create "has one", "has many", "belongs to", and "many to many" relationships between `@model` types. + +| Relationship | Description | +| --- | --- | +| `@hasOne` | Create a one-directional one-to-one relationship between two models. For example, a Project "has one" Team. This allows you to query the team from the project record. | +| `@hasMany` | Create a one-directional one-to-many relationship between two models. For example, a Post has many comments. This allows you to query all the comments from the post record. | +| `@belongsTo` | Use a "belongs to" relationship to make a "has one" or "has many" relationship bi-directional. For example, a Project has one Team and a Team belongs to a Project. This allows you to query the team from the project record and vice versa. | +| `@manyToMany` | Configures a "join table" between two models to facilitate a many-to-many relationship. For example, a Blog has many Tags and a Tag has many Blogs. | + +### Has One relationship + +import gqlv2callout from '/src/fragments/cli/gqlv2callout.mdx'; + + + +Create a one-directional one-to-one relationship between two models using the `@hasOne` directive. + +In the example below, a Project has a Team. + +```graphql +type Project @model { + id: ID! + name: String + team: Team @hasOne +} + +type Team @model { + id: ID! + name: String! +} +``` + +This generates queries and mutations that allow you to retrieve the related record from the source record: + +```graphql +mutation CreateProject { + createProject(input: { projectTeamId: "team-id", name: "Some Name" }) { + team { + name + id + } + name + id + } +} +``` + + +```js +import { createProject } from './graphql/mutations'; + +const result = await client.graphql({ + query: createProject, + variables: { + input: { projectTeamId: 'team-id', name: 'Some Name' } + } +}); + +const project = result.data.createProject; +``` + +To customize the field to be used for storing the relationship information, set the `fields` array argument and matching it to a field on the type: + +```graphql +type Project @model { + id: ID! + name: String + teamID: ID + team: Team @hasOne(fields: ["teamID"]) +} + +type Team @model { + id: ID! + name: String! +} +``` + +In this case, the Project type has a `teamID` field added as an identifier for the team. @hasOne can then get the connected Team object by querying the Team table with this `teamID`: + +```graphql +mutation CreateProject { + createProject(input: { name: "New Project", teamID: "a-team-id" }) { + id + name + team { + id + name + } + } +} +``` + + +```js +import { createProject } from './graphql/mutations'; + +const result = await client.graphql({ + query: createProject, + variables: { + input: { + teamID: 'team-id', + name: 'New Project' + } + } +}); +const project = result.data.createProject; +``` + +A `@hasOne` relationship always uses a reference to the primary key of the related model, by default `id` unless overridden with the [`@primaryKey` directive](#configure-a-primary-key). + +### Has Many relationship + + + +Create a one-directional one-to-many relationship between two models using the `@hasMany` directive. + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + content: String! +} +``` + +This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record: + +```graphql +mutation CreatePost { + createPost(input: { title: "Hello World!!" }) { + title + id + comments { + items { + id + content + } + } + } +} +``` + + +```js +import { createPost } from './graphql/mutations'; + +const result = await client.graphql({ + query: createPost, + variables { + input: { title: 'Hello World!!' }, + } +}); +const post = result.data.createPost; +const comments = post.comments.items; +``` + +Under the hood, `@hasMany` configures a default secondary index on the related table to enable you to query the related model from the source model. + +To customize the specific secondary index used for the "has many" relationship, create an `@index` directive on the field in the related table where you want to assign the secondary index. + +Next, provide the secondary index with a `name` attribute and a value. Optionally, you can configure a “sort key” on the secondary index by providing a `sortKeyFields` attribute and a designated field as its value. + +On the `@hasMany` configuration, pass in the name value from your secondary index as the value for the `indexName` parameter. Then, pass in the respective `fields` that match the connected index. + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) +} + +type Comment @model { + id: ID! + postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) + content: String! +} +``` + +In this case, the Comment type has a `postID` field added to store the reference of Post record. The `id` field referenced by `@hasMany` is the `id` on the `Post` type. `@hasMany` can then get the connected Comment object by querying the Comment table's secondary index "byPost" with this `postID`: + +```graphql +mutation CreatePost { + createPost(input: { title: "Hello world!" }) { + comments { + items { + postID + content + id + } + } + title + id + } +} +``` + +```js +import { createPost, createComment } from './graphql/mutations'; +import { getPost } from './graphql/mutations'; + +// create post +const result = await client.graphql({ + query: createPost, + variables: { + input: { title: 'Hello World!!' } + } +}); +const post = result.data.createPost; + +// create comment +await client.graphql({ + query: createComment, + variables: { + input: { content: 'Hi!', postID: post.id } + } +}); + +// get post +const result = await client.graphql({ + query: getPost, + variables: { id: post.id } +}); + +const postWithComments = result.data.createPost; +const postComments = postWithComments.comments.items; // access comments from post +``` + + +### Belongs To relationship + +Make a "has one" or "has many" relationship bi-directional with the `@belongsTo` directive. + + + + + +For 1:1 relationships, the @belongsTo directive solely facilitates the ability for you to query from both sides of the relationship. When updating a bi-directional hasOne relationship, you must update both sides of the relationship with corresponding IDs. + +```graphql +type Project @model { + id: ID! + name: String + team: Team @hasOne +} + +type Team @model { + id: ID! + name: String! + project: Project @belongsTo +} +``` + +This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: + +```graphql +mutation CreateProject { + createProject(input: { name: "New Project", teamID: "a-team-id" }) { + id + name + team { + # query team from project + id + name + project { + # bi-directional query: team to project + id + name + } + } + } +} +``` + +```js +import { createProject, createTeam, updateTeam } from './graphql/mutations'; + +// create team +const result = await client.graphql({ + query: createTeam, + variables: { + input: { name: 'New Team' } + } +}); +const team = result.data.createTeam; + +// create project +const result = await client.graphql({ + query: createProject, + variables: { + input: { name: 'New Project', projectTeamId: team.id } + } +}); +const project = result.data.createProject; +const projectTeam = project.team; // access team from project + +// update team +const updateTeamResult = await client.graphql({ + query: updateTeam, + variables: { + input: { id: team.id, teamProjectId: project.id } + } +}); + +const updatedTeam = updateTeamResult.data.updateTeam; +const teamProject = updatedTeam.project; // access project from team +``` + + + + + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + content: String! + post: Post @belongsTo +} +``` + +This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: + +```graphql +mutation CreatePost { + createPost(input: { title: "Hello World!!" }) { + title + id + comments { + # query comments from the post + items { + id + content + post { + # bi-directional query: comment to post + id + title + } + } + } + } +} +``` + +```js +import { createPost, createComment } from './graphql/mutations'; +import { getPost } from './graphql/mutations'; + +// create post +const result = await client.graphql({ + query: createPost, + variables: { + input: { title: 'Hello World!!' } + } +}); +const post = result.data.createPost; + +// create comment +await client.graphql({ + query: createComment, + variables: { + input: { content: 'Hi!', postID: post.id } + } +}); + +// get post +const result = await client.graphql({ + query: getPost, + variables: { id: post.id } +}); + +const postWithComments = result.data.createPost; +const postComments = postWithComments.comments.items; // access comments from post + +const commentPost = postComments[0].post; // access post from comment; +``` + + + + + +`@belongsTo` can be used without the `fields` argument. In those cases, a field is automatically generated to reference the parent’s primary key. + +Alternatively, you set up a custom field to store the reference of the parent object. An example bidirectional “has many” relationship is shown below. + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) +} + +type Comment @model { + id: ID! + postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) + content: String! + post: Post @belongsTo(fields: ["postID"]) +} +``` + +> Note: The `@belongsTo` directive requires that a `@hasOne` or `@hasMany` relationship already exists from parent to the related model. + +### Many-to-many relationship + +Create a many-to-many relationship between two models with the `@manyToMany` directive. Provide a common `relationName` on both models to join them into a many-to-many relationship. + +```graphql +type Post @model { + id: ID! + title: String! + content: String + tags: [Tag] @manyToMany(relationName: "PostTags") +} + +type Tag @model { + id: ID! + label: String! + posts: [Post] @manyToMany(relationName: "PostTags") +} +``` + +Under the hood, the `@manyToMany` directive will create a "join table" named after the `relationName` to facilitate the many-to-many relationship. This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: + +```graphql +mutation CreatePost { + createPost(input: { title: "Hello World!!" }) { + id + title + content + tags { + # queries the "join table" PostTags + items { + tag { + # related Tag records from Post + id + label + posts { + # queries the "join table" PostTags + items { + post { + # related Post records from Tag + id + title + content + } + } + } + } + } + } + } +} +``` + +```js +import { createPost, createTag, createPostTags } from './graphql/mutations'; +import { listPosts } from './graphql/queries'; + +// create post +const result = await client.graphql({ + query: createPost, + variables: { + input: { title: 'Hello World' } + } +}); +const post = result.data.createPost; + +// create tag +const tagResult = await client.graphql({ + query: createTag, + variables: { + input: { + label: 'My Tag' + } + } +}); +const tag = tagResult.data.createTag; + +// connect post and tag +await client.graphql({ + query: createPostTags, + variables: { + input: { + postId: post.id, + tagId: tag.id + } + } +}); + +// get posts +const listPostsResult = await client.graphql({ query: listPosts }); +const posts = listPostsResult.data.listPosts; + +const postTags = posts[0].tags; // access tags from post +``` + +## Assign default values for fields + +You can use the `@default` directive to specify a default value for optional [scalar type fields](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html) such as `Int`, `String`, and more. + +```graphql +type Todo @model { + content: String @default(value: "My new Todo") + # Note: all "value" parameters must be passed as a string value. + # Under the hood, Amplify will parse the string values into respective types. + # For example, to set a default value for an integer field, + # you must pass in `"0"` instead of `0` without the double-quotes. + likes: Int @default(value: "0") # +} +``` + +If you create a new Todo and don't supply a `content` input, Amplify will ensure that `My new Todo` is auto populated as a value. When `@default` is applied, non-null assertions using `!` are disregarded. For example, `String!` is treated the same as `String`. + +## Server-side filtering for subscriptions + +A server-side subscription filter expression is automatically generated for any `@model` type. + +```graphql +type Task @model { + title: String! + description: String + type: String + priority: Int +} +``` + +You can filter the subscriptions server-side by passing a filter expression. For example: If you want to subscribe to tasks of type `Security` and priority greater than `5`, you can set the `filter` argument accordingly. + +```graphql +subscription OnCreateTask { + onCreateTask( + filter: { and: [{ type: { eq: "Security" } }, { priority: { gt: 5 } }] } + ) { + title + description + type + priority + } +} +``` + +```js +import { onCreateTask } from './graphql/subscriptions'; + +const subscription = client.graphql({ + query: onCreateTask, + variables: { + filter: { + and: [ + { type: { eq: "Security" } } + { priority: { gt: 5 } } + ] + } + } +}).subscribe({ + next: ({ data }) => console.log(data), + error: (error) => console.warn(error) +}); +``` + +If you want to get all subscription events, don’t pass any `filter` parameters. + + + +**Important**: Passing an empty object `{}` as a filter is NOT recommended. Using `{}` as a filter might cause inconsistent behavior based on your data model's authorization rules. + + + + + +## Advanced + +### Rename generated queries, mutations, and subscriptions + +You can override the names of any `@model`-generated GraphQL queries, mutations, and subscriptions by supplying the desired name. + +```graphql +type Todo @model(queries: { get: "queryFor" }) { + name: String! + description: String +} +``` + +In the example above, you will be able to run a `queryForTodo` query to get a single Todo element. + +### Disable generated queries, mutations, and subscriptions + +You can disable specific operations by assigning their value to `null`. + +```graphql +type Todo @model(queries: { get: null }, mutations: null, subscriptions: null) { + name: String! + description: String +} +``` + +The example above disables the `getTodo` query, all mutations, and all subscriptions while allowing the generation of other queries such as `listTodo`. + +### Creating a custom query + +You can disable the `get` query and create a custom query that enables us to retrieve a single Todo model. + +```graphql +type Query { + getMyTodo(id: ID!): Todo @function(name: "getmytodofunction-${env}") +} +``` + +The example above creates a custom query that utilizes the `@function` directive to call a Lambda function for this query. + +For the type definitions of queries, mutations, and subscriptions, see [Type Definitions of the `@model` Directive](#type-definition-of-the-`@model`-directive). + +### Customize creation and update timestamps + +The `@model` directive automatically adds `createdAt` and `updatedAt` timestamps to each entity. The timestamp field names can be changed by passing timestamps attribute to the directive. + +```graphql +type Todo + @model(timestamps: { createdAt: "createdOn", updatedAt: "updatedOn" }) { + name: String! + description: String +} +``` + +For example, the schema above will allow you to query for the following contents: + +```graphql +type Todo { + id: ID! + name: String! + description: String + createdOn: AWSDateTime! + updatedOn: AWSDateTime! +} +``` + +### Modify subscriptions (real-time updates) access level + +By default, real-time updates are on for all `@model` types, which means customers receive real-time updates and authorization rules are applied during initial connection time. You can also turn off subscriptions for that model or make the real-time updates public, receivable by all subscribers. + +```graphql +type Todo + @model(subscriptions: { level: off }) { # or level: public + name: String! + description: String +} +``` + +### Create multiple relationships between two models + +You need to explicitly specify the connection field names if relational directives are used to create two connections of the same type between the two models. + +```graphql +type Individual @model { + id: ID! + homeAddress: Address @hasOne + shippingAddress: Address @hasOne +} + +type Address @model { + id: ID! + homeIndividualID: ID + shippingIndividualID: ID + homeIndividual: Individual @belongsTo(fields: ["homeIndividualID"]) + shipIndividual: Individual @belongsTo(fields: ["shippingIndividualID"]) +} +``` + +### Relationships to a model with a composite primary key + +When a primary key is defined by a _sort key_ in addition to the _hash key_, then it's called a **composite primary key**. + +If you explicitly define the `fields` argument on the `@hasOne`, `@hasMany`, or `@belongsTo` directives and reference a model that has a composite primary key, then you must set the values in the `fields` argument in a specific order: + +- The first value should always be the primary key of the related model. +- Remaining values should match the `sortKeyFields` specified in the `@primaryKey` directive of the related model. + + + + + +```graphql +type Project @model { + projectId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! + team: Team @hasOne(fields: ["teamId", "teamName"]) + teamId: ID # customized foreign key for child primary key + teamName: String # customized foreign key for child sort key +} + +type Team @model { + teamId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! +} +``` + + + + + +```graphql +type Project @model { + projectId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! + team: Team @hasOne(fields: ["teamId", "teamName"]) + teamId: ID # customized foreign key for child primary key + teamName: String # customized foreign key for child sort key +} + +type Team @model { + teamId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! + project: Project @belongsTo(fields: ["projectId", "projectName"]) + projectId: ID # customized foreign key for parent primary key + projectName: String # customized foreign key for parent sort key +} +``` + + + + + +```graphql +type Post @model { + postId: ID! @primaryKey(sortKeyFields: ["title"]) + title: String! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"]) +} + +type Comment @model { + commentId: ID! @primaryKey(sortKeyFields: ["content"]) + content: String! + postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key + postTitle: String # customized foreign key for parent sort key +} +``` + + + + + +```graphql +type Post @model { + postId: ID! @primaryKey(sortKeyFields: ["title"]) + title: String! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"]) +} + +type Comment @model { + commentId: ID! @primaryKey(sortKeyFields: ["content"]) + content: String! + post: Post @belongsTo(fields: ["postId", "postTitle"]) + postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key + postTitle: String # customized foreign key for parent sort key +} +``` + + + + + +### Generate a secondary index without a GraphQL query + +Because query creation against a secondary index is automatic, if you wish to define a secondary index that does not have a corresponding query in your API, set the `queryField` parameter to `null`. + +```graphql +type Customer @model { + id: ID! + name: String! + phoneNumber: String + accountRepresentativeID: ID! @index(queryField: null) +} +``` + +### Split GraphQL files + + +Amplify Studio does not support splitting GraphQL schemas. + +If using Amplify Studio, please follow the [Limitations](https://docs.amplify.aws/javascript/tools/console/data/data-model/#split-graphql-files) section of the Data Modeling documentation for Amplify Studio. + + +AWS Amplify supports splitting your GraphQL schema into separate `.graphql` files. + +You can start by creating a `amplify/backend/api//schema/` directory. As an example, you might split up the schema for a blog site by creating `Blog.graphql`, `Post.graphql`, and `Comment.graphql` files. + +You can then run `amplify api gql-compile` and the output build schema will include all the types declared across your schema files. + +As your project grows, you may want to organize your custom queries, mutations, and subscriptions depending on the size and maintenance requirements of your project. You can either consolidate all of them into one file or colocate them with their corresponding models. + +**Using a Single `Query.graphql` File** + +This method involves consolidating all queries into a single `Query.graphql` file. It is useful for smaller projects or when you want to keep all queries in one place. + +1. In the `amplify/backend/api//schema/` directory, create a file named `Query.graphql`. + +2. Copy all query type definitions from your multiple schema files into the `Query.graphql` file. + +3. Make sure all your queries are properly formatted and enclosed within a single `type Query { ... }` block. + +**Using the `extend` Keyword** + +Declaring a `Query` type in separate schema files will result in schema validation errors similar to the following when running `amplify api gql-compile`: + +```sh +🛑 Schema validation failed. + +There can be only one type named "Query". +``` + +Amplify GraphQL schemas support the `extend` keyword, which allows you to extend types with additional fields. In this case, it also allows you to split your custom queries, mutations, and subscriptions into multiple files. This may be more ideal for larger, more complex projects. + +1. Organize your GraphQL schema into multiple files as per your project's architecture. + +2. In one of the files (e.g., `schema1.graphql`), declare your type normally: + +```graphql +type Query { + # initial custom queries +} +``` + +3. In other schema files (e.g., `schema2.graphql`), use the `extend` keyword to add to the type: + +```graphql +extend type Query { + # additional custom queries +} +``` + +The order in which the Query types are extended does not affect the compilation of separate schema files. + + + +Declaring custom Query, Mutation, and/or Subscription with the same field names in another schema file will result in schema validation errors similar to the following: + +`🛑 Object type extension 'Query' cannot redeclare field getBlogById` + + + +## How it works + +### Model directive + +The `@model` directive will generate: + +- An Amazon DynamoDB table with PAY_PER_REQUEST billing mode enabled by default. +- An AWS AppSync DataSource configured to access the table above. +- An AWS IAM role attached to the DataSource that allows AWS AppSync to call the above table on your behalf. +- Up to 8 resolvers (create, update, delete, get, list, onCreate, onUpdate, onDelete) but this is configurable via the queries, mutations, and subscriptions arguments on the @model directive. +- Input objects for create, update, and delete mutations. +- Filter input objects that allow you to filter objects in list queries and relationship fields. +- For list queries the default number of objects returned is 100. You can override this behavior by setting the limit argument. + +**Type definition of the `@model` directive** + +```graphql +directive @model( + queries: ModelQueryMap + mutations: ModelMutationMap + subscriptions: ModelSubscriptionMap + timestamps: TimestampConfiguration +) on OBJECT + +input ModelMutationMap { + create: String + update: String + delete: String +} + +input ModelQueryMap { + get: String + list: String +} + +input ModelSubscriptionMap { + onCreate: [String] + onUpdate: [String] + onDelete: [String] + level: ModelSubscriptionLevel +} + +enum ModelSubscriptionLevel { + off + public + on +} + +input TimestampConfiguration { + createdAt: String + updatedAt: String +} +``` + +### Relational directives + +The relational directives are `@hasOne`, `@hasMany`, `@belongsTo` and `@manyToMany`. + + + + +The `@hasOne` will generate: + +- Foreign key fields in parent type that refer to the primary key and sort key fields of the child model. +- Foreign key fields in parent input object of `create` and `update` mutations. + +**Type definition of the `@hasOne` directive** + +```graphql +directive @hasOne(fields: [String!]) on FIELD_DEFINITION +``` + + + + +The `@hasMany` will generate: + +- Foreign key fields in child type that refer to the primary key and sort key fields of the parent model. +- Foreign key fields in child input object of `create` and `update` mutations. +- A global secondary index (GSI) in the child type Amazon DynamoDB table. + +**Type definition of the `@hasMany` directive** + +```graphql +directive @hasMany( + indexName: String + fields: [String!] + limit: Int = 100 +) on FIELD_DEFINITION +``` + +- The default number of nested objects returned is 100. You can override this behavior by setting the limit argument. + + + + +The `@belongsTo` will generate: + +- Foreign key fields that refer to the primary key and sort key fields of the related model. +- Foreign key fields in the input object of `create` and `update` mutations. + +**Type definition of the `@belongsTo` directive** + +```graphql +directive @belongsTo(fields: [String!]) on FIELD_DEFINITION +``` + + + + +The `@manyToMany` will generate: + +- A joint table defining the intermediate model type with the name of `relationName`. +- Foreign key fields in the joint table that refer to the primary key and sort key fields of both models. +- Foreign key fields in the intermediate model input object of `create` and `update` mutations. + +**Type definition of the `@manyToMany` directive** + +```graphql +directive @manyToMany( + relationName: String! + limit: Int = 100 +) on FIELD_DEFINITION +``` + +- The default number of nested objects returned is 100. You can override this behavior by setting the limit argument. + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx new file mode 100644 index 00000000000..577a673050a --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx @@ -0,0 +1,504 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Modify Amplify-generated resources', + description: 'Learn more about how to modify Amplify-generated resources for Amplify GraphQL APIs. This allows you to modify underlying AppSync, DynamoDB, Lambda, and OpenSearch resources.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/modify-amplify-generated-resources/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +Amplify GraphQL API uses a variety of auto-generated, underlying AWS services and resources. You can customize these underlying resources to optimize the deployed stack for your specific use case. + + + + + +```bash +amplify override api +``` + +Run the command above to override Amplify-generated GraphQL API resources including AWS AppSync API, Amazon DynamoDB table, Amazon OpenSearch domain, and more. + + + +If you need to customize a specific Amplify-generated VTL resolver, review [Override Amplify-generated resolvers](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#override-amplify-generated-resolvers) first. + + + +The command creates a new `overrides.ts` file under `amplify/backend/api//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). + + + + +In your CDK project, you can access every underlying resource as an ["L2"](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_using) or ["L1" construct](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_l1_using). Access the generated resources as L2 constructs via the `.resources` property on the returned stack or access the generated resources as L1 constructs using the `.resources.cfnResources` property. + +```ts +const api = new AmplifyGraphQlApi(this, 'api', { }); + +// Access L2 resources under `.resources` +api.resources.tables["Todo"].tableArn; + +// Access L1 resources under `.resources.cfnResources` +api.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true; +Object.values(api.resources.cfnResources.cfnTables).forEach(table => { + table.pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: false }; +}); +``` + + + + + +## Customize Amplify-generated AppSync GraphQL API resources + + + + + +Apply all the overrides in the `override(...)` function. For example to enable X-Ray tracing for the AppSync GraphQL API: + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.api.GraphQLAPI.xrayEnabled = true; +} +``` + +You can override the following GraphQL API resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [GraphQLAPI](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlapi.html) | AWS AppSync GraphQL API resource | +| [GraphQLAPIDefaultApiKey](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-apikey.html) | API Key resource for the AppSync GraphQL API | +| [GraphQLAPITransformerSchema](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlschema.html) | The GraphQL schema that's being deployed. (The output of the GraphQL Transformer) | +| [GraphQLAPINONEDS](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | A "none" data source that is used for requests that don't exit the AppSync API | +| [AmplifyDataStore](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The delta sync table used for Amplify DataStore's conflict resolution | +| [AmplifyDataStoreIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role used to access the delta sync table for DataStore | +| [DynamoDBAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access the DynamoDB resources from AppSync | + + + + +Apply all the customizations on `.resources.graphqlApi` or `.resources.cfnResources.cfnGraphqlApi`. For example to enable X-Ray tracing for the AppSync GraphQL API: + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); + +amplifyApi.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true; +``` + + + + + +## Customize Amplify-generated resources for @model directive + + + + + +Apply all the overrides in the `override(...)` function. Pass in the @model type name into `resources.models[...]` to modify the resources generated for that particular @model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Todo'].modelDDBTable.timeToLiveSpecification = { + attributeName: 'ttl', + enabled: true + }; +} +``` + +You can override the following @model directive resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [modelStack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html) | The nested stack containing all resources for the @model type | +| [modelDDBTable](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The DynamoDB table containing the data for this @model type | +| [modelIamRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access the DynamoDB table for this @model type | +| [modelIamRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access the delta sync table for this @model type in case DataStore is enabled | +| [dynamoDBAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | Default policy associated with the IAM role to access the DynamoDB table for this @model type | +| [modelDatasource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | The AppSync DataSource to representing the DynamoDB table | +| [invokeLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for Lambda-based conflict resolution function | + +For example, you can override a model generated DynamoDB table configuration. + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Todo'].modelDatasource.dynamoDbConfig['deltaSyncConfig'][ + 'baseTableTtl' + ] = '3600'; +} +``` + + + + +Apply all the customizations on `.resources.tables["MODEL_NAME"]` or `.resources.cfnResources.cfnTables["MODEL_NAME"]`. Pass in the @model type name into `.resources.cfnResources.cfnTables["MODEL_NAME"]` to modify the resources generated for that particular @model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); + +amplifyApi.resources.cfnResources.cfnTables['Todo'].timeToLiveSpecification = { + attributeName: 'ttl', + enabled: true +}; +``` + + + + + +### Example - Configure DynamoDB table's billing mode + +Set the [DynamoDB billing mode](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-billingmode) for the DynamoDB table. Either "PROVISIONED" or "PAY_PER_REQUEST". + + + + + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Post'].modelDDBTable.billingMode = 'PAY_PER_REQUEST'; +} +``` + + + + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); + +amplifyApi.resources.cfnResources.cfnTables['Todo'].billingMode = + 'PAY_PER_REQUEST'; +``` + + + + + +### Example - Configure provisioned throughput for DynamoDB table or Global Secondary Index + +Override the default [ProvisionedThroughput](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-provisionedthroughput) provisioned for each `@model` table and its Global Secondary Indexes (GSI). + +Only valid if the "DynamoDBBillingMode" is set to "PROVISIONED" + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Post'].modelDDBTable.provisionedThroughput = { + readCapacityUnits: 5, + writeCapacityUnits: 5 + }; + + /** + * When billing mode is set to "PROVISIONED", it is necessary to specify `provisionedThroughput` for every Global Secondary Index (GSI) that exists in the table. + */ + + resources.models[ + 'Post' + ].modelDDBTable.globalSecondaryIndexes[0].provisionedThroughput = { + readCapacityUnits: 5, + writeCapacityUnits: 5 + }; +} +``` + +### Example - Enable point-in-time recovery for DynamoDB table + +Enable/disable [DynamoDB point-in-time recovery](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-pointintimerecoveryspecification.html) for each `@model` table. + + + + + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.models['Post'].modelDDBTable.pointInTimeRecoverySpecification = { + pointInTimeRecoveryEnabled: true + }; +} +``` + + + + +```ts +const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { + definition: AmplifyGraphqlDefinition.fromFiles( + path.join(__dirname, 'schema.graphql') + ), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } +}); + +amplifyApi.resources.cfnResources.cfnTables[ + 'Todo' +].pointInTimeRecoverySpecification = { + pointInTimeRecoveryEnabled: true +}; +``` + + + + + +## Customize Amplify-generated resources for @searchable (OpenSearch) directive + +Apply all the overrides in the `override(...)` function. For example, to modify the OpenSearch instance count: + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { + ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, + instanceCount: 6 + }; +} +``` + +You can override the following @searchable directive resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [OpenSearchDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | The AppSync data source representing the OpenSearch integration | +| [OpenSearchAccessIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access OpenSearch domain | +| [OpenSearchAccessIAMRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access OpenSearch domain | +| [OpenSearchDomain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticsearch-domain.html) | OpenSearch domain containing the @searchable data | +| [OpenSearchStreamingLambdaIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to stream DynamoDB data to OpenSearch domain | +| [OpenSearchStreamingLambdaIAMRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to stream DynamoDB data to OpenSearch domain | +| [CloudwatchLogsAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for granting CloudWatch logs access | +| [OpenSearchStreamingLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | Lambda function to stream DynamoDB data to OpenSearch domain | +| [OpenSearchModelLambdaMapping](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html) | Event source mapping for DynamoDB table stream to Lambda function | + +### Example - Configure Runtime for Streaming Lambda + +You can define the runtime for the `@searchable` by setting the runtime value on the function object itself. This can be done to resolve a deprecated runtime in the event that you cannot upgrade your version of the Amplify CLI. + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchStreamingLambdaFunction.runtime = 'python3.9'; +} +``` + +### Example - Configure OpenSearch Streaming function name + +Override the name of the AWS Lambda searchable streaming function + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchStreamingLambdaFunction.FunctionName = + 'CustomFunctionName'; +} +``` + +### Example - Configure OpenSearch instance version + +Override the `elasticsearchVersion` in the OpenSearch domain created by `@searchable` + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.elasticsearchVersion = 'OpenSearch_1.3'; +} +``` + +### Example - Configure OpenSearch instance type + +Override the type of instance launched into the OpenSearch domain created by `@searchable` + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { + ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, + instanceType: 'm3.medium.elasticsearch' + }; +} +``` + +### Example - Configure OpenSearch instance count + +Override the number of instances launched into the OpenSearch domain created by `@searchable` + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { + ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, + instanceCount: 2 + }; +} +``` + +### Example - Configure OpenSearch EBS volume size + +Override the amount of disk space allocated to each instance in the OpenSearch domain created by `@searchable` + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.opensearch.OpenSearchDomain.ebsOptions = { + ...resources.opensearch.OpenSearchDomain.ebsOptions, + volumeSize: 10 + }; +} +``` + +## Customize Amplify-generated resources for @predictions directive + +Apply all the overrides in the `override(...)` function. For example, to add a Path to IAM role that facilitates text translation: + +```ts +import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { + resources.predictions.TranslateDataSourceServiceRole.path = + '/my/organization/'; +} +``` + +You can override the following @predictions directive resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [RekognitionDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync HTTP data source to connect to Amazon Rekognition service | +| [RekognitionDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Amazon Rekognition | +| [TranslateDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync HTTP data source to connect to Amazon Translate service | +| [translateTextAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to connect to Amazon Translate | +| [LambdaDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync Lambda data source to connect to Amazon Polly | +| [LambdaDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Lambda function calling Amazon Polly | +| [LambdaDataSourceServiceRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for AppSync to connect to Lambda function calling Amazon Polly | +| [TranslateDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Amazon Translate | +| [predictionsLambdaIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role for Lambda function calling Amazon Polly | +| [predictionsLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | Lambda function calling Amazon Polly | +| [PredictionsLambdaAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for Lambda function to access Amazon Polly | +| [predictionsIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access s3 bucket used by @predictions | +| [PredictionsStorageAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access S3 bucket used by @predictions | +| [identifyTextAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to enable Identify Text | +| [identifyLabelsAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to enable Identify Text | + +## Place AppSync Resolvers in Custom-named Stacks + +If you have a particularly large GraphQL schema, you may run into issues with too many resources defined in a stack. The most common case where this happens is in the ConnectionStack which contains the resolvers for all of the relational directives in the schema. + +Creating a stack mapping does not create an additional root stack for the Amplify environment. All mapped stacks will still be placed under the existing Amplify environment root stack. To map a resolver to a different stack, update `/amplify/api//transform.conf.json` with a "StackMapping" block. The StackMapping defines a map from resolver logical ID to stack name. + +```json +{ + "Version": 5, + "ElasticsearchWarning": true, + "StackMapping": { + "": "Custom stack name" + } +} +``` + +The easiest way to determine a resolver logical ID is to run `amplify api gql-compile` and note the resolver logical ID in the list of Resources in the generated CloudFormation stack. Resolvers for model operations will be of the form `Resolver`. Resolvers for relational directives are of the form `Resolver`. + +### Example + +Given the following schema: + +```graphql +type Blog @model { + id: ID! + name: String! + posts: [Post] @hasMany +} + +type Post @model { + id: ID! + title: String! + content: String + blog: Blog @belongsTo +} +``` + +To map the CreatePostResolver and the relational resolvers to a stack named 'MyCustomStack', add the following in `transform.conf.json`: + +```json +"StackMapping": { + "CreatePostResolver": "MyCustomStack", + "BlogpostsResolver": "MyCustomStack", + "PostblogResolver": "MyCustomStack", +} +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx new file mode 100644 index 00000000000..f90599e51bf --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx @@ -0,0 +1,145 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Evolving GraphQL schemas', + description: 'Evolve your GraphQL schema over time using the @mapsTo directive to retain tables while renaming models', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/schema-evolution/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + +GraphQL schemas change over the lifecycle of a project. Sometimes these changes include breaking API changes. One such change is renaming a model in the schema, which Amplify offers a way to do while retaining the underlying records for that model. + +## Renaming models while retaining data + +Amplify supports renaming models in a GraphQL schema by using the `@mapsTo` directive. +Normally when renaming a model, Amplify will remove the underlying table for the model and create a new table with the new name. Once a table contains production data that cannot be deleted, `@mapsTo` can be used to specify the original name. Amplify will use the original name to ensure the underlying DynamoDB tables and other resources point to the existing data. +Other GraphQL API references to the model will use the new name. + +For example, a schema such as: +```graphql +type Todo @model { + id: ID! + title: String! +} +``` +becomes: +```graphql +type Task @model @mapsTo(name: "Todo") { + id: ID! + title: String! +} +``` +Amplify will update all of the GraphQL operations and types to use the name Task, but the Task model will point to the table that Todo was originally using. + + + +- `@mapsTo` cannot be used to point a model to an arbitrarily named table. It can only be used to point a renamed model to it's original name. +- `@mapsTo` can only be used on @model GraphQL types that are backed by a DynamoDB table. + + + +When renaming a model that has relationships with other models, Amplify will automatically map auto-generated foreign key fields to their original name. For example, given: + +```graphql +type Post @model { + id: ID! + title: String! + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + message: String! + # postCommentsId: String is an autogenerated field containing the foreign key +} +``` +Amplify will automatically add a field named `postCommentsId` to the Comment model that contains the foreign key of the Post. If the Post type is renamed to Article: + +```graphql +type Article @model @mapsTo(name: "Post") { + id: ID! + title: String! + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + message: String! + # articleCommentsId: String is the new autogenerated field containing the foreign key +} +``` +The underlying table still contains records with `postCommentsId` as the foreign key field in the Comment table. In the new schema the foreign key field is now `articleCommentsId`. +Amplify is aware of this and will automatically map incoming requests with `articleCommentsId` to `postCommentsId` and do the reverse mapping for results. + +## Limitations + +### Constraint on relationship field names with @mapsTo + +In the above example if you renamed Comment to Reaction: +```graphql +type Post @model { + id: ID! + title: String! + comments: [Reaction] @hasMany # this field cannot be renamed and still access existing relationship data +} + +type Reaction @model @mapsTo(name: "Comment") { + id: ID! + message: String! + # autogenerated field postCommentsId: String contains the foreign key +} +``` +The `@hasMany` field `comments` cannot be renamed to `reactions`. This is because the foreign key field in Reaction uses the parent field name as part of the name. Amplify cannot determine the original name if this is changed. + +If a model is renamed multiple times, the value specified in `@mapsTo` must be the _original_ name, not the previous name. + +### Constraints to prevent naming conflicts + +A model in the schema cannot have the same name as the name another type maps to. For example, the following schema is invalid: +```graphql +type Article @model @mapsTo(name: "Post") { + id: ID! +} + +type Post @model { + id: ID! +} +``` +This schema would create a conflict on the Post table. + +Furthermore, even if the Post model is mapped to a different name, it is still not allowed. While this scenario technically does not pose a conflict, it is disallowed to prevent confusion. + +If you are accessing the table of a renamed model directly (ie. without going through AppSync), your access patterns will need to be aware that foreign key fields of records in the database are not renamed. See "How it works" below. + +## How it works +`@mapsTo` does not modify any existing tables or records. Instead, it points AppSync resolvers for the new name to the existing DynamoDB table for the original name. + +To handle renamed autogenerated foreign key fields when using relational directives, Amplify adds additional AppSync pipeline resolvers before and after fetching data from the database. +The resolvers before the fetch map any occurrence of the renamed foreign keys in the request to the original name. Then the resolvers after the fetch map any occurrence of the original name to the current name before returning the result. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx new file mode 100644 index 00000000000..adf0c4a8c57 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx @@ -0,0 +1,483 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Build search and aggregate queries', + description: 'Add authorization rules to your GraphQL schema to control access to your data.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/search-and-result-aggregations/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + + +Add the `@searchable` directive to an `@model` type to enable OpenSearch-based data search and result aggregations. This gives you the ability to: + +- search for data using advanced filters, such as substring matching, wildcards, regex, `and`/`or`/`not` conditions +- get aggregation values, such as sum, average, min, max, terms +- retrieve total search result count +- sort the search results across one or multiple fields + +```graphql +type Student @model @searchable { + name: String + dateOfBirth: AWSDate + email: AWSEmail + examsCompleted: Int +} +``` + +> Once the `@searchable` directive is added, all new records added to the model are streamed to OpenSearch. To backfill existing data, see [Backfill OpenSearch index from DynamoDB table](/[platform]/build-a-backend/graphqlapi/troubleshooting/#backfill-opensearch-index-from-dynamodb-table). + +## Search and filter data + +Every model with a `@searchable` directive attached generates a new "search" GraphQL query to search and filter for records. The example above provides you the ability to search for "Student" records using a "searchStudents" query. + +The `filter` parameter allows you to filter for records based on their field values. + +```graphql +query SearchStudentsByEmail { + searchStudents(filter: { name: { eq: "Rene Brandel" } }) { + items { + id + name + email + } + } +} +``` + +In the example above, the search result consists of students with the name "Rene Brandel" + +### Supported search operations + +| Field type | Supported search operations | +| --- | --- | +| String | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp | +| Int | ne, gt, lt, gte, lte, eq, range | +| Float | ne, gt, lt, gte, lte, eq, range | +| Boolean | eq, ne | +| Enum | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp | + +### Nested search conditions (and, or, not) + +Use the filter parameter to pass a nested `and`/`or`/`not` condition. + +```graphql +query MyQuery { + searchStudents( + filter: { + name: { wildcard: "*Brandel" } + or: [{ dateOfBirth: { lt: "2000-01-01" } }, { email: { exists: true } }] + } + ) { + items { + id + name + email + dateOfBirth + } + } +} +``` + +By default, every operation in the filter properties is `and`ed. Use the `or` or `not` properties in the search query's `filter` parameter to override this behavior. + +The query above returns a "Student" if: + +- their name ends with "Brandel" +- `and` + - their date of birth is earlier than 2000-01-01 + - `or` + - their email exists. + +## Sort search results + +Use the `sort` parameter to sort your search results by a field in ascending or descending order. The `field` argument accepts any field available on the model. The `direction` accepts either `asc` or `desc`. + +```graphql +query SearchAndSort { + searchStudents( + filter: { name: { wildcard: "*Brandel" } } + sort: { direction: desc, field: name } + ) { + items { + name + id + } + } +} +``` + +In the example above, the search result is sorted based on their `name` in a `desc`ending order. + +### Sort search result over multiple fields + +To sort over multiple fields, provide array of sort conditions. When sorting over multiple fields, the sort conditions are applied in the `sort` array's order. + +```graphql +query SearchAndSort { + searchStudents( + filter: { name: { wildcard: "*Brandel" } } + sort: [ + { field: name, direction: desc } # Sort condition #1 + { field: dateOfBirth, direction: asc } # Sort condition #2 + ] + ) { + items { + id + name + dateOfBirth + } + } +} +``` + +In the example above, the search result is first sorted by `name` in a `desc`ending order and then by `dateOfBirth` in an `asc`ending order. + +## Paginate over search results + +By default, the search result page size is 100. To customize the page size modify the `limit` parameter. Query for the `nextToken` and use it in your subsequent pagination requests: + +``` +query MyQuery { + searchTodos(nextToken: "") { # Pass in your nextToken in query + items { + description + id + name + createdAt + } + nextToken # Next token to paginate on + } +} +``` + +## Total count of search results + +Add the `total` field in your query response to get the total count of search result hits. + +```graphql +query MyQuery { + searchStudents(filter: { name: { wildcard: "*Brandel" } }) { + items { + id + } + total # Specify to get total counts + } +} +``` + +In the example above, the response's `total` field contains the total search result count for "Students" whose name ends with "Brandel". Note: `total` is calculated based on all records, irrespective of pagination configurations. + +## Aggregate values for search result (minimum, maximum, average, sum, terms) + +Use the `aggregates` parameter to get aggregate values such as "minimum", "maximum", "average", and "sum" returned in the `aggregateItems` field. Note: `aggregates` are calculated based on all records, irrespective of pagination configurations. + + + + +Provide the `min` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchStudents( + aggregates: { + type: min # Specifies that you want the "min" value + field: examsCompleted # Specifies the field for the aggregate value + name: "minimumExams" # provides a name to reference in the response field + } + filter: { name: { wildcard: "Rene*" } } + ) { + aggregateItems { + name + result { + ... on SearchableAggregateScalarResult { + value + } + } + } + } +} +``` + +In the example above, the response includes the minimum value of "examsCompleted" for all Students whose name starts with "Rene". + +```graphql +{ + "data": { + "searchStudents": { + "aggregateItems": [{ + "name": "minimumExams", + "result": { + "value": 7 + } + }] + } + } +} +``` + + + + + +Provide the `max` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchStudents( + aggregates: { + type: max # Specifies that you want the "max" value + field: examsCompleted # Specifies the field for the aggregate value + name: "maximumExams" # provides a name to reference in the response field + } + filter: { name: { wildcard: "Rene*" } } + ) { + aggregateItems { + name + result { + ... on SearchableAggregateScalarResult { + value + } + } + } + } +} +``` + +In the example above, the response includes the maximum value of "examsCompleted" for all Students whose name starts with "Rene". + +```graphql +{ + "data": { + "searchStudents": { + "aggregateItems": [{ + "name": "maximumExams", + "result": { + "value": 28 + } + }] + } + } +} +``` + + + + + +Provide the `avg` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchStudents( + aggregates: { + type: avg # Specifies that you want the "avg" value + field: examsCompleted # Specifies the field for the aggregate value + name: "averageExams" # provides a name to reference in the response field + } + filter: { name: { wildcard: "Rene*" } } + ) { + aggregateItems { + name + result { + ... on SearchableAggregateScalarResult { + value + } + } + } + } +} +``` + +In the example above, the response includes the average value of "examsCompleted" for all Students whose name starts with "Rene". + +```graphql +{ + "data": { + "searchStudents": { + "aggregateItems": [{ + "name": "averageExams", + "result": { + "value": 17.3 + } + }] + } + } +} +``` + + + + + +Provide the `sum` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchStudents( + aggregates: { + type: sum # Specifies that you want the "sum" value + field: examsCompleted # Specifies the field for the aggregate value + name: "examsSum" # provides a name to reference in the response field + } + filter: { name: { wildcard: "Rene*" } } + ) { + aggregateItems { + name + result { + ... on SearchableAggregateScalarResult { + value + } + } + } + } +} +``` + +In the example above, the response includes the sum of all "examsCompleted" values for all Students whose name starts with "Rene". + +```graphql +{ + "data": { + "searchStudents": { + "aggregateItems": [{ + "name": "examsSum", + "result": { + "value": 392 + } + }] + } + } +} +``` + + + + +Provide the `terms` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. + +```graphql +query MyQuery { + searchTodos( + aggregates: { field: description, type: terms, name: "descriptionTerms" } + ) { + aggregateItems { + result { + ... on SearchableAggregateBucketResult { + __typename + buckets { + doc_count + key + } + } + } + name + } + } +} +``` + +In the example above, the response includes the terms for the description and their count: + +```graphql +{ + "data": { + "searchTodos": { + "aggregateItems": [ + { + "result": { + "__typename": "SearchableAggregateBucketResult", + "buckets": [{ + "doc_count": 2, + "key": "Shopping list" + }, { + "doc_count": 1, + "key": "Me next todo" + }] + }, + "name": "descriptionTerms" + } + ] + } + } +} +``` + + + + +## Set up OpenSearch for production environments + +By default, Amplify CLI will configure a t2.small instance type. This is great for getting started and prototyping but not recommended to be used in the production environment per the [OpenSearch best practices](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp.html). + +To configure the OpenSearch instance type per environment: + +1. Run `amplify env add` to create a new environment (e.g. "prod") +2. Edit the `amplify/team-provider-info.json` file and set `OpenSearchInstanceType` to the instance type that works for your application + +```json +{ + "dev": { + "categories": { + "api": { + "": { + "OpenSearchInstanceType": "t2.small.elasticsearch" + } + } + } + }, + "prod": { + "categories": { + "api": { + "": { + "OpenSearchInstanceType": "t2.medium.elasticsearch" + } + } + } + } +} +``` + +3. Deploy your changes with `amplify push` + +Learn more about Amazon OpenSearch Service instance types [here](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-instance-types.html). + +## How it works + +The `@searchable` directive streams the data of an @model type to Amazon OpenSearch Service and configures search resolvers to query against OpenSearch. + +Type definition of the `@searchable` directive: + +```graphql +# Streams data from DynamoDB to OpenSearch and exposes search capabilities. +directive @searchable(queries: SearchableQueryMap) on OBJECT +input SearchableQueryMap { + search: String +} +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx new file mode 100644 index 00000000000..84d29845a33 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx @@ -0,0 +1,96 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Troubleshooting', + description: 'Add authorization rules to your GraphQL schema to control access to your data.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/graphqlapi/troubleshooting/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +## Deploying multiple index changes at once + +You can make `@index` updates on one "amplify push". Under the hood, Amplify CLI needs to locally sequence multiple individual deployments to your DynamoDB table because each Global Secondary Index (GSI), managed by `@index`, change requires time to create the new index. + +If your deployment fails locally when updating multiple GSIs, you'll have the ability to run: + +- `amplify push --iterative-rollback` to rollback the last-known-good state +- `amplify push --force` to rollback the last-known-good state and try redeploying your changes again using. + +```console +Attempting to mutate more than 1 global secondary index at the same time. +``` + +If you're running into the error above during `amplify push`, it is likely that you don't have this feature enabled. To enable multiple GSI updates, set the "enableIterativeGsiUpdates" feature flag to true in your `amplify/cli.json` file. + +## Backfill OpenSearch index from DynamoDB table + +When you add `@searchable` to a `@model` type with existing data, then you need to backfill the OpenSearch index. Download the following Python script to help you backfill your OpenSearch index: + +[DynamoDB to OpenSearch backfill script](https://raw.githubusercontent.com/aws-amplify/amplify-category-api/main/packages/graphql-elasticsearch-transformer/scripts/ddb_to_es.py) + +The script creates an event stream of your DynamoDB records and sends them to your OpenSearch Index. Execute the script with the following parameters to initiate the backfill: + +| Parameter | Description | Required | +| --- | --- | --- | +| `--rn` | DynamoDB table region. See [AWS Regions](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) for available options | Yes | +| `--tn` | DynamoDB table name. Format: `{@model type name}-{AppSync API ID}-{Amplify environment}` | Yes | +| `--lf` | ARN of the "DynamoDB to OpenSearch streaming" Lambda function. Format: `arn:aws:lambda:{region}:{AWS Account ID}:function:amplify-{Amplify project name}-{Amplify environment}-{Random string}-OpenSearchStreamingLambd-{Random string}` | Yes | +| `--esarn` | ARN of the DynamoDB table stream. Format: `arn:aws:dynamodb:{region}:{AWS Account ID}:table/{@model type name}-{AppSync API ID}-{Amplify environment}/stream/{Table creation date}` | Yes | +| `--ak` | AWS Access Key ID. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | +| `--sk` | AWS Secret Access Key. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | +| `--st` | AWS Session Token. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | + +In the example below, the `Post` table data in `us-west-2` gets backfilled in the OpenSearch index. + +```bash +python3 ddb_to_es.py \ + --rn 'us-west-2' \ # Use the region in which your table and OpenSearch domain reside + --tn 'Post-XXXX-dev' \ # Table name + --lf 'arn:aws:lambda:us-west-2:<...>:function:amplify-<...>-OpenSearchStreamingLambd-<...>' \ # Lambda function ARN, find the DynamoDB to OpenSearch streaming functions, copy entire ARN + --esarn 'arn:aws:dynamodb:us-west-2:<...>:table/Post-<...>/stream/2019-20-03T00:00:00.350' # Event source ARN, copy the full DynamoDB table ARN +``` + +## Index with multiple sort key fields + +When you add an `@index` directive with 2 or more sort key fields, you will need to backfill the new composite sort key for existing data. With `@index(sortKeyFields: ["status", "date"])`, you will need to backfill the `status#date` field with composite key values made up of each object's `status` and `date` fields joined by a `#`. You do not need to backfill data for `@index` directives with zero to one sort key field(s). + +## Maximum Global Secondary Index limit for DynamoDB table exceeded + +If you have increased the [soft limit of GSI per table](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html) in DynamoDB beyond 20, then iterative deployments will fail with the following warning. + +```console +DynamoDB
can have max of 20 GSIs. +To disable this check, use the --disable-gsi-limit-check option. +``` + +In order to disable this behavior, update your deploy scripts or ci commands to include the `--disable-gsi-limit-check` option to circumvent this validation during pushes. + +```bash +amplify push --disable-gsi-limit-check +``` From 7450a477df38fcd70e4059a9c0cd5919c9ec47b9 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 6 May 2024 17:13:17 -0700 Subject: [PATCH 23/88] chore: fix v1 rest api gen 1 --- src/directory/directory.mjs | 9 + .../flutter/getting-started/10_preReq.mdx | 15 +- .../restapi/configure-rest-api copy/index.mdx | 290 ++++++++++++++++ .../restapi/configure-rest-api/index.mdx | 290 ++++++++++++++++ .../restapi/override-api-gateway/index.mdx | 325 ++++++++++++++++++ .../restapi/test-api/index.mdx | 185 ++++++++++ 6 files changed, 1110 insertions(+), 4 deletions(-) create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api copy/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index f6073310f69..d1502333b3b 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -2342,6 +2342,9 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/index.mdx', children: [ + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/set-up-rest-api/index.mdx' }, @@ -2360,8 +2363,14 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/customize-authz/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/existing-resources/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx' } ] }, diff --git a/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx index 649105f720b..eb0c1c4160c 100644 --- a/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx @@ -1,5 +1,12 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) -* A Flutter application targeting Flutter SDK >= 2.10.0 with Amplify libraries integrated - * An iOS configuration targeting at least iOS 11.0 - * An Android configuration targeting at least Android API level 21 (Android 5.0) or above - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) +* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated + + The following are also required, depending on which platforms you are targeting: + + * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 + * An Android configuration targeting at least Android API level 24 (Android 7.0) or above + * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) + * Any Windows OS meeting Flutter minimums + * macOS version 10.15 or higher + * Any Ubuntu Linux distribution meeting Flutter minimums + * For a full example please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api copy/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api copy/index.mdx new file mode 100644 index 00000000000..661b7680e18 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api copy/index.mdx @@ -0,0 +1,290 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure REST API', + description: "Use Amplify CLI's simple guided workflow to add REST APIs to cloud-based web and mobile apps.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/restapi/configure-rest-api/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +The Amplify CLI provides a guided workflow to easily add, develop, test and manage REST APIs to access your AWS resources from your web and mobile applications. + +A REST API or HTTP endpoint will be composed by one or more paths. Eg: `/items`. Each path will use a Lambda function to handle HTTP requests and responses. Amplify CLI creates a single resource in Amazon API Gateway so you can handle all routes, HTTP Methods and paths, with a single Lambda function via a Lambda Proxy integration. HTTP proxy integrations forward all requests and responses directly through to your HTTP endpoint. + +Amplify CLI let's you choose either an existing Lambda function or create a new one. To kickstart your implementation, you can choose between the following templates: + +- Serverless ExpressJS function +- CRUD function for DynamoDB + +> Lambda templates use [serverless-express](https://github.com/awslabs/aws-serverless-express) and provide the building blocks to start your REST API development. + +> See the list of all [supported Lambda runtimes](/[platform]/build-a-backend/functions/set-up-function/). + +Amplify CLI allows you to restrict REST API access to + +- Only authenticated users; or +- Authenticated and Guest users +- User Pool Groups + +See a description of these user types below + +| User type | Description | +| --- | --- | +| Authenticated user | User needs to sign in to use the REST API | +| Guest user | User doesn't need to sign in to use the REST API | +| User Pool Group | User needs to sign in and belong to the User Pool Group to use the REST API | + +For each user type you can further specify what actions it has access to. + +| User type | Actions | Http Method | Authentication Provider | +| --- | --- | --- | --- | +| Authenticated user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | +| Guest user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | +| User Pool Group | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | + +REST APIs have support for [multiple environments](/[platform]/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. + +Because Amplify environments could be in separate AWS accounts, you cannot use the environment feature of API Gateway. Each Amplify environment will have a separate API Gateway resource associated with it. For example: + +```console +https://.execute-api.eu-west-2.amazonaws.com/dev/items +https://.execute-api.eu-west-2.amazonaws.com/prod/items +``` + +## Create a REST API + +Navigate into the root of a JavaScript, iOS, or Android project and run: + +```bash +amplify init +``` + +Follow the wizard to create a new app. After finishing the wizard run: + +```bash +amplify add api +``` + +Select the following options: + +- Please select from one of the below mentioned services: **REST** +- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsApi** +- Provide a path (e.g., /book/\{isbn}): **/items** + +This will be the configuration for `/items` path in API Gateway: + +```console +/ + |_ /items Main resource. Eg: /items + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser + |_ /\{proxy+} Proxy resource. Eg: /items/, /items/id, items/object/\{id} + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser +``` + +By default Amplify CLI creates a greedy path variable `/items/\{proxy+}` that catches all child resources for a path and forwards them to your Lambda. This will match all child routes including `/items/id` and `/items/object/id`. + +- Choose a Lambda source **Create a new Lambda function** +- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsLambda** +- Provide the AWS Lambda function name: **itemsLambda** +- Choose the runtime that you want to use: **NodeJS** +- Choose the function template that you want to use: **Serverless ExpressJS function** + +The Lambda function template **Serverless ExpressJS function** implements route handlers for `GET`, `POST`, `PUT` and `DELETE` Http Methods and paths for `/items` and `/items/*`. Some possible routes examples include: + +```console +GET /items List all items +GET /items/1 Load an item by id +POST /items Create an item +PUT /items Update an item +DELETE /items/1 Delete an item by id +``` + +- Do you want to access other resources in this project from your Lambda function? **No** +- Do you want to invoke this function on a recurring schedule? **No** +- Do you want to configure Lambda layers for this function? **No** +- Do you want to edit the local lambda function now? **Yes** + +> You are not going to change this template but it's good that you have it open as you follow the next steps. + +- Press enter to continue +- Restrict API access **Yes** +- Who should have access? **Authenticated and Guest users** +- What kind of access do you want for Authenticated users? **create, read, update, delete** +- What kind of access do you want for Guest users? **read** + +When configuration of your API is complete, the CLI displays a message confirming that you have configured local CLI metadata for this category. You can confirm this by running `amplify status`. Finally deploy your changes to the cloud: + +Amplify CLI restricts API access combining Amazon Cognito for authentication and AWS IAM (Identity and Access Management) for granting execution permissions on routes. + +- Do you want to add another path? **No** + +Deploy your new API. + +```bash +amplify push +``` + +At the end of this command you can take note of your new REST API url. + +```console +REST API endpoint: https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev +``` + +> REST APIs follow this pattern `https://{restapi-id}.execute-api.\{region}.amazonaws.com/\{environment}/\{path}`. + +Let's see an overview of all the resources created by Amplify CLI. + +```console +REST + |_ /items (path) + |_ itemsApi (Amazon API Gateway) + |_ itemsLambda (AWS Lambda) + |_ Logs (Amazon CloudWatch) +``` + +## Create REST API and restrict specific routes to specific User Pool Groups + +If your app uses User Pool Groups to manage different user types and would like to restrict access of specific routes to specific User Pool Groups. You can accomplish this by the following flow: + +- Create API route. +- Add API route handler function. +- Restrict-access to the API route to the User Pool Group. + +> The following example flow assumes the existence of two User Pool Groups : AdminUsers and GuestUsers for a Book store. The app would like to limit admin functionality like updating book records to the AdminUsers User Pool Group, while borrowing and returning books would be limited to the GuestUsers User Pool Group. +> +> - Path : /book/admin is restricted to AdminUsers and commands are handled by the bookAdminHandler lambda function +> - Path : /book/guest is restricted to GuestUsers and commands are handled by the bookGuestHandler lambda function + +```bash +amplify add api +$> ? Select from one of the below mentioned services: REST +$> ✔ Provide a friendly name for your resource to be used as a label for this category in the project: · mybookapi +$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/admin +$> ✔ Choose a Lambda source · Create a new Lambda function +$> ? Provide an AWS Lambda function name: bookAdminHandler +$> ? Choose the runtime that you want to use: NodeJS +$> ? Choose the function template that you want to use: Hello World +$> ? Do you want to configure advanced settings? No +$> ? Do you want to edit the local lambda function now? No +Successfully added resource bookAdminHandler locally. +$> ✔ Restrict API access? (Y/n) · yes +$> ✔ Restrict access by: · Individual Groups +$> ✔ Select groups: AdminUsers +$> ✔ What permissions do you want to grant to AdminUsers users? · create, read, update, delete +$> ✔ Do you want to add another path? (y/N) · yes +$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/guest +$> ✔ Choose a Lambda source · Create a new Lambda function +$> ? Provide an AWS Lambda function name: bookGuestHandler +$> ? Choose the runtime that you want to use: NodeJS +$> ? Choose the function template that you want to use: Hello World +$> ? Do you want to configure advanced settings? No +$> ? Do you want to edit the local lambda function now? No +Successfully added resource bookGuestHandler locally. +$> ✔ Restrict API access? (Y/n) · yes +$> ✔ Restrict access by: Individual Groups +$> ✔ Select groups: GuestUsers +$> ✔ What permissions do you want to grant to GuestUsers users? create, read, update +$> ✔ Do you want to add another path? (y/N) No +✅ Successfully added resource mybookapi locally +``` + +At the end of this command you can verify the routes and their respective User Pool Group restrictions in the `cli-inputs.json` file at the following path. + +```bash + /amplify/backend/api//cli-inputs.json +``` + +## REST endpoint that triggers new Lambda functions + +During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths. + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source Create a new Lambda function +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda +? Provide the AWS Lambda function name: itemsLambda +? Choose the function template that you want to use: + CRUD function for Amazon DynamoDB +❯ Serverless ExpressJS function +``` + +## REST endpoint that triggers existing Lambda functions + +During the CLI setup, you'll be guided through to use your own Lambda functions which you've initialized as a part of your CLI project using the `amplify add function` command. This would allow you to have custom logic in your Lambda function and not use the predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) templates generated by the CLI as in the examples above. + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source + Create a new Lambda function +❯ Use a Lambda function already added in the current Amplify project +``` + +## Set up a REST API with Amazon DynamoDB + +During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths with support for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command). + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source Create a new Lambda function +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda +? Provide the AWS Lambda function name: itemsLambda +? Choose the function template that you want to use: +❯ CRUD function for Amazon DynamoDB + Serverless ExpressJS function +``` + +In the example above with `/items` path, the following API will be created for you: + +1. GET /items/[ID] will return a list containing the item at the [ID]. If the item does not exist then an empty array is returned. +2. GET /items/object/[ID] will return a single item at [ID]. If the item does not exist then an empty object is returned. +3. PUT /items with your item in the request body will create or update the item. +4. POST /items with your item in the request body will create or update the item. +5. DELETE /items/object/[ID] will delete the item. + +When you have a sort key, you can append it to the end of the path, for example: `GET /items/object/[ID]/[SORT_KEY_ID]` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx new file mode 100644 index 00000000000..661b7680e18 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx @@ -0,0 +1,290 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure REST API', + description: "Use Amplify CLI's simple guided workflow to add REST APIs to cloud-based web and mobile apps.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/restapi/configure-rest-api/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +The Amplify CLI provides a guided workflow to easily add, develop, test and manage REST APIs to access your AWS resources from your web and mobile applications. + +A REST API or HTTP endpoint will be composed by one or more paths. Eg: `/items`. Each path will use a Lambda function to handle HTTP requests and responses. Amplify CLI creates a single resource in Amazon API Gateway so you can handle all routes, HTTP Methods and paths, with a single Lambda function via a Lambda Proxy integration. HTTP proxy integrations forward all requests and responses directly through to your HTTP endpoint. + +Amplify CLI let's you choose either an existing Lambda function or create a new one. To kickstart your implementation, you can choose between the following templates: + +- Serverless ExpressJS function +- CRUD function for DynamoDB + +> Lambda templates use [serverless-express](https://github.com/awslabs/aws-serverless-express) and provide the building blocks to start your REST API development. + +> See the list of all [supported Lambda runtimes](/[platform]/build-a-backend/functions/set-up-function/). + +Amplify CLI allows you to restrict REST API access to + +- Only authenticated users; or +- Authenticated and Guest users +- User Pool Groups + +See a description of these user types below + +| User type | Description | +| --- | --- | +| Authenticated user | User needs to sign in to use the REST API | +| Guest user | User doesn't need to sign in to use the REST API | +| User Pool Group | User needs to sign in and belong to the User Pool Group to use the REST API | + +For each user type you can further specify what actions it has access to. + +| User type | Actions | Http Method | Authentication Provider | +| --- | --- | --- | --- | +| Authenticated user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | +| Guest user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | +| User Pool Group | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | + +REST APIs have support for [multiple environments](/[platform]/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. + +Because Amplify environments could be in separate AWS accounts, you cannot use the environment feature of API Gateway. Each Amplify environment will have a separate API Gateway resource associated with it. For example: + +```console +https://.execute-api.eu-west-2.amazonaws.com/dev/items +https://.execute-api.eu-west-2.amazonaws.com/prod/items +``` + +## Create a REST API + +Navigate into the root of a JavaScript, iOS, or Android project and run: + +```bash +amplify init +``` + +Follow the wizard to create a new app. After finishing the wizard run: + +```bash +amplify add api +``` + +Select the following options: + +- Please select from one of the below mentioned services: **REST** +- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsApi** +- Provide a path (e.g., /book/\{isbn}): **/items** + +This will be the configuration for `/items` path in API Gateway: + +```console +/ + |_ /items Main resource. Eg: /items + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser + |_ /\{proxy+} Proxy resource. Eg: /items/, /items/id, items/object/\{id} + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser +``` + +By default Amplify CLI creates a greedy path variable `/items/\{proxy+}` that catches all child resources for a path and forwards them to your Lambda. This will match all child routes including `/items/id` and `/items/object/id`. + +- Choose a Lambda source **Create a new Lambda function** +- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsLambda** +- Provide the AWS Lambda function name: **itemsLambda** +- Choose the runtime that you want to use: **NodeJS** +- Choose the function template that you want to use: **Serverless ExpressJS function** + +The Lambda function template **Serverless ExpressJS function** implements route handlers for `GET`, `POST`, `PUT` and `DELETE` Http Methods and paths for `/items` and `/items/*`. Some possible routes examples include: + +```console +GET /items List all items +GET /items/1 Load an item by id +POST /items Create an item +PUT /items Update an item +DELETE /items/1 Delete an item by id +``` + +- Do you want to access other resources in this project from your Lambda function? **No** +- Do you want to invoke this function on a recurring schedule? **No** +- Do you want to configure Lambda layers for this function? **No** +- Do you want to edit the local lambda function now? **Yes** + +> You are not going to change this template but it's good that you have it open as you follow the next steps. + +- Press enter to continue +- Restrict API access **Yes** +- Who should have access? **Authenticated and Guest users** +- What kind of access do you want for Authenticated users? **create, read, update, delete** +- What kind of access do you want for Guest users? **read** + +When configuration of your API is complete, the CLI displays a message confirming that you have configured local CLI metadata for this category. You can confirm this by running `amplify status`. Finally deploy your changes to the cloud: + +Amplify CLI restricts API access combining Amazon Cognito for authentication and AWS IAM (Identity and Access Management) for granting execution permissions on routes. + +- Do you want to add another path? **No** + +Deploy your new API. + +```bash +amplify push +``` + +At the end of this command you can take note of your new REST API url. + +```console +REST API endpoint: https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev +``` + +> REST APIs follow this pattern `https://{restapi-id}.execute-api.\{region}.amazonaws.com/\{environment}/\{path}`. + +Let's see an overview of all the resources created by Amplify CLI. + +```console +REST + |_ /items (path) + |_ itemsApi (Amazon API Gateway) + |_ itemsLambda (AWS Lambda) + |_ Logs (Amazon CloudWatch) +``` + +## Create REST API and restrict specific routes to specific User Pool Groups + +If your app uses User Pool Groups to manage different user types and would like to restrict access of specific routes to specific User Pool Groups. You can accomplish this by the following flow: + +- Create API route. +- Add API route handler function. +- Restrict-access to the API route to the User Pool Group. + +> The following example flow assumes the existence of two User Pool Groups : AdminUsers and GuestUsers for a Book store. The app would like to limit admin functionality like updating book records to the AdminUsers User Pool Group, while borrowing and returning books would be limited to the GuestUsers User Pool Group. +> +> - Path : /book/admin is restricted to AdminUsers and commands are handled by the bookAdminHandler lambda function +> - Path : /book/guest is restricted to GuestUsers and commands are handled by the bookGuestHandler lambda function + +```bash +amplify add api +$> ? Select from one of the below mentioned services: REST +$> ✔ Provide a friendly name for your resource to be used as a label for this category in the project: · mybookapi +$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/admin +$> ✔ Choose a Lambda source · Create a new Lambda function +$> ? Provide an AWS Lambda function name: bookAdminHandler +$> ? Choose the runtime that you want to use: NodeJS +$> ? Choose the function template that you want to use: Hello World +$> ? Do you want to configure advanced settings? No +$> ? Do you want to edit the local lambda function now? No +Successfully added resource bookAdminHandler locally. +$> ✔ Restrict API access? (Y/n) · yes +$> ✔ Restrict access by: · Individual Groups +$> ✔ Select groups: AdminUsers +$> ✔ What permissions do you want to grant to AdminUsers users? · create, read, update, delete +$> ✔ Do you want to add another path? (y/N) · yes +$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/guest +$> ✔ Choose a Lambda source · Create a new Lambda function +$> ? Provide an AWS Lambda function name: bookGuestHandler +$> ? Choose the runtime that you want to use: NodeJS +$> ? Choose the function template that you want to use: Hello World +$> ? Do you want to configure advanced settings? No +$> ? Do you want to edit the local lambda function now? No +Successfully added resource bookGuestHandler locally. +$> ✔ Restrict API access? (Y/n) · yes +$> ✔ Restrict access by: Individual Groups +$> ✔ Select groups: GuestUsers +$> ✔ What permissions do you want to grant to GuestUsers users? create, read, update +$> ✔ Do you want to add another path? (y/N) No +✅ Successfully added resource mybookapi locally +``` + +At the end of this command you can verify the routes and their respective User Pool Group restrictions in the `cli-inputs.json` file at the following path. + +```bash + /amplify/backend/api//cli-inputs.json +``` + +## REST endpoint that triggers new Lambda functions + +During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths. + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source Create a new Lambda function +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda +? Provide the AWS Lambda function name: itemsLambda +? Choose the function template that you want to use: + CRUD function for Amazon DynamoDB +❯ Serverless ExpressJS function +``` + +## REST endpoint that triggers existing Lambda functions + +During the CLI setup, you'll be guided through to use your own Lambda functions which you've initialized as a part of your CLI project using the `amplify add function` command. This would allow you to have custom logic in your Lambda function and not use the predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) templates generated by the CLI as in the examples above. + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source + Create a new Lambda function +❯ Use a Lambda function already added in the current Amplify project +``` + +## Set up a REST API with Amazon DynamoDB + +During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths with support for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command). + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services REST +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi +? Provide a path (e.g., /book/\{isbn}) /items +? Choose a Lambda source Create a new Lambda function +? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda +? Provide the AWS Lambda function name: itemsLambda +? Choose the function template that you want to use: +❯ CRUD function for Amazon DynamoDB + Serverless ExpressJS function +``` + +In the example above with `/items` path, the following API will be created for you: + +1. GET /items/[ID] will return a list containing the item at the [ID]. If the item does not exist then an empty array is returned. +2. GET /items/object/[ID] will return a single item at [ID]. If the item does not exist then an empty object is returned. +3. PUT /items with your item in the request body will create or update the item. +4. POST /items with your item in the request body will create or update the item. +5. DELETE /items/object/[ID] will delete the item. + +When you have a sort key, you can append it to the end of the path, for example: `GET /items/object/[ID]/[SORT_KEY_ID]` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx new file mode 100644 index 00000000000..667062f433d --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx @@ -0,0 +1,325 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Override Amplify-generated API Gateway resources', + description: "The 'amplify override api' command generates a developer-configurable 'overrides' TypeScript file which provides Amplify-generated API Gateway resources as CDK constructs. For example, developers can configure a custom description or the minimum compression size of their REST API.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/restapi/override-api-gateway/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import ios_maintenance from '/src/fragments/lib-v1/ios-maintenance.mdx'; + + + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +```bash +amplify override api +``` + +Run the command above to override Amplify-generated Amazon API Gateway resources. + +The command creates a new `overrides.ts` file under `amplify/backend/api//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). + +Apply all the overrides in the `override(...)` function. For example: + +```ts +// This file is used to override the REST API resources configuration +import { AmplifyApiRestResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyApiRestResourceStackTemplate) { + resources.restApi.description = "Custom description"; + resources.restApi.minimumCompressionSize = 1024; +} +``` + +To change a field on a particular path, use `resources.restApi.body.paths[\]`: + +```ts +export function override(resources: AmplifyApiRestResourceStackTemplate) { + // Change the default CORS response header Access-Control-Allow-Origin from "'*'" to the API's domain + resources.restApi.body.paths['/items'].options['x-amazon-apigateway-integration'].responses.default.responseParameters['method.response.header.Access-Control-Allow-Origin'] = { 'Fn::Sub': "'https://${ApiId}.execute-api.${AWS::Region}.amazonaws.com'" }; +} +``` + +You can override the following REST API resources that Amplify generates: + +
+ +|Amplify-generated resource|Description| +|-|-| +|[restApi](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html)|The Amazon API Gateway REST API created by `amplify add api`| +|[deploymentResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-deployment.html)|The deployment resource that deploys the REST API above to a stage.| +|[policies](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|User pool group-related IAM policy. Example: `resources.policies["/items"].groups["Admin"]` + + +
+ +## Authorize API requests with Cognito User Pools + +Amazon Cognito User Pools is a common service to use alongside API Gateway when +adding user Sign-Up and Sign-In to your application. If your application needs to +interact with other AWS services such as S3 on behalf of the user who invoked +an endpoint, you will need to use IAM credentials with Cognito Identity Pools. + +Amplify CLI does not support Cognito User Pool authorizers out-of-the-box. To +implement this functionality, you must override your REST API and add a Cognito +User Pool authorizer yourself by adding the following code into the +`override(...)` function, in order. + +First, assuming the Cognito User Pool you would like to use as an authorizer is +the Auth resource configured with your Amplify Project, create a parameter that resolves +to its User Pool ARN: + +```ts +// Replace the following with your Auth resource name +const authResourceName = ""; +const userPoolArnParameter = "AuthCognitoUserPoolArn"; + +// Add a parameter to your Cloud Formation Template for the User Pool's ID +resources.addCfnParameter({ + type: "String", + description: "The ARN of an existing Cognito User Pool to authorize requests", + default: "NONE", + }, + userPoolArnParameter, + { "Fn::GetAtt": [`auth${authResourceName}`, "Outputs.UserPoolArn"], } +); +``` + + + +Make sure to replace `` with the name of your auth resource. +This is the name of the folder in `amplify/backend/auth` that was created when +you added an Auth resource to your Amplify project. + + + +Now, define a REST API authorizer with Cognito User Pools using the OpenAPI extension, [`x-amazon-apigateway-authorizer`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html). This change will be applied by modifying the security definition of your REST API: + +```ts +// Create the authorizer using the AuthCognitoUserPoolArn parameter defined above +resources.restApi.addPropertyOverride("Body.securityDefinitions", { + Cognito: { + type: "apiKey", + name: "Authorization", + in: "header", + "x-amazon-apigateway-authtype": "cognito_user_pools", + "x-amazon-apigateway-authorizer": { + type: "cognito_user_pools", + providerARNs: [ + { + 'Fn::Join': ['', [{ Ref: userPoolArnParameter }]], + }, + ], + }, + }, +}); +``` + +Finally, update the security methods for all of the paths in your REST API to +use this new Cognito User Pool authorizer. You also add the `Authorization` header +as a parameter on incoming requests for these paths as a place for users to provide +their Cognito User ID Tokens. + +```ts +// For every path in your REST API +for (const path in resources.restApi.body.paths) { + // Add the Authorization header as a parameter to requests + resources.restApi.addPropertyOverride( + `Body.paths.${path}.x-amazon-apigateway-any-method.parameters`, + [ + ...resources.restApi.body.paths[path]["x-amazon-apigateway-any-method"] + .parameters, + { + name: "Authorization", + in: "header", + required: false, + type: "string", + }, + ] + ); + // Use your new Cognito User Pool authorizer for security + resources.restApi.addPropertyOverride( + `Body.paths.${path}.x-amazon-apigateway-any-method.security`, + [ { Cognito: [], }, ] + ); +} +``` + + + +Note that you can add more advanced logic to only use the Cognito User Pool authorizer +with some paths or methods. + + + +When performing requests to your REST API, make sure to add the `Authorization` +header with an ID Token provided by Cognito. + +Requests to endpoints are now populated with information from Cognito about the +user who is invoking the +endpoint, and you can reuse the verified ID Token in your endpoint resolvers to assume +the identity of the user for accessing other services like AWS AppSync or S3. + +## Authorize API requests with Lambda authorizer + +While Amplify CLI does not support Lambda authorizers natively out-of-box, you can implement this functionality by overriding your REST API resources. The following steps will walk you through how to create token-based Lambda authorizer. + +First, you need to have a Lambda authorizer function with required [authorization logic](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html#api-gateway-lambda-authorizer-lambda-function-create) in your Amplify project to use it as an authorizer. Refer to the steps to [set up a function](/[platform]/build-a-backend/functions/set-up-function/) using `amplify add function` + +After running `amplify override api`, add the following code to `override(...)` function. +Initially, create a parameter that resolves to Lambda Function ARN + +```ts +// Replace the following with your Function resource name +const functionResourcename = ""; +const functionArnParameter = "FunctionArn"; + +// Adding parameter to your Cloud Formation Template for Authorizer function arn +resources.addCfnParameter( + { + type: "String", + description: "The ARN of an existing Lambda Function to authorize requests", + default: "NONE", + }, + functionArnParameter, + { "Fn::GetAtt": [`function${functionResourcename}`, "Outputs.Arn"], } +); +``` + + + + +Make sure to replace `` with the name of your function resource. This is the name of the folder in `amplify/backend/function` that was created when you added an function resource to your Amplify project. + + + +Next, define the Lambda authorizer using the OpenAPI extension, [`x-amazon-apigateway-authorizer`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html). This change will be applied by modifying the security definition of your REST API: + +```ts +// Create the authorizer using the functionArnParameter parameter defined above +resources.restApi.addPropertyOverride("Body.securityDefinitions", { + Lambda: { + type: "apiKey", + name: "Authorization", + in: "header", + "x-amazon-apigateway-authtype": "oauth2", + "x-amazon-apigateway-authorizer": { + type: "token", + authorizerUri: + { + 'Fn::Join': [ + '', + [ + "arn:aws:apigateway:", + { Ref: 'AWS::Region' }, + ":lambda:path/2015-03-31/functions/", + { Ref: functionArnParameter }, + "/invocations" + ] + ], + }, + authorizerResultTtlInSeconds: 0 + }, + }, +}); +``` + +As API Gateway needs permission to invoke the Authorizer lambda function, add resource based policy to the function using following code: + +```ts +// Adding Resource Based policy to Lambda authorizer function +resources.addCfnResource( + { + type: "AWS::Lambda::Permission", + properties: { + Action: "lambda:InvokeFunction", + FunctionName: {Ref: functionArnParameter}, + Principal: "apigateway.amazonaws.com", + SourceArn:{ + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "" + }, + "/*/*" + ] + ] + } + } + }, + "LambdaAuthorizerResourceBasedPolicy" +); + +``` + + +Make sure to replace `` with the name of your Rest API resource. This is the name of the folder in `amplify/backend/api` that was created when you added an Rest API resource to your Amplify project. + + + +Finally, update the security methods for all of the paths in your REST API to use this new Lambda authorizer. You can also add the Authorization header as a parameter on incoming requests for these paths as a place for users to provide their Auth token. + +```ts +for (const path in resources.restApi.body.paths) { + // Add the Authorization header as a parameter to requests + resources.restApi.addPropertyOverride( + `Body.paths.${path}.x-amazon-apigateway-any-method.parameters`, + [ + ...resources.restApi.body.paths[path]["x-amazon-apigateway-any-method"] + .parameters, + { + name: "Authorization", + in: "header", + required: false, + type: "string", + }, + ] + ); + // Use your new Lambda authorizer for security + resources.restApi.addPropertyOverride( + `Body.paths.${path}.x-amazon-apigateway-any-method.security`, + [ { Lambda: [], }, ] + ); +} +``` + +Note that you can add more advanced logic to only use the Lambda authorizer with some paths or methods. + + +When performing requests to your REST API, make sure to add the Authorization header with an token required by Lambda authorizer function. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx new file mode 100644 index 00000000000..3be70d14016 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx @@ -0,0 +1,185 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Test the REST API', + description: 'Learn how you can test the REST API from the terminal, with Amplify Mock, or with the API Gateway console.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/restapi/test-api/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import ios_maintenance from '/src/fragments/lib-v1/ios-maintenance.mdx'; + + + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +## Test the API from the terminal + +If Guest users have access to your REST API you can test it from the terminal using Curl. + +[Curl](https://github.com/curl/curl) is a command-line tool that lets you transfer data to and from a server using various protocols. + +> Curl is available in many distributions including Mac, Windows and Linux. Follow the install instructions in the [docs](https://curl.haxx.se/docs/install.html). + + + + +### GET method example + +```bash +curl https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos +``` + +### POST method example + +```bash +curl -H "Content-Type: application/json" -d '{"name":"todo-1"}' https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos +``` + + + + +### GET method example + +```bash +curl https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos +``` + +### POST method example + +```bash +curl -H "Content-Type: application/json" -d {\"name\":\"todo-1\"} https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos +``` + + + + +> Important! Testing methods using production endpoints may result in changes to resources that cannot be undone. + +## Test the API with Amplify Mock + +Amplify CLI allows you to quickly test your REST APIs by using the `amplify mock function` command. + +Let's test your new REST API using the route below with HTTP Method `GET` and path `/todos?limit=10` which includes a `limit` query string parameter. + +```console +GET /todos?limit=10 +``` + +> Important! Testing methods using production endpoints may result in changes to resources that cannot be undone. + +Before you continue, edit the file at `{project}/amplify/backend/function/todosLambda/src/event.json` and replace its content for the purpose of the test. + +```json +{ + "httpMethod": "GET", + "path": "/todos", + "queryStringParameters": { + "limit": "10" + } +} +``` + +Make sure you have saved the changes and run + +```bash +amplify mock function todosLambda +``` + +Select the following options: + +- Provide the path to the event JSON object relative to `{project}/amplify/backend/function/todosLambda` __src/event.json__ + +```console +Starting execution... +EVENT: {"httpMethod":"GET","path":"/todos","queryStringParameters":{"limit":"10"}} +App started + +Result: +{"statusCode":200,"body":"{\"success\":\"get call succeed!\",\"url\":\"/todos?limit=10\"}","headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","content-type":"application/json; charset=utf-8","content-length":"55", "date":"Tue, 18 Aug 2020 16:50:53 GMT","connection":"close"},"isBase64Encoded":false} +Finished execution. +``` + +## Test the API with API Gateway console + +Let's test your new REST API using the route below with HTTP Method `GET` and path `/todos?limit=10` which includes a `limit` query string parameter. + +```console +GET /todos?limit=10 +``` + +> Important! Testing methods with the API Gateway console may result in changes to resources that cannot be undone. + +- Sign in to the API Gateway console at [https://console.aws.amazon.com/apigateway](https://console.aws.amazon.com/apigateway). +- Choose the `todosApi` REST API. +- In the Resources pane, choose the method you want to test. Pick `ANY` right under `/todos`. + +```console +/ + |_ /todos Main resource. Eg: /todos + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser + |_ /{proxy+} Proxy resource. Eg: /todos/, /todos/id, todos/object/{id} + ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT + OPTIONS Allow pre-flight requests in CORS by browser +``` + +- In the Method Execution pane, in the Client box, choose TEST. Choose the `GET` method. Add `limit=10` to the Query String `{todos}` field. + +- Choose Test to run the test for `GET /todos?limit=10`. The following information will be displayed: request, status, latency, response body, response headers and logs. + +```bash +Request: /todos?limit=10 +Status: 200 +Latency: 139 ms +Response Body +{ + "success": "get call succeed!", + "url": "/todos?limit=10" +} +Response Headers +{"access-control-allow-origin":"*","date":"Tue, 18 Aug 2020 17:36:14 GMT","content-length":"55","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","x-powered-by":"Express","content-type":"application/json; charset=utf-8","connection":"close"} +Logs +Execution log for request 4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f +Tue Aug 18 17:36:14 UTC 2020 : Starting execution for request: 4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f +Tue Aug 18 17:36:14 UTC 2020 : HTTP Method: GET, Resource Path: /todos +Tue Aug 18 17:36:14 UTC 2020 : Method request path: {} +Tue Aug 18 17:36:14 UTC 2020 : Method request query string: {limit=10} +Tue Aug 18 17:36:14 UTC 2020 : Method request headers: {} +Tue Aug 18 17:36:14 UTC 2020 : Method request body before transformations: +Tue Aug 18 17:36:14 UTC 2020 : Endpoint request URI: https://lambda.eu-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-west-2:664371068953:function:expressLambda-dev/invocations +Tue Aug 18 17:36:14 UTC 2020 : Endpoint request headers: { X-Amz-Date=20200818T173614Z, X-Amz-Source-Arn=arn:aws:execute-api:eu-west-2:664371068953:s3zmw6fqy5/test-invoke-stage/GET/todos, Accept=application/json, User-Agent=AmazonAPIGateway_s3zmw6fqy5, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDEaCWV1LXdlc3QtMiJGMEQCIC3KIeR66WhaCBw+eJ+GPhF7y4hz9xC2nN+ARb7T3psyAiBdsoaD9yMfiw2dHWjQM5x7vM11XmToNSGu64mckUQdzSq0AwgaEAEaDDU0NDM4ODgxNjY2MyIMIzObNbCd6QtYwb0IKpEDpHXEzkM2OYq7JfL0U/WbF09KNamodfnifRYwZd/GNOwykykc/zHiU9X0XZPRd+QTnQe/9eoy8DaxBkDgRzQQjTThQWJWadtcfjryTLRKpVeo1UueL+f6DTUDf+URjb0P9CN1gPm+ntZD3LSyAXGwACKG7YMA5/HyeEk [TRUNCATED] +Tue Aug 18 17:36:14 UTC 2020 : Endpoint request body after transformations: {"resource":"/todos","path":"/todos","httpMethod":"GET","headers":null,"multiValueHeaders":null,"queryStringParameters":{"limit":"10"},"multiValueQueryStringParameters":{"limit":["10"]},"pathParameters":null,"stageVariables":null,"requestContext":{"resourcePath":"/todos","httpMethod":"GET","requestTime":"18/Aug/2020:17:36:14 +0000","path":"/todos","accountId":"EXAMPLE_ID","protocol":"HTTP/1.1","stage":"test-invoke-stage","domainPrefix":"testPrefix","requestTimeEpoch":1597772174890,"requestId":"4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f","identity":{"cognitoIdentityPoolId":null,"cognitoIdentityId":null,"apiKey":"test-invoke-api-key","principalOrgId":null,"cognitoAuthenticationType":null,"userArn":"arn:aws:iam::664371068953:root","apiKeyId":"test-invoke-api-key-id","userAgent":"aws-internal/3 aws-sdk-java/1.11.820 Linux/4.9.217-0.1.ac.205.84.332.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.252-b09 java/1.8.0_252 v [TRUNCATED] +Tue Aug 18 17:36:14 UTC 2020 : Sending request to https://lambda.eu-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-west-2:664371068953:function:expressLambda-dev/invocations +Tue Aug 18 17:36:15 UTC 2020 : Received response. Status: 200, Integration latency: 137 ms +Tue Aug 18 17:36:15 UTC 2020 : Endpoint response headers: {Date=Tue, 18 Aug 2020 17:36:15 GMT, Content-Type=application/json, Content-Length=443, Connection=keep-alive, sampled=0} +Tue Aug 18 17:36:15 UTC 2020 : Endpoint response body before transformations: {"statusCode":200,"body":"{\"success\":\"get call succeed!\",\"url\":\"/todos?limit=10\"}","headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","content-type":"application/json; charset=utf-8","content-length":"55","date":"Tue, 18 Aug 2020 17:36:14 GMT","connection":"close"},"isBase64Encoded":false} +Tue Aug 18 17:36:15 UTC 2020 : Method response body after transformations: {"success":"get call succeed!","url":"/todos?limit=10"} +Tue Aug 18 17:36:15 UTC 2020 : Method response headers: {x-powered-by=Express, access-control-allow-origin=*, access-control-allow-headers=Origin, X-Requested-With, Content-Type, Accept, content-type=application/json; charset=utf-8, content-length=55, date=Tue, 18 Aug 2020 17:36:14 GMT, connection=close, Sampled=0} +Tue Aug 18 17:36:15 UTC 2020 : Successfully completed execution +Tue Aug 18 17:36:15 UTC 2020 : Method completed with status: 200 +``` From 1db0e42a7640cb5db47b98d5bec7df93007ccb7e Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 6 May 2024 22:21:40 -0700 Subject: [PATCH 24/88] chore: fix storage and functions v1 gen 1 and --- src/directory/directory.mjs | 35 ++ .../configureaccess/10_protected_upload.mdx | 42 +- .../configureaccess/20_protected_download.mdx | 50 +- .../configureaccess/30_private_upload.mdx | 32 +- .../configureaccess/40_private_download.mdx | 33 +- .../lib-v1/storage/flutter/download.mdx | 96 +++- .../flutter/getting-started/10_preReq.mdx | 15 +- .../flutter/getting-started/20_installLib.mdx | 10 +- src/fragments/lib-v1/storage/flutter/list.mdx | 59 +- .../lib-v1/storage/flutter/remove.mdx | 46 +- .../lib-v1/storage/flutter/upload.mdx | 274 ++++++++-- .../flutter/upload/upload-create-file.mdx | 62 ++- .../native_common/configureaccess/common.mdx | 111 ++++ .../native_common/getting-started/common.mdx | 12 +- src/fragments/lib/storage/flutter/copy.mdx | 23 +- .../lib/storage/flutter/get-properties.mdx | 8 +- .../functions/build-options/index.mdx | 141 +++++ .../functions/configure-options/index.mdx | 114 ++++ .../functions/environment-variables/index.mdx | 86 +++ .../functions/graphql-from-lambda/index.mdx | 504 ++++++++++++++++++ .../prev/build-a-backend/functions/index.mdx | 36 ++ .../functions/layers/index.mdx | 229 ++++++++ .../functions/secrets/index.mdx | 105 ++++ .../functions/set-up-function/index.mdx | 161 ++++++ .../storage/configure-storage/index.mdx | 190 +++++++ .../build-a-backend/storage/import/index.mdx | 260 +++++++++ .../index.mdx | 114 ++++ .../storage/transfer-acceleration/index.mdx | 10 + 28 files changed, 2644 insertions(+), 214 deletions(-) create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index d1502333b3b..611eafb265b 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -2377,6 +2377,9 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/index.mdx', children: [ + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/set-up-storage/index.mdx' }, @@ -2413,12 +2416,18 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/lambda-triggers/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/custom-plugin/index.mdx' }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/existing-resources/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/sdk/index.mdx' }, @@ -2427,6 +2436,32 @@ export const directory = { } ] }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx', + children: [ + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/build-a-backend/functions/build-options/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/build-a-backend/functions/configure-options/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/build-a-backend/functions/graphql-from-lambda/index.mdx' + } + ] + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx', children: [ diff --git a/src/fragments/lib-v1/storage/flutter/configureaccess/10_protected_upload.mdx b/src/fragments/lib-v1/storage/flutter/configureaccess/10_protected_upload.mdx index 076422e037b..005422a52ea 100644 --- a/src/fragments/lib-v1/storage/flutter/configureaccess/10_protected_upload.mdx +++ b/src/fragments/lib-v1/storage/flutter/configureaccess/10_protected_upload.mdx @@ -1,35 +1,25 @@ ```dart -import 'dart:io'; -import 'package:path_provider/path_provider.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; -Future uploadProtected() async { - // Create a dummy file - const exampleString = 'Example file contents'; - final tempDir = await getTemporaryDirectory(); - final exampleFile = File(tempDir.path + '/example.txt') - ..createSync() - ..writeAsStringSync(exampleString); - - // Set the access level to `protected` for the current user - // Note: A user must be logged in through Cognito Auth - // for this to work. - final uploadOptions = S3UploadFileOptions( +Future uploadProtectedFile({ + required String filePath, + required String key, +}) async { + final awsFile = AWSFile.fromPath(filePath); + const options = StorageUploadFileOptions( accessLevel: StorageAccessLevel.protected, ); - // Upload the file to S3 with protected access try { - final UploadFileResult result = await Amplify.Storage.uploadFile( - local: exampleFile, - key: 'ExampleKey', - options: uploadOptions, - onProgress: (progress) { - safePrint('Fraction completed: ${progress.getFractionCompleted()}'); - } - ); - safePrint('Successfully uploaded file: ${result.key}'); + final uploadResult = await Amplify.Storage.uploadFile( + localFile: awsFile, + key: key, + options: options, + ).result; + safePrint('Uploaded file: ${uploadResult.uploadedItem.key}'); } on StorageException catch (e) { - safePrint('Error uploading protected file: $e'); + safePrint('Something went wrong uploading file: ${e.message}'); + rethrow; } } -``` \ No newline at end of file +``` diff --git a/src/fragments/lib-v1/storage/flutter/configureaccess/20_protected_download.mdx b/src/fragments/lib-v1/storage/flutter/configureaccess/20_protected_download.mdx index 43323c9bb1b..e5eacf5dffd 100644 --- a/src/fragments/lib-v1/storage/flutter/configureaccess/20_protected_download.mdx +++ b/src/fragments/lib-v1/storage/flutter/configureaccess/20_protected_download.mdx @@ -1,35 +1,33 @@ ```dart -import 'dart:io'; -import 'package:path_provider/path_provider.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; -Future downloadProtected(String cognitoIdentityId) async { - // Create a file to store downloaded contents - final documentsDir = await getApplicationDocumentsDirectory(); - final filepath = documentsDir.path + '/example.txt'; - final file = File(filepath); - - // Set access level and Cognito Identity ID. - // Note: `targetIdentityId` is only needed when downloading - // protected files of a user other than the one currently - // logged in. - final downloadOptions = S3DownloadFileOptions( +Future downloadProtectedFile({ + required String key, + required String targetIdentityId, + required String downloadPath, +}) async { + final awsFile = AWSFile.fromPath(downloadPath); + final options = StorageDownloadFileOptions( + // specify that the file has a protected access level accessLevel: StorageAccessLevel.protected, - - // e.g. us-west-2:2f41a152-14d1-45ff-9715-53e20751c7ee - targetIdentityId: cognitoIdentityId, + // specify the identity ID of the user who uploaded this file + pluginOptions: S3DownloadFilePluginOptions.forIdentity( + targetIdentityId, + ), ); - // Download protected file and read contents try { - await Amplify.Storage.downloadFile( - key: 'ExampleKey', - local: file, - options: downloadOptions, - ); - final contents = file.readAsStringSync(); - safePrint('Got protected file with contents: $contents'); + final result = await Amplify.Storage.downloadFile( + key: key, + localFile: awsFile, + options: options, + ).result; + + safePrint('Downloaded file is located at: ${result.localFile.path}'); } on StorageException catch (e) { - safePrint('Error downloading protected file: $e'); + safePrint('Something went wrong downloading the file: ${e.message}'); + rethrow; } } -``` \ No newline at end of file +``` diff --git a/src/fragments/lib-v1/storage/flutter/configureaccess/30_private_upload.mdx b/src/fragments/lib-v1/storage/flutter/configureaccess/30_private_upload.mdx index eca58ec392c..d0d2370a31e 100644 --- a/src/fragments/lib-v1/storage/flutter/configureaccess/30_private_upload.mdx +++ b/src/fragments/lib-v1/storage/flutter/configureaccess/30_private_upload.mdx @@ -1,20 +1,26 @@ ```dart -Future uploadPrivateFile() async { - ... - // Update the uploadOptions - final uploadOptions = S3UploadFileOptions( - // Add the private access level +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future uploadPrivateFile({ + required String filePath, + required String key, +}) async { + final awsFile = AWSFile.fromPath(filePath); + const options = StorageUploadFileOptions( accessLevel: StorageAccessLevel.private, ); try { - final UploadFileResult result = await Amplify.Storage.uploadFile( - ... - //Be sure to use the options - options: uploadOptions, - ... - ); - safePrint('Successfully uploaded file: ${result.key}'); - }... + final uploadResult = await Amplify.Storage.uploadFile( + localFile: awsFile, + key: key, + options: options, + ).result; + + safePrint('Uploaded file: ${uploadResult.uploadedItem.key}'); + } on StorageException catch (e) { + safePrint('Something went wrong uploading file: ${e.message}'); + rethrow; + } } ``` diff --git a/src/fragments/lib-v1/storage/flutter/configureaccess/40_private_download.mdx b/src/fragments/lib-v1/storage/flutter/configureaccess/40_private_download.mdx index 9d92e716c0b..1cbd1a7ca6d 100644 --- a/src/fragments/lib-v1/storage/flutter/configureaccess/40_private_download.mdx +++ b/src/fragments/lib-v1/storage/flutter/configureaccess/40_private_download.mdx @@ -1,19 +1,26 @@ ```dart -Future downloadPrivateFile(String cognitoIdentityId) async { - ... - final downloadOptions = S3DownloadFileOptions( - // Add the private access level - accessLevel: StorageAccessLevel.private - ... +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future downloadPrivateFile({ + required String key, + required String downloadPath, +}) async { + final awsFile = AWSFile.fromPath(downloadPath); + const options = StorageDownloadFileOptions( + accessLevel: StorageAccessLevel.private, ); try { - await Amplify.Storage.downloadFile( - ... - // Be sure to use the correct options - options: downloadOptions, - ); - ... - }... + final result = await Amplify.Storage.downloadFile( + key: key, + localFile: awsFile, + options: options, + ).result; + + safePrint('Downloaded file is located at: ${result.localFile.path}'); + } on StorageException catch (e) { + safePrint('Something went wrong downloading the file: ${e.message}'); + rethrow; + } } ``` diff --git a/src/fragments/lib-v1/storage/flutter/download.mdx b/src/fragments/lib-v1/storage/flutter/download.mdx index f31bcfeea8e..e7b4a508985 100644 --- a/src/fragments/lib-v1/storage/flutter/download.mdx +++ b/src/fragments/lib-v1/storage/flutter/download.mdx @@ -6,46 +6,104 @@ You can download file to a local directory using `Amplify.Storage.downloadFile`. You can use the [path_provider](https://pub.dev/packages/path_provider) package to create a local file in the user's documents directory where you can store the downloaded data. + + + + ```dart -import 'dart:io'; import 'package:path_provider/path_provider.dart'; -Future downloadFile() async { +Future downloadToLocalFile(String key) async { final documentsDir = await getApplicationDocumentsDirectory(); final filepath = documentsDir.path + '/example.txt'; - final file = File(filepath); + try { + final result = await Amplify.Storage.downloadFile( + key: key, + localFile: AWSFile.fromPath(filepath), + onProgress: (progress) { + safePrint('Fraction completed: ${progress.fractionCompleted}'); + }, + ).result; + safePrint('Downloaded file is located at: ${result.localFile.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + + + +On Web, the download process will be handled by the browser. You can provide the downloaded file name by specifying the `path` parameter of `AWSFile.fromPath`. E.g. this instructs the browser to download the file `download.txt`. + +```dart +Future downloadToLocalFileOnWeb(String key) async { try { final result = await Amplify.Storage.downloadFile( - key: 'ExampleKey', - local: file, + key: key, + localFile: AWSFile.fromPath('download.txt'), + ).result; + + safePrint('Downloaded file: ${result.downloadedItem.key}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + + + +## Download data + +You can download a file to in-memory buffer with `Amplify.Storage.downloadData`: + +```dart +Future downloadToMemory(String key) async { + try { + final result = await Amplify.Storage.downloadData( + key: key, onProgress: (progress) { - safePrint('Fraction completed: ${progress.getFractionCompleted()}'); + safePrint('Fraction completed: ${progress.fractionCompleted}'); }, - ); - final contents = result.file.readAsStringSync(); - // Or you can reference the file that is created above - // final contents = file.readAsStringSync(); - safePrint('Downloaded contents: $contents'); + ).result; + + safePrint('Downloaded data: ${result.bytes}'); } on StorageException catch (e) { - safePrint('Error downloading file: $e'); + safePrint(e.message); } } ``` ## Generate a download URL -You can get a downloadable URL for the file in storage by its key using `Amplify.Storage.getUrl`. +You can get a downloadable URL for the file in storage by its key and access level. + +When creating a downloadable URL, you can choose to check if the file exists by setting `validateObjectExistence` to `true` in `S3GetUrlPluginOptions`. If the file is inaccessible or does not exist, a `StorageException` is thrown. This allows you to check if an object exists during generating the presigned URL, which you can then use to download that object. ```dart -Future getDownloadUrl() async { +Future getDownloadUrl({ + required String key, + required StorageAccessLevel accessLevel, +}) async { try { - final result = await Amplify.Storage.getUrl(key: 'ExampleKey'); - // NOTE: This code is only for demonstration - // Your debug console may truncate the printed url string - safePrint('Got URL: ${result.url}'); + final result = await Amplify.Storage.getUrl( + key: key, + options: const StorageGetUrlOptions( + accessLevel: accessLevel, + pluginOptions: S3GetUrlPluginOptions( + validateObjectExistence: true, + expiresIn: Duration(days: 1), + ), + ), + ).result; + return result.url.toString(); } on StorageException catch (e) { - safePrint('Error getting download URL: $e'); + safePrint('Could not get a downloadable URL: ${e.message}'); + rethrow; } } ``` diff --git a/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx index 145d78787e1..9338778fc30 100644 --- a/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx @@ -1,6 +1,11 @@ -* A Flutter application targeting Flutter SDK >= 2.10.0 (stable version) with Amplify libraries integrated - * An iOS configuration targeting at least iOS 11.0 - * An Android configuration targeting at least Android API level 21 (Android 5.0) or above - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) +* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated -You can see [an example Amplify Storage + Flutter application here](https://github.com/aws-amplify/amplify-flutter/tree/main/packages/storage/amplify_storage_s3/example). + The following are also required, depending on which platforms you are targeting: + + * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 + * An Android configuration targeting at least Android API level 24 (Android 7.0) or above + * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) + * Any Windows OS meeting Flutter minimums + * macOS version 10.15 or higher + * Any Ubuntu Linux distribution meeting Flutter minimums + * For a full example please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) diff --git a/src/fragments/lib-v1/storage/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/storage/flutter/getting-started/20_installLib.mdx index 482751e9412..c4629c0e637 100644 --- a/src/fragments/lib-v1/storage/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/storage/flutter/getting-started/20_installLib.mdx @@ -2,12 +2,14 @@ Add the following dependency to your **app**'s `pubspec.yaml` along with others ```yaml environment: - sdk: ">=2.15.0 <3.0.0" + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" dependencies: - amplify_auth_cognito: ^0.6.0 - amplify_flutter: ^0.6.0 - amplify_storage_s3: ^0.6.0 flutter: sdk: flutter + + amplify_auth_cognito: ^1.0.0 + amplify_flutter: ^1.0.0 + amplify_storage_s3: ^1.0.0 ``` diff --git a/src/fragments/lib-v1/storage/flutter/list.mdx b/src/fragments/lib-v1/storage/flutter/list.mdx index 85c4dedc4c2..98387d0c559 100644 --- a/src/fragments/lib-v1/storage/flutter/list.mdx +++ b/src/fragments/lib-v1/storage/flutter/list.mdx @@ -1,15 +1,64 @@ You can list all files uploaded under a given path. +This will list all files located under path `album` that: +* have `private` access level +* are in the root of `album/` (the result doesn't include files under any sub path) + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +Future listAlbum() async { + try { + String? nextToken; + bool hasNextPage; + do { + final result = await Amplify.Storage.list( + path: 'album/', + options: StorageListOptions( + accessLevel: StorageAccessLevel.private, + pageSize: 50, + nextToken: nextToken, + pluginOptions: const S3ListPluginOptions( + excludeSubPaths: true, + ), + ), + ).result; + safePrint('Listed items: ${result.items}'); + nextToken = result.nextToken; + hasNextPage = result.hasNextPage; + } while (hasNextPage); + } on StorageException catch (e) { + safePrint('Error listing files: ${e.message}'); + rethrow; + } +} +``` + +Pagination is enabled by default. The default `pageSize` is `1000` if it is not set in the `StorageListOptions`. + +You can also list all files under a given path without pagination by using the `pluginOptions` and `S3ListPluginOptions.listAll()` constructor. + This will list all public files (i.e. those with `guest` access level): ```dart -Future listItems() async { +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +Future listAllWithGuestAccessLevel() async { try { - final result = await Amplify.Storage.list(); - final items = result.items; - safePrint('Got items: $items'); + final result = await Amplify.Storage.list( + options: const StorageListOptions( + accessLevel: StorageAccessLevel.guest, + pluginOptions: S3ListPluginOptions.listAll(), + ), + ).result; + + safePrint('Listed items: ${result.items}'); } on StorageException catch (e) { - safePrint('Error listing items: $e'); + safePrint('Error listing files: ${e.message}'); + rethrow; } } ``` +import { delimiter } from "path" diff --git a/src/fragments/lib-v1/storage/flutter/remove.mdx b/src/fragments/lib-v1/storage/flutter/remove.mdx index b97a5f9ad3f..ad4d42004a1 100644 --- a/src/fragments/lib-v1/storage/flutter/remove.mdx +++ b/src/fragments/lib-v1/storage/flutter/remove.mdx @@ -1,14 +1,50 @@ ## Remove a file -Remove a file uploaded to S3 by using `Amplify.Storage.remove` and specifying the key: +You can remove a single file using `Amplify.Storage.remove` with the `key` and its associated access level: ```dart -Future removeFile(String key) async { +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future removeFile({ + required String key, + required StorageAccessLevel accessLevel, +}) async { + try { + final result = await Amplify.Storage.remove( + key: key, + options: StorageRemoveOptions( + accessLevel: accessLevel, + ), + ).result; + safePrint('Removed file: ${result.removedItem.key}'); + } on StorageException catch (e) { + safePrint('Error deleting file: ${e.message}'); + rethrow; + } +} +``` + +## Remove multiple files + +You can remove multiple files using `Amplify.Storage.removeMany` with the `keys`, the files to be removed in a batch should have the same access level: + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future removePrivateFiles({ + required List keys, +}) async { try { - final result = await Amplify.Storage.remove(key: key); - safePrint('Removed file: ${result.key}'); + final result = await Amplify.Storage.removeMany( + keys: keys, + options: const StorageRemoveManyOptions( + accessLevel: StorageAccessLevel.private, + ), + ).result; + safePrint('Removed files: ${result.removedItems}'); } on StorageException catch (e) { - safePrint('Error deleting file: $e'); + safePrint('Error deleting files: ${e.message}'); + rethrow; } } ``` diff --git a/src/fragments/lib-v1/storage/flutter/upload.mdx b/src/fragments/lib-v1/storage/flutter/upload.mdx index 73ca38cc046..6d374b8e202 100644 --- a/src/fragments/lib-v1/storage/flutter/upload.mdx +++ b/src/fragments/lib-v1/storage/flutter/upload.mdx @@ -1,120 +1,278 @@ + +## Upload File + +To upload to S3 from a file, specify the `key` to upload the file to and the `localFile` to be uploaded. `localFile` can be an instance of `AWSFile` created from either an OS platform `File` instance or the result of Flutter file picker plugins such as [file_picker](https://pub.dev/packages/file_picker). + + + ## Upload File To upload to S3 from a file, specify the key and the local file to be uploaded. A file can be created locally, or retrieved from the user's device using a package such as [image_picker](https://pub.dev/packages/image_picker) or [file_picker](https://pub.dev/packages/file_picker). + + ### Upload a local file + -import flutter0 from "/src/fragments/lib-v1/storage/flutter/upload/upload-create-file.mdx"; + +### Upload platform `File` + - + -### Upload file with Flutter file picker packages +**Note**: To use `AWSFilePlatform`, add [aws_common](https://pub.dev/packages/aws_common) package to your Flutter project +by running: `flutter pub add aws_common` - + - +import flutter0 from "/src/fragments/lib-v1/storage/flutter/upload/upload-create-file.mdx"; -Make sure to follow the setup instructions on the image_picker [homepage](https://pub.dev/packages/image_picker). + -```dart -import 'dart:io'; +### Upload with Flutter file_picker plugin -import 'package:image_picker/image_picker.dart'; +The [file picker](https://pub.dev/packages/file_picker) plugin can be used to retrieve arbitrary file types from the user's device. -final picker = ImagePicker(); +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:file_picker/file_picker.dart'; Future uploadImage() async { - // Select image from user's gallery - final pickedFile = await picker.pickImage(source: ImageSource.gallery); + // Select a file from the device + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + withData: false, + // Ensure to get file stream for better performance + withReadStream: true, + allowedExtensions: ['jpg', 'png', 'gif'], + ); - if (pickedFile == null) { - safePrint('No image selected'); + if (result == null) { + safePrint('No file selected'); return; } - // Upload image with the current time as the key - final key = DateTime.now().toString(); - final file = File(pickedFile.path); + // Upload file with its filename as the key + final platformFile = result.files.single; try { final result = await Amplify.Storage.uploadFile( - local: file, - key: key, + localFile: AWSFile.fromStream( + platformFile.readStream!, + size: platformFile.size, + ), + key: platformFile.name, onProgress: (progress) { - safePrint('Fraction completed: ${progress.getFractionCompleted()}'); + safePrint('Fraction completed: ${progress.fractionCompleted}'); }, - ); - safePrint('Successfully uploaded image: ${result.key}'); + ).result; + safePrint('Successfully uploaded file: ${result.uploadedItem.key}'); } on StorageException catch (e) { - safePrint('Error uploading image: $e'); + safePrint('Error uploading file: $e'); + rethrow; } } ``` - +## Upload Data - +To upload to S3 from a data object, specify the `key` and `data`, where `data` is an instance of `S3DataPayload` created from various data formats. -The [file_picker](https://pub.dev/packages/file_picker) package can be used to retrieve arbitrary file types from the user's device. + + ```dart -import 'dart:io'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; -import 'package:file_picker/file_picker.dart'; +Future uploadStringData({ + required String dataString, + required String key, +}) async { + try { + final result = await Amplify.Storage.uploadData( + data: S3DataPayload.string( + dataString, + contentType: 'text/plain', + ), + key: key, + ).result; -Future uploadFile() async { - // Select a file from the device - final result = await FilePicker.platform.pickFiles(); + safePrint('Uploaded data: ${result.uploadedItem.key}'); + } on StorageException catch (e) { + safePrint('Error uploading data: ${e.message}'); + rethrow; + } +} +``` + - if (result == null) { - safePrint('No file selected'); - return; + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +Future uploadJsonObject({ + required Map json, + required String key, +}) async { + try { + final result = await Amplify.Storage.uploadData( + data: S3DataPayload.json(json), + key: key, + ).result; + + safePrint('Uploaded data: ${result.uploadedItem.key}'); + } on StorageException catch (e) { + safePrint('Error uploading data: ${e.message}'); + rethrow; } +} +``` + - // Upload file with its filename as the key - final platformFile = result.files.single; - final path = platformFile.path!; - final key = platformFile.name; - final file = File(path); + + +`S3DataPayload.dataUrl()` parses the provided data URL string, and throws exception if the data URL is invalid. See more info about [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs). + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; +Future uploadDataUrl({ + required String dataUrl, + required String key, +}) async { try { - final result = await Amplify.Storage.uploadFile( - local: file, + final result = await Amplify.Storage.uploadData( + data: S3DataPayload.dataUrl(dataUrl), key: key, - onProgress: (progress) { - safePrint('Fraction completed: ${progress.getFractionCompleted()}'); - }, - ); - safePrint('Successfully uploaded file: ${result.key}'); + ).result; + + safePrint('Uploaded data: ${result.uploadedItem.key}'); } on StorageException catch (e) { - safePrint('Error uploading file: $e'); + safePrint('Error uploading data: ${e.message}'); + rethrow; } } ``` + + + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +Future uploadBytes({ + required List bytes, + required String key, + required String contentType, +}) async { + try { + final result = await Amplify.Storage.uploadData( + data: S3DataPayload.bytes( + bytes, + contentType: contentType, + ), + key: key, + ).result; + safePrint('Uploaded data: ${result.uploadedItem.key}'); + } on StorageException catch (e) { + safePrint('Error uploading data: ${e.message}'); + rethrow; + } +} +``` ## Upload Options -You may attach metadata while uploading data or a file via specifying `metadata` in options. +You can attach metadata while uploading data or a file by specifying the `metadata` property in options. If you want the `metadata` to be included in the upload result, you can set the `getProperties` flag to `true` in options. ```dart -await Amplify.Storage.uploadFile( - key: 'file', - local: File('path/to/file'), - options: S3UploadFileOptions( - metadata: const { - 'project': 'ExampleProject', - }, - ), -); +Future uploadWithOptions() async { + // When uploading data, use `StorageUploadDataOptions` + final uploadDataOperation = Amplify.Storage.uploadData( + data: S3DataPayload.string( + 'example', + contentType: 'text/plain', + ), + key: 'example.txt', + options: const StorageUploadDataOptions( + metadata: { + 'project': 'ExampleProject', + }, + pluginOptions: S3UploadDataPluginOptions( + getProperties: true, + ), + ), + ); + final uploadDataResult = await uploadDataOperation.result; + safePrint( + 'Uploaded data with metadata: ${uploadDataResult.uploadedItem.metadata}', + ); + + // When uploading a file, use `StorageUploadFileOptions` + final uploadFileOperation = Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('path/to/example.txt'), + key: 'example.txt', + options: const StorageUploadFileOptions( + metadata: { + 'project': 'ExampleProject', + }, + pluginOptions: S3UploadFilePluginOptions( + getProperties: true, + ), + ), + ); + final uploadFileResult = await uploadFileOperation.result; + safePrint( + 'Uploaded file with metadata: ${uploadFileResult.uploadedItem.metadata}', + ); +} ``` -In S3 console, you should see the metadata attached to your file. You can learn more about the different access levels in [File access levels](/gen1/[platform]/prev/build-a-backend/storage/configure-access/). +The [`Amplify.Storage.getProperties` API](/gen1/[platform]/build-a-backend/storage/get-properties/) allows you to retrieve metadata without downloading the file. + +In S3 console, you should see the metadata attached to your file as the following. ![S3 Metadata](/images/s3_metadata.png) +## Control of Upload Operations + +A call to `Amplify.Storage.uploadFile` or `Amplify.Storage.uploadData` returns a reference to the operation that is performing the upload. + +To cancel the upload (for example, in response to the user pressing a Cancel button), simply call `.cancel()` on the returned upload operation. + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +S3UploadFileOperation? uploadOperation; + +Future uploadFile(String path) async { + try { + final storagePlugin = Amplify.Storage.getPlugin(AmplifyStorageS3.pluginKey); + uploadOperation = storagePlugin.uploadFile( + localFile: AWSFile.fromPath(path), + key: 'example_file.txt', + ); + + final result = await uploadOperation!.result; + safePrint('Uploaded ${result.uploadedItem.key}'); + } on StorageException catch (e) { + safePrint('Error uploading file: ${e.message}'); + } +} + +void cancelUpload() { + uploadOperation?.cancel(); + uploadOperation = null; +} +``` + ## Multipart upload Amplify will automatically perform a S3 multipart upload for files larger than 5MB. For more information about S3's multipart upload support, see [Uploading and copying objects using multipart upload](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html). diff --git a/src/fragments/lib-v1/storage/flutter/upload/upload-create-file.mdx b/src/fragments/lib-v1/storage/flutter/upload/upload-create-file.mdx index 1cd1711909a..6afe6a44946 100644 --- a/src/fragments/lib-v1/storage/flutter/upload/upload-create-file.mdx +++ b/src/fragments/lib-v1/storage/flutter/upload/upload-create-file.mdx @@ -1,37 +1,49 @@ + + +```dart +import 'dart:io' as io; -import flutter0 from "/src/fragments/lib-v1/storage/flutter/path-provider.mdx"; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; +import 'package:aws_common/vm.dart'; - +Future uploadIOFile(io.File file) async { + final awsFile = AWSFilePlatform.fromFile(file); + try { + final uploadResult = await Amplify.Storage.uploadFile( + localFile: awsFile, + key: 'upload/file.png', + ).result; + safePrint('Uploaded file: ${uploadResult.uploadedItem.key}'); + } on StorageException catch (e) { + safePrint('Error uploading file: ${e.message}'); + rethrow; + } +} +``` + + ```dart -import 'dart:io'; - -import 'package:path_provider/path_provider.dart'; +import 'dart:html' as html; -Future createAndUploadFile() async { - // Create a dummy file - const exampleString = 'Example file contents'; - final tempDir = await getTemporaryDirectory(); +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; +import 'package:aws_common/web.dart'; - final exampleFile = File('${tempDir.path}/example.txt'); - await exampleFile.create(); - await exampleFile.writeAsString(exampleString); - - // Upload the file to S3 +Future uploadHtmlFile(html.File file) async { + final awsFile = AWSFilePlatform.fromFile(file); try { - final result = await Amplify.Storage.uploadFile( - local: exampleFile, - key: 'ExampleKey', - onProgress: (progress) { - safePrint('Fraction completed: ${progress.getFractionCompleted()}'); - }, - ); - safePrint('Successfully uploaded file: ${result.key}'); + final uploadResult = await Amplify.Storage.uploadFile( + localFile: awsFile, + key: 'upload/file.png', + ).result; + safePrint('Uploaded file: ${uploadResult.uploadedItem.key}'); } on StorageException catch (e) { - safePrint('Error uploading file: $e'); - } finally { - await exampleFile.delete(); + safePrint('Error uploading file: ${e.message}'); + rethrow; } } ``` + + + diff --git a/src/fragments/lib-v1/storage/native_common/configureaccess/common.mdx b/src/fragments/lib-v1/storage/native_common/configureaccess/common.mdx index b85ced3b646..a4640ee03b6 100644 --- a/src/fragments/lib-v1/storage/native_common/configureaccess/common.mdx +++ b/src/fragments/lib-v1/storage/native_common/configureaccess/common.mdx @@ -85,3 +85,114 @@ import ios10 from '/src/fragments/lib-v1/storage/ios/configureaccess/50_customiz import android11 from '/src/fragments/lib-v1/storage/android/configureaccess/50_customization.mdx'; + + + +## Customization + +### Customize Object Key Path + +You can customize your key path by defining a prefix resolver: + +```dart +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +// Define your own prefix resolver, which implements the `S3PrefixResolver`. +class MyPrefixResolver implements S3PrefixResolver { + const MyPrefixResolver(); + + @override + Future resolvePrefix({ + required StorageAccessLevel accessLevel, + String? identityId, + }) async { + if (accessLevel == StorageAccessLevel.guest) { + return 'myPublicPrefix/'; + } + + final String accessLevelPrefix; + + if (accessLevel == StorageAccessLevel.protected) { + accessLevelPrefix = 'myProtectedPrefix/'; + } else { + accessLevelPrefix = 'myPrivatePrefix/'; + } + + final targetIdentityId = identityId ?? await getCurrentUserIdentityId(); + + return '$accessLevelPrefix$targetIdentityId/'; + } + + Future getCurrentUserIdentityId() async { + final authPlugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); + final authSession = await authPlugin.fetchAuthSession(); + return authSession.identityIdResult.value; + } +} +``` + +Then, configure the Storage plugin with your custom prefix resolver: + +```dart +final storagePlugin = AmplifyStorageS3( + prefixResolver: const MyPrefixResolver(), +); +... +await Amplify.addPlugin(storagePlugin); +``` + +Add the IAM policy that corresponds with the prefixes defined above to enable read, write and delete operation for all the objects under path _myPublicPrefix/_: + +```json +{ + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + "Resource": ["arn:aws:s3:::your-s3-bucket/myPublicPrefix/*"] + } + ] +} +``` + +If you want to have custom _private_ path prefix like `myPrivatePrefix/`, you need to add it into your IAM policy: + +```json +{ + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::your-s3-bucket/myPrivatePrefix/${cognito-identity.amazonaws.com:sub}/*" + ] + } + ] +} +``` + +## Passthrough PrefixResolver + +If you would like no prefix resolution logic, such as performing S3 requests at the root of the bucket, you can use the `PassThroughPrefixResolver` provided by the `amplify_storage_s3` package. + +```dart +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +final storagePlugin = AmplifyStorageS3( + prefixResolver: const PassThroughPrefixResolver(), +); + +await Amplify.addPlugin(storagePlugin); +``` + + diff --git a/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx b/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx index 028c4148e39..1cbac772919 100644 --- a/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx @@ -1,9 +1,5 @@ The Amplify Storage category provides an interface for managing user content for your app in public, protected, or private storage buckets. The Storage category comes with default built-in support for Amazon Simple Storage Service (S3). The Amplify CLI helps you to create and configure the storage buckets for your app. The Amplify AWS S3 Storage plugin leverages [Amazon S3](https://aws.amazon.com/s3). -import flutter0 from '/src/fragments/lib-v1/storage/flutter/getting-started/50_developerPreview.mdx'; - - - ## Goal To setup and configure your application with Amplify Storage and go through a simple upload file example @@ -103,9 +99,11 @@ import flutter11 from '/src/fragments/lib-v1/storage/flutter/getting-started/30_ + ## Uploading data to your bucket To upload to S3 from a data object, specify the key and the data object to be uploaded. + import ios12 from '/src/fragments/lib-v1/storage/ios/getting-started/40_upload.mdx'; @@ -115,10 +113,7 @@ import android13 from '/src/fragments/lib-v1/storage/android/getting-started/40_ -import flutter14 from '/src/fragments/lib-v1/storage/flutter/getting-started/40_upload.mdx'; - - - + Upon successfully executing this code, you should see a new folder in your bucket, called `public`. It should contain a file called `ExampleKey`, whose contents is `Example file contents`. ## Next Steps @@ -131,6 +126,7 @@ Congratulations! You've uploaded a file to an s3 bucket. Check out the following - [Remove Files](/gen1/[platform]/prev/build-a-backend/storage/remove/) - [File Access Levels](/gen1/[platform]/prev/build-a-backend/storage/configure-access/) - [Using Lambda Triggers](/gen1/[platform]/prev/build-a-backend/storage/lambda-triggers/) + - [Escape Hatch](/gen1/[platform]/prev/build-a-backend/storage/sdk/) diff --git a/src/fragments/lib/storage/flutter/copy.mdx b/src/fragments/lib/storage/flutter/copy.mdx index d82444d1768..df196cb69e9 100644 --- a/src/fragments/lib/storage/flutter/copy.mdx +++ b/src/fragments/lib/storage/flutter/copy.mdx @@ -1,15 +1,28 @@ You can copy an existing file to a different location in your S3 bucket. User who initiates a copy operation should have read permission on the copy source file. ```dart -Future copy() async { +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future copyGuestFileToPrivate({ + required String sourceKey, + required String destinationKey, +}) async { try { final result = await Amplify.Storage.copy( - source: const StoragePath.fromString('public/file-1.txt'), - destination: const StoragePath.fromString('public/file-2.txt'), + source: StorageItemWithAccessLevel( + storageItem: StorageItem(key: sourceKey), + accessLevel: StorageAccessLevel.guest, + ), + destination: StorageItemWithAccessLevel( + storageItem: StorageItem(key: destinationKey), + accessLevel: StorageAccessLevel.private, + ), ).result; - safePrint('Copied file: ${result.copiedItem.path}'); + + safePrint('Copied file: ${result.copiedItem.key}'); } on StorageException catch (e) { - safePrint(e.message); + safePrint('Error copying file: ${e.message}'); + rethrow; } } ``` diff --git a/src/fragments/lib/storage/flutter/get-properties.mdx b/src/fragments/lib/storage/flutter/get-properties.mdx index 97169de98cc..a2d601a3778 100644 --- a/src/fragments/lib/storage/flutter/get-properties.mdx +++ b/src/fragments/lib/storage/flutter/get-properties.mdx @@ -1,14 +1,18 @@ You can get file properties and metadata without downloading the file using `Amplify.Storage.getProperties`. ```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + Future getFileProperties() async { try { final result = await Amplify.Storage.getProperties( - path: const StoragePath.fromString('example.txt'), + key: 'example.txt', ).result; + safePrint('File size: ${result.storageItem.size}'); } on StorageException catch (e) { - safePrint(e.message); + safePrint('Could not retrieve properties: ${e.message}'); + rethrow; } } ``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx new file mode 100644 index 00000000000..0b4019f0afd --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx @@ -0,0 +1,141 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Build options', + description: "Use build options for the function category in Amplify to execute a script before a function is deployed, for example, to transpile Typescript or ES6 with Babel into a format that is supported by the AWS Lambda's node runtime.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/build-options/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + + + + +In some cases, it might be necessary to execute a script before a function is deployed, e.g. to transpile Typescript or ES6 with Babel or with `tsc` into a format that is supported by the AWS Lambda's node runtime. `amplify push` will look for a `script` definition in the project root's `package.json` with the name `amplify:` and run it right after `npm install` is called in the function resource's `src` directory. + +**Example: Transpiling Typescript code with TSC** + +Make sure you have the `tsc` command installed globally by running `npm install -g typescript` or locally by running `npm install --save-dev typescript` + +Let's say, a function resource has been created with `amplify function add` and it is called `generateReport`. The ES6 source code for this function is located in `amplify/backend/function/generateReport/lib` and the resource's `src` directory only contains the auto-generated `package.json` for this function. In order to compile TypeScript, you have to add the following script definition to your project root's `package.json`: + +```json +{ + "scripts": { + "amplify:generateReport": "cd amplify/backend/function/generateReport && tsc -p ./tsconfig.json && cd -" + }, +} +``` + +Navigate into `amplify/backend/function/generateReport` and create `tsconfig.json` then add the following to it: + +```json +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "lib": ["dom", "esnext"], + "module": "commonjs", + "moduleResolution": "node", + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./src", + "baseUrl": "./", + "rootDir": "./lib", + "paths": { + "src": ["./lib"] + } + } +} +``` + +**Note:** It is important to note that if you are using `aws-sdk` in your TypeScript file, you will get a timeout if you attempt to import it with the following: + +```js +import AWS from 'aws-sdk'; +``` + +Change to this: + +```js +import * as AWS from 'aws-sdk'; +``` + +Once you run `amplify push`, the `amplify:generateReport` script will be executed, either by `yarn` or by `npm` depending on the existence of a `yarn.lock` file in the project root directory. + +**Example: Transpiling ES6 code with Babel** + +Let's say, a function resource has been created with `amplify function add` and it is called `generateReport`. The ES6 source code for this function is located in `amplify/backend/function/generateReport/lib` and the resource's `src` directory only contains the auto-generated `package.json` for this function. In order to run Babel, you have to add the following script definition and dev dependencies to your project root's `package.json`: + +```json +{ + "scripts": { + "amplify:generateReport": "cd amplify/backend/function/generateReport && babel lib -d src && cd -" + }, + "devDependencies": { + "@babel/cli": "^7.5.5", + "@babel/preset-env": "^7.5.5", + } +} +``` + +Babel needs to be configured properly so that the transpiled code can be run on AWS Lambda. This can be done by adding a `.babelrc` file to the resource folder (`amplify/backend/function/generateReport/.babelrc` in this case): + +```json +{ + "presets": [ + [ + "env", + { + "exclude": ["transform-regenerator"], + "targets": { + "node": "10.18" + } + } + ] + ], + "plugins": [ + "transform-async-to-generator", + "transform-exponentiation-operator", + "transform-object-rest-spread" + ] +} +``` + +Once you run `amplify push`, the `amplify:generateReport` script will be executed, either by `yarn` or by `npm` depending on the existence of a `yarn.lock` file in the project root directory. + + + + + +There are no existing build options for Python functions. The process of building and packaging Python functions is in line with Amazon's [existing documentation](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-venv) for manually creating a Lambda deployment package which depends on a virtual environment. + +Amplify will run `pipenv install` in your function's source directory during builds using either Pipenv's default virtual environment, or whichever virtual environment happens to be active. Then, during the packaging stage, the contents of the `site-packages` directory for that virtual environment will be zipped up along with the function-specific files. + +The contents of the Python build can include local development dependencies (e.g. for testing) in addition to those necessary for your function to run. Packages installed as "editable" (using the `-e` flag) will not be packaged, as they are represented as an `.egg-link` file pointing to the local, editable code of the dependency. + + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx new file mode 100644 index 00000000000..8d6b61eab10 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx @@ -0,0 +1,114 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure Lambda function settings', + description: 'Learn how to configure custom settings for your Lambda function', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/configure-options/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +You may want to override the Amplify CLI default configurations for your Lambda function or configure changes not available within the `amplify add function` workflow. + +*Example*: When creating a `Node.js` function, the CLI will automatically configure a runtime version, a default memory size, and more. There are a few things you may want to override or configure: + +1. Runtime +2. Memory size +3. Environment variables + +Let's look at how to update all of these things. + +## Updating the Runtime + +You may want to tweak the runtime version to be either a newer or older version than the Amplify-generated default. + +Let's say we've deployed a Lambda function using a Node.js runtime and you want to modify the version of the runtime to be `14.x`. + +To do so, open __amplify/backend/function/function-name/function-name-cloudformation-template.json__ and set the `Runtime` property in the `LambdaFunction` resource to: + +```json +"Resources": { + "LambdaFunction": { + ... + "Properties": { + "Runtime": "nodejs14.x", // Runtime now set to 14.x + "Layers": [], + } + ... + } + }, +} +``` + +Next, deploy the updates using the Amplify CLI: + +```sh +amplify push +``` + +## Updating the default memory size + +When you deploy a function with Amplify, the default memory size will be set to a low setting (128MB). Often you will want to increase the default memory size in order to improve performance. A popular memory setting in Lambda is 1024MB as it speeds the function noticeably while usually keeping the cost the same or close to it. + +To update the memory size, open __amplify/backend/function/function-name/function-name-cloudformation-template.json__ and set the `MemorySize` property in the `LambdaFunction` resource: + +```json +"Resources": { + "LambdaFunction": { + ... + "Properties": { + "Runtime": "nodejs14.x", + "MemorySize": 1024, // Memory size now set to 1024 MB + "Layers": [], + } + ... + } + }, +} +``` + +Next, deploy the updates using the Amplify CLI: + +```sh +amplify push +``` + +_To learn more about optimizing resources allocation for Lambda functions, check out [this](https://dev.to/aws/deep-dive-finding-the-optimal-resources-allocation-for-your-lambda-functions-35a6) blog post._ + +## Setting an environment variable + +A very common scenario is the need to set and use an environment variable in your Lambda function. + +There are generally two types of environment variables: +- [Secret values (example: access keys, API keys etc.)](/[platform]/build-a-backend/functions/secrets/) +- [Non-secret values (example: endpoint information, locale information etc.)](/[platform]/build-a-backend/functions/environment-variables/) + + + +To view all configuration options available in AWS Lambda, check out the documentation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-environment.html) + +To learn more about extending the Amplify CLI with custom resources, check out the documentation [here](/[platform]/tools/cli/custom/cdk/) + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx new file mode 100644 index 00000000000..dea2617a370 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx @@ -0,0 +1,86 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Environment variables', + description: 'Configure environment variables for AWS Lambda functions', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/environment-variables/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +Amplify CLI allows you to configure environment variables for your Lambda functions. Each Amplify environment can have a different environment variable value. This enables use cases such as switching between dev and prod URLs depending on the environment. + +> Environment variables should NOT be used for storing sensitive configuration values such as database passwords, API keys, or access tokens. Use [function secrets configuration](/[platform]/build-a-backend/functions/secrets/) instead! + +## Configuring environment variables +To configure a new function with environment variables, run `amplify add function`, select `yes` to the advanced settings prompt and select `yes` to the environment variables configuration prompt. From there, you will be able to specify a key and value for the environment variable. + +```console +$ amplify add function +... +? Do you want to configure advanced settings? Yes +... +? Do you want to configure environment variables for this function? Yes +? Enter the environment variable name: API_URL +? Enter the environment variable value: https://example.com/test +? Select what you want to do with environment variables: (Use arrow keys) + Add new environment variable + Update existing environment variables + Remove existing environment variables +> I'm done +``` + +To configure environment variables for an existing function, run `amplify update function`, and select `Environment variables configuration`. You can then add, update, or remove environment variables. + +```console +$ amplify update function +... +? Which setting do you want to update? + Resource access permissions + Scheduled recurring invocation + Lambda layers configuration +> Environment variables configuration + Secret values configuration +? Select what you want to do with environment variables: +> Add new environment variable + Update existing environment variables + Remove existing environment variables + I'm done +``` + +## Multi-environment flows +When creating a new Amplify environment using `amplify env add`, Amplify CLI asks if you want to apply all environment variable values to the new environment or modify them. If you choose to apply the existing values, you can still make edits anytime by running `amplify update function`. + +When creating a new Amplify environment using `amplify env add --yes`, Amplify CLI will apply all environment variable values from the current environment to the new environment. + +In multi-environment workflows, you may have added a new environment variable in one Amplify environment and then checked out a different Amplify environment. In this case, on the next `amplify push`, Amplify CLI will detect that there is a new environment variable that does not have a value specified in the current environment and prompt for one. +Running `amplify push --yes` in this case will fail with a message explaining the missing environment variable values. + +In [**git-based** multi-environment workflows](/[platform]/tools/cli/teams/), you may run into errors during deployment. For example, this happens when you add an environment variable in `envA` (corresponding to a git branch `branchA`), then `amplify checkout envB` and `git checkout branchB` and `git merge` branchA into branchB. Upon pushing `envB`, Amplify CLI detects that a new environment variable has been added but can't infer a value for it. To resolve this issue, run the following commands in the terminal: + +1. `amplify env checkout ` +2. `amplify push` - when prompted, enter a new value for the environment variable(s) +3. `git commit` +4. `git push` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx new file mode 100644 index 00000000000..51cbb4bed84 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx @@ -0,0 +1,504 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Call a GraphQL API from a Lambda function', + description: 'Interact with a GraphQL API from a Lambda function.', + platforms: [ + 'flutter', + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +You can call an AppSync GraphQL API from a Node.js app or a Lambda function. Take a basic `Todo` app as an example: + +```graphql +type Todo @model @auth(rules: [{ allow: public }]) { + name: String + description: String +} +``` + +This API will have operations available for `Query`, `Mutation`, and `Subscription`. Let's take a look at how to perform both a **query** as well as a **mutation** from a Lambda function using Node.js. + +## Utilizing Lambda function template (IAM authorization) + +First, create a Lambda function with `amplify add function` and choose the `AppSync - GraphQL API request (with IAM)` to get started. Be sure to grant access to your GraphQL API when prompted by the CLI to grant access to other resources in the project. Alternatively, you can create the [function from scratch](#create-from-scratch). + +```console +amplify add function +? Select which capability you want to add: Lambda function (serverless function) +? Provide an AWS Lambda function name: myfunction +? Choose the runtime that you want to use: NodeJS +? Choose the function template that you want to use: AppSync - GraphQL API request (with IAM) + +Available advanced settings: +- Resource access permissions +- Scheduled recurring invocation +- Lambda layers configuration +- Environment variables configuration +- Secret values configuration + +? Do you want to configure advanced settings? Yes +? Do you want to access other resources in this project from your Lambda function? Yes +? Select the categories you want this function to have access to. api +? Select the operations you want to permit on Query, Mutation, Subscription + +You can access the following resource attributes as environment variables from your Lambda function + API__GRAPHQLAPIENDPOINTOUTPUT + API__GRAPHQLAPIIDOUTPUT + API__GRAPHQLAPIKEYOUTPUT + ENV + REGION +``` + + + +The function can only be added when the GraphQL API with IAM authorization exists. + + + +### Create from scratch + +```console +amplify add function +? Select which capability you want to add: Lambda function (serverless function) +? Provide an AWS Lambda function name: myfunction +? Choose the runtime that you want to use: NodeJS +? Choose the function template that you want to use: Hello World + +Available advanced settings: +- Resource access permissions +- Scheduled recurring invocation +- Lambda layers configuration +- Environment variables configuration +- Secret values configuration + +? Do you want to configure advanced settings? Yes +? Do you want to access other resources in this project from your Lambda function? Yes +? Select the categories you want this function to have access to. api +? Select the operations you want to permit on Query, Mutation, Subscription + +You can access the following resource attributes as environment variables from your Lambda function + API__GRAPHQLAPIENDPOINTOUTPUT + API__GRAPHQLAPIIDOUTPUT + API__GRAPHQLAPIKEYOUTPUT + ENV + REGION +``` + +The examples on this page use [`node-fetch`](https://www.npmjs.com/package/node-fetch) to make a HTTP request to your GraphQL API. When the Node.js v18 runtime is released for Lambda this dependency can be removed in favor of [native `fetch`](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#fetch) To get started, add the `node-fetch` module as a dependency: + +**CommonJS**: + +For functions written using CommonJS, you will need to install version 2 of `node-fetch` + +```diff +{ + "name": "myfunction", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "node-fetch": "2" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +**ESM**: + +```diff +{ + "name": "myfunction", ++ "type": "module", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "node-fetch": "^3.2.3" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +## Query + +Using an API Key for authenticating your requests, you can query the GraphQL API to get a list of all `Todo`s. To paginate over the list queries, you need to pass in a `limit` and `nextToken` on the `listTodos` query. See more at [GraphQL pagination ](/[platform]/build-a-backend/graphqlapi/query-data/). + +{/* prettier-ignore */} +```js +import { default as fetch, Request } from 'node-fetch'; + +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; + +const query = /* GraphQL */ ` + query LIST_TODOS { + listTodos { + items { + id + name + description + } + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + /** @type {import('node-fetch').RequestInit} */ + const options = { + method: 'POST', + headers: { + 'x-api-key': GRAPHQL_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ query }) + }; + + const request = new Request(GRAPHQL_ENDPOINT, options); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 400; + body = { + errors: [ + { + status: response.status, + message: error.message, + stack: error.stack + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` + +## Mutation + +In this example you will create a mutation showing how to pass in variables as arguments to create a `Todo` record. + +{/* prettier-ignore */} +```js +import { default as fetch, Request } from 'node-fetch'; + +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; + +const query = /* GraphQL */ ` + mutation CREATE_TODO($input: CreateTodoInput!) { + createTodo(input: $input) { + id + name + createdAt + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + const variables = { + input: { + name: 'Hello, Todo!' + } + }; + + /** @type {import('node-fetch').RequestInit} */ + const options = { + method: 'POST', + headers: { + 'x-api-key': GRAPHQL_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ query, variables }) + }; + + const request = new Request(GRAPHQL_ENDPOINT, options); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 400; + body = { + errors: [ + { + status: response.status, + message: error.message, + stack: error.stack + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` + +## IAM Authorization + +Let's take a look at another example schema that uses `iam` authorization. + +```graphql +type Todo @model @auth(rules: [{ allow: private, provider: iam }]) { + name: String + description: String +} +``` + +The CLI will automatically configure the Lambda execution IAM role to call the GraphQL API. Before writing your Lambda function you will first need to install the appropriate AWS SDK v3 dependencies: + +```diff +{ + "name": "myfunction", ++ "type": "module", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-crypto/sha256-js": "^2.0.1", ++ "@aws-sdk/credential-provider-node": "^3.76.0", ++ "@aws-sdk/protocol-http": "^3.58.0", ++ "@aws-sdk/signature-v4": "^3.58.0", ++ "node-fetch": "^3.2.3" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +Then, the following example will sign the request to call the GraphQL API using IAM authorization. + +{/* due to placeholder text */} + +{/* prettier-ignore */} +```js +import crypto from '@aws-crypto/sha256-js'; +import { defaultProvider } from '@aws-sdk/credential-provider-node'; +import { SignatureV4 } from '@aws-sdk/signature-v4'; +import { HttpRequest } from '@aws-sdk/protocol-http'; +import { default as fetch, Request } from 'node-fetch'; + +const { Sha256 } = crypto; +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; + +const query = /* GraphQL */ ` + query LIST_TODOS { + listTodos { + items { + id + name + description + } + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + const endpoint = new URL(GRAPHQL_ENDPOINT); + + const signer = new SignatureV4({ + credentials: defaultProvider(), + region: AWS_REGION, + service: 'appsync', + sha256: Sha256 + }); + + const requestToBeSigned = new HttpRequest({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + host: endpoint.host + }, + hostname: endpoint.host, + body: JSON.stringify({ query }), + path: endpoint.pathname + }); + + const signed = await signer.sign(requestToBeSigned); + const request = new Request(GRAPHQL_ENDPOINT, signed); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 500; + body = { + errors: [ + { + message: error.message + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` + +### CommonJS + +When writing functions with CommonJS, you will need to install version 2 of `node-fetch`: + +```diff +{ + "name": "myfunction", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-crypto/sha256-js": "^2.0.1", ++ "@aws-sdk/credential-provider-node": "^3.76.0", ++ "@aws-sdk/protocol-http": "^3.58.0", ++ "@aws-sdk/signature-v4": "^3.58.0", ++ "node-fetch": "2" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +Similar to the example above you can now write your handler. The difference here is the use of `require()` rather than `import ... from` + +```js +const { Sha256 } = require('@aws-crypto/sha256-js'); +const { defaultProvider } = require('@aws-sdk/credential-provider-node'); +const { SignatureV4 } = require('@aws-sdk/signature-v4'); +const { HttpRequest } = require('@aws-sdk/protocol-http'); +const { default: fetch, Request } = require('node-fetch'); + +const GRAPHQL_ENDPOINT = + process.env.API_ < YOUR_API_NAME > _GRAPHQLAPIENDPOINTOUTPUT; +const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; + +const query = /* GraphQL */ ` + query LIST_TODOS { + listTodos { + items { + id + name + description + } + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +exports.handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + const endpoint = new URL(GRAPHQL_ENDPOINT); + + const signer = new SignatureV4({ + credentials: defaultProvider(), + region: AWS_REGION, + service: 'appsync', + sha256: Sha256 + }); + + const requestToBeSigned = new HttpRequest({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + host: endpoint.host + }, + hostname: endpoint.host, + body: JSON.stringify({ query }), + path: endpoint.pathname + }); + + const signed = await signer.sign(requestToBeSigned); + const request = new Request(GRAPHQL_ENDPOINT, signed); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 500; + body = { + errors: [ + { + message: error.message + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx new file mode 100644 index 00000000000..be5e840404b --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx @@ -0,0 +1,36 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; +import { getChildPageNodes } from '@/utils/getChildPageNodes'; + +export const meta = { + title: 'Functions', + description: 'Build and deploy serverless functions to perform various tasks, such as handling API requests, executing backend logic, or integrating with other AWS services.', + platforms: [ + 'flutter', + ], + route: '/[platform]/build-a-backend/functions', + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + const childPageNodes = getChildPageNodes(meta.route); + return { + props: { + platform: context.params.platform, + meta, + childPageNodes + } + }; +} + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx new file mode 100644 index 00000000000..6ebdbfaf42b --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx @@ -0,0 +1,229 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Reuse code and assets using layers', + description: "Use Amplify CLI's Lambda layer capability to reuse code & assets across functions.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/layers/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +Lambda layers allow you to pull common code & assets for your Lambda function into a centralized location. With Lambda layers you can: + +1. _*Re-use your code & assets*_: Your Lambda functions can leverage these layers to reuse shared code & assets across functions +2. _*Speed up function deployments*_: Iterating on your Lambda function will be significantly faster because it can be independently updated from the layer which usually contains the bulk of large static content + +> **Known limitation**: Functions using a layer can't be mocked locally using `amplify mock`. We recommend you to create a dev environment and test the functions inside the AWS Lambda console. + +![Lambda layer architecture diagram](/images/layers-architecture.gif) + +The general workflow breaks down into the following steps: + +1. Create a Lambda layer +2. Add shared code & assets to the layer +3. Add the Lambda layer to a function +4. Deploy the layer & function + +## Create a Lambda Layer + +To create a layer, run the following command within your Amplify project: + +```bash +amplify add function +``` + +Select `Lambda layer` as the capability to add + +```console +? Select which capability you want to add: +> Lambda layer (shared code & resource used across functions) +``` + +```console +? Provide a name for your Lambda layer: (layer-name) +? Choose the runtime that you want to use: (Use arrow keys) +❯ NodeJS + Python +``` + +Next, you'll be guided through a workflow to provide a **layer name**, and select a **supported runtime**. Currently Amplify CLI provides NodeJS or Python runtime support for layers. + +```console +? The current AWS account will always have access to this layer. + Optionally, configure who else can access this layer. (Hit to skip) +◯ Specific AWS accounts +◯ Specific AWS organization +◯ Public (everyone on AWS can use this layer) +``` + +After that, you'll be asked to configure your **layer's permission**. + +The **current AWS account will always have access to this layer**. +In addition, the customer can configure access for: + +- **Specific AWS accounts**: provide a comma-separated list of AWS Account IDs to provide access to them. +- **Specific AWS organizations**: provide a comma-separated list of AWS Organization IDs to provide access to them. *AWS Organization IDs start with `o-`.* +- **Public**: make this layer available for everyone AWS. Anyone in AWS can reference this layer using its ARN. + +```console +Next steps: +Move your libraries to the following folder: +[NodeJS]: amplify/backend/function//lib/nodejs + +Include any files you want to share across runtimes in this folder: +amplify/backend/function//opt + +"amplify function update " - configure a function with this Lambda layer +"amplify push" - builds all of your local backend resources and provisions them in the cloud +``` + +Your Lambda layer is ready to use after the permissions are set up. + +## Add shared code & assets + +Now that your layer is set up, you'll see a new folder with the `layer-name` added to `amplify/backend/function/`. The respective runtime's folder structure is autogenerated. + +### Add shared code + + + + + +A `nodejs` folder is auto-generated for you. In there you'll find an empty `package.json` file and a `node_modules` folder. If you want to offload other node_modules you can either: + +1. `cd` into the `nodejs` folder and add the dependencies into the `package.json` file, or +2. move all your existing function's `node_modules` content into the layer's `node_modules` folder + +Any dependency listed within the layer's `package.json` file will be installed and packaged during `amplify push`. + +Any node module that is in the layer's `node_modules` folder can be accessed from the function as if the node module is in the function's `node_modules` folder. + +*In order to take advantage of Lambda layer's for your NodeJS function, you don't even need to update your function's code!* + + + + + +A `python` folder is auto-generated for you. In there you'll find an empty `Pipfile` file. Any packages listed within the layer's `Pipfile` file will be installed and packaged during `amplify push`. You can `import` these packages from within your Python function just like any other package within your Python function. + + + + + +### Add shared assets + +Any assets like large images or other files that you want to share across various functions can be placed in the `amplify/backend/function//opt/` folder. Your function's code can import any assets by looking for files in the `/opt/` path. + +### Lambda layer versions + +Every time `amplify push` or `amplify update function` is run, Amplify CLI checks if a layer's content has changed and automatically creates a new *layer version*. Layer versions are immutable and functions always use a specific layer version. + +In order to speed up deployments when vast amount of node_modules exist, Amplify CLI scans only for changes within each module's `package.json` file. If you don't see Amplify CLI detect your latest changes, verify that at least of your node module's `package.json` content has changed. + +## Add a layer to a function + +You can either create a new function and add Lambda layers by running `amplify add function` or add layers to an existing function using `amplify update function`. Select `Lambda function` when prompted and you'll be presented the following question during the guided flow: + +```console +... +? Do you want to enable Lambda layers for this function? Yes +? Provide existing layers or select layers in this project to access from this function (pick up to 5): + ◯ Provide existing Lambda layer ARNs +❯◉ myamplifylayer1 + ◯ myamplifylayer2 +``` + +You can either add an existing layer in AWS by referencing its ARN or select a layer from your Amplify project that's listed below. + +```console +? Select a version for myamplifylayer1: +❯ Always choose latest version + 2: Updated layer version 2021-06-08T05:33:42.651Z + 1: Updated layer version 2021-06-08T05:30:43.101Z +``` + +When adding a layer from your Amplify project, you'll also be able to select a specific layer version or always choose the latest layer version. The largest layer version number represents the most recent changes. + +```console +? Modify the layer order: +(Layers with conflicting files will overwrite contents of layers earlier in the list): +- layer2 +- layer3 +- layer6 +- +- +``` + +Given that layers can have overlapping contents, the order of the layer matters. You can adjust the layer's order if needed in the next step. + +Now, you've successfully added a layer to your function. + +## Deploy Lambda layers & functions with Lambda layers + +Once you're ready with your changes in your layer and functions, you can deploy them by running `amplify push`. + +If a layer’s content has been updated and it has permissions associated, Amplify CLI will prompt you whether you want to carry the permissions forward to a newer version. + +```console +Content changes in Lambda layers detected. +Suggested configuration for new layer versions: + +myamplifylayer1 + - Description: Updated layer version 2021-06-08T05:33:42.651Z + +? Accept the suggested layer version configurations? (Y/n) +``` + +During `amplify push`, you get to modify the layer version description. By default, Amplify CLI will populate the description as `Updated layer version `. + +## Update layer content + +Any file changes within a layer's folder are automatically tracked by Amplify CLI. If there are changes available, the Amplify CLI will create a new layer version with the changes. + +## Update layer settings + +You can update layer's permissions by running `amplify update function` and selecting `Lambda layer`. + +Next, you'll be prompted to select the layer for which you want to update the settings for. + +#### Note: Update Layer Permissions from Public to Specific + - To update a lambda layer from Public access to Specific (Account/Organization) access, please remember to remove Public access by **un-selecting** the option in the 'amplify update' CLI flow before selecting a specific AWS account/organization. + - If you have already selected 'Public' access, just adding additional 'specific' AWS accounts/organizations will not have any effect on the Lambda Layer configuration. It will not automatically remove Public access. + +## Remove a layer + +To remove a Lambda layer, run the `amplify function remove` command and select `Lambda layers`. Next, you'll be prompted to select which layer to remove. You can delete specific layer versions or all of them. + +> Warning: When you delete a layer, you can no longer configure functions to use it. However, any function that already uses the layer continues to have access to it. + +```console +? Choose the resource you would want to remove (layer) +When you delete a layer version, you can no longer configure functions to use it. +However, any function that already uses the layer version continues to have access to it. +? Choose the Layer versions you want to remove. +❯◯ 1: Updated layer version 2021-06-08T05:30:43.101Z + ◯ 2: Updated layer version 2021-06-08T05:33:42.651Z +? Are you sure you want to delete the resource? This action deletes all files related to this resource from the backend directory. (Y/n) +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx new file mode 100644 index 00000000000..fcf0f3862cc --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx @@ -0,0 +1,105 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Access secret values', + description: 'Configure Lambda functions to securely access secret values', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/functions/secrets/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +Amplify CLI allows you to configure secret values that can be securely accessed from a Lambda function. Each Amplify environment can have a different secret value. This enables use cases such as different API keys for a dev and prod environment. Secrets should be used for values such as database passwords, API keys, and access tokens. + +> To access non-sensitive configuration values in a Lambda function, use [environment variables](/[platform]/build-a-backend/functions/environment-variables/). + +## Configuring secret values +To configure a new function with secret values, run `amplify add function`, select `yes` to the advanced settings prompt and select `yes` to the secrets configuration prompt. From there, you can specify the name and value of the secret. + +```console +$ amplify add function +... +? Do you want to configure advanced settings? Yes +... +? Do you want to configure secret values this function can access? Yes +? Enter a secret name (this is the key used to look up the secret value): API_KEY +? Enter the value for API_KEY: [hidden] +? What do you want to do? (Use arrow keys) + Add a secret + Update a secret + Remove secrets +> I'm done +``` + +To configure secrets for an existing function, run `amplify update function`, and select `Secret values configuration`. You can then add, update and remove secret values. + +```console +$ amplify update function +... +? Which setting do you want to update? + Resource access permissions + Scheduled recurring invocation + Lambda layers configuration + Environment variables configuration +> Secret values configuration +? What do you want to do? +> Add a secret + Update a secret + Remove secrets + I'm done +``` + +> Note: Amplify CLI never stores secrets locally. All secret values are immediately stored in [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) using the SecureString parameter type. + +## Accessing the values in your function +To access the secret values in your Lambda function, use the [AWS SSM GetParameter API](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html). Amplify CLI will automatically supply the SSM parameter name of the secret as an environment variable to the function. This value can be passed into the API call as the "Name" to retrieve the value. Ensure that the API call has "WithDecryption" specified as `true`. + +If your Lambda function is using the Node.js runtime, a comment block will be placed at the top of your `index.js` file with example code to retrieve the secret values. + +```js +const aws = require('aws-sdk'); + +const { Parameters } = await (new aws.SSM()) + .getParameters({ + Names: ["EXAMPLE_SECRET_1", "EXAMPLE_SECRET_2"].map(secretName => process.env[secretName]), + WithDecryption: true, + }) + .promise(); + +// Parameters will be of the form { Name: 'secretName', Value: 'secretValue', ... }[] +``` + +## Multi-environment flows +When creating a new Amplify environment using `amplify env add`, Amplify CLI asks if you want to apply all secret values to the new environment or modify them. If you choose to apply the existing values, you can still make edits anytime using `amplify update function`. + +When creating a new Amplify environment using `amplify env add --envName --yes`, Amplify CLI will apply all secret values from the current environment to the new environment. + +In multi-environment workflows, you may have added a new secret in one Amplify environment and then checked out a different Amplify environment. In this case, on the next `amplify push`. Amplify CLI will detect that there is a new secret that does not have a value specified in the current environment and prompt for one. Running `amplify push --yes` in this case will fail with a message explaining the missing secret values. + +In [**git-based** multi-environment workflows](/[platform]/tools/cli/teams/), you may run into errors during deployment. For example, this happens when you add a secret in `envA` (corresponding to a git branch `branchA`), then `amplify env checkout envB` and `git checkout branchB` and `git merge` branchA into branchB. Upon pushing `envB`, Amplify CLI detects that a new secret has been added but can't infer a value for it. To resolve this issue, run the following commands in the terminal: + +1. `amplify env checkout ` +2. `amplify push` - when prompted, enter a new value for the secret(s) +3. `git commit` +4. `git push` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx new file mode 100644 index 00000000000..98144a46542 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx @@ -0,0 +1,161 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Set up a function', + description: 'Use Amplify CLI to add powerful Lambda functions to your cloud-based mobile and web app with a simple guided workflow.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter' + ], + canonicalPath: '/javascript/build-a-backend/functions/set-up-function/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +## Set up a function + +You can add a Lambda function to your project which you can use alongside a REST API or as a datasource in your GraphQL API using the [`@function` directive](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#lambda-function-resolver). + +```bash +amplify add function +``` + +```console +? Select which capability you want to add: Lambda function (serverless function) +? Provide a friendly name for your resource to be used as a label for this category in the project: lambdafunction +? Provide the AWS Lambda function name: lambdafunction +? Choose the runtime that you want to use: NodeJS +? Choose the function template that you want to use: (Use arrow keys) +> Hello world function + CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB) + Serverless express function (Integration with Amazon API Gateway) +``` + +## Function templates + +- The `Hello World function` will create a basic hello world Lambda function +- The `CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB)` function will add a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) Lambda function template for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command) +- The `Serverless express function (Integration with Amazon API Gateway)` will add a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) Lambda function template with routing enabled for your REST API paths. + +You can update the Lambda execution role policies for your function to access other resources generated and maintained by the CLI using the CLI. + +```console +$ amplify update function +Please select the Lambda Function you would want to update: lambdafunction +? Which setting do you want to update? Resource access permissions +? Select the category (Press to select,
to toggle all, to invert selection) +> api + function + storage + auth +? Select the operations you want to permit on (Press to select, to toggle all, to invert selection) +> Query + Mutation + Subscription + +You can access the following resource attributes as environment variables from your Lambda function + API__GRAPHQLAPIENDPOINTOUTPUT + API__GRAPHQLAPIIDOUTPUT + API__GRAPHQLAPIKEYOUTPUT +``` + +Behind the scenes, the CLI automates populating of the resource identifiers for the selected resources as Lambda environment variables which you will see in your function code as well. This process additionally configures CRUD level IAM policies on the Lambda execution role to access these resources from the Lambda function. For instance, you might grant permissions to your Lambda function to read/write to a DynamoDB table in the Amplify project by using the above flow and the appropriate IAM policy would be set on that Lambda function's execution policy which is scoped to that table only. + +## Supported Lambda runtimes + +Amplify CLI enables you to create, test and deploy Lambda functions with the following runtimes: + +| Runtime | Default Version | Requirements | +| --- | --- | --- | +| NodeJS | 14.x | - Install [NodeJS](https://nodejs.org/en/) | +| Java | 11 | - Install [Java 11 JDK](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) and [Gradle 5+](https://docs.gradle.org/current/userguide/installation.html) | +| Go | 1.x | - Install [Go](https://golang.org/doc/install) | +| .NET Core | 3.1 | - Install [.NET Core SDK](https://docs.microsoft.com/en-us/dotnet/core/install/sdk) | +| Python | 3.8.x | - Install [python3](https://www.python.org/downloads/) and [pipenv](https://pypi.org/project/pipenv/)
- Ensure `python3` and `pipenv` commands are available in your `PATH` | + +In order to create and test Lambda functions locally, you need to have the runtime's requirements (table above) fulfilled. You'll be asked to `Choose the runtime you would like to use:` when running `amplify add function`. + +Once a runtime is selected, you can select a function template for the runtime to help bootstrap your Lambda function. + +## Access existing AWS resource from Lambda Function + +You can grant your Lambda function access to your existing resources. After running `amplify add function`, the CLI generates a `custom-policies.json` file under the folder `amplify/backend/function//`. The file is where you can specify the [resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html) and [actions](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html) that grant Lambda Function access to the specified AWS resources. + +### File Structure + +```json +[ + { + "Action": ["s3:CreateBucket"], + "Resource": ["arn:aws:s3:::*"] + } +] +``` + +**Action:** Specify the actions that are required to be granted to your AWS resource. Wild characters ‘\*’ is accepted. + +**Resource**: Specify resources that the AWS resource needs access. The resource accepts multiple Arns for a service and wild card character ‘\*’ is accepted. + +> Note: Specifying resource or action as ‘\*’ is not recommended as best practice. This gives the Amplify function resource Administrative privileges which should be avoided. + +If your Amplify resource requires access to multiple AWS services and resources, create another block to grant access to the additional services and resources. + +```json +[ + { + "Action": ["s3:CreateBucket"], + "Resource": ["arn:aws:s3:::*"] + }, + { + "Action": ["iam:GetPolicy"], + "Resource": ["arn:aws:iam:::policy/*"] + } +] +``` + +Optionally, the `Effect` field can be specified to use ‘Allow’ or ‘Deny’. If not specified the field defaults to ‘Allow’ + +```json +{ + "Action": ["s3:CreateBucket"], + "Resource": ["arn:aws:s3:::*"], + "Effect": "Allow" +} +``` + +### Multi-Environment Workflow + +To specify AWS ARN resources across environments, an optional `\${env}` parameter can be used for resources. The `\${env}` parameter in the AWS ARN resource will get populated with the current Amplify environment name at deployment. + +```json +"Resource": ["arn:aws:s3:::${env}my-bucket"] +``` + +### Next Step + +On running `amplify push` commands, the IAM policies specified in the `custom-policies.json` file will be appended to the existing IAM policy list tied to the Lambda Function's execution role. + +## Schedule recurring Lambda functions + +Amplify CLI allows you to schedule Lambda functions to be executed periodically (e.g every minute, hourly, daily, weekly, monthly or yearly). You can also formulate more complex schedules using AWS Cron Expressions such as: _“10:15 AM on the last Friday of every month”_. Review the [Schedule Expression for Rules documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions) for more details. + +To schedule your Lambda function, answer **Yes** to `Do you want to invoke this function on a recurring schedule?` in the `amplify add function` flow. Once you deploy a function, it'll create a CloudWatch Rule to periodically execute the selected Lambda function. + +For more information on files generated in the function resource folder, see [Function Category Files](/[platform]/tools/cli/reference/files/#function-category-files). diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx new file mode 100644 index 00000000000..9a7dea61c1d --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx @@ -0,0 +1,190 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Configure Storage', + description: 'Use Amplify CLI to create and manage cloud-connected file and data storage for your app.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/storage/configure-storage/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +Amplify CLI's `storage` category enables you to create and manage cloud-connected file & data storage. Use the `storage` category when you need to store: + +1. app content (images, audio, video etc.) in an public, protected or private storage bucket or +2. app data in a NoSQL database and access it with a REST API + Lambda + +## Setup a new storage resource + +You can setup a new storage resource by running the following command: + +```bash +amplify add storage +``` + +Amplify allows you to either setup a app content storage (images, audio, video etc.) backed by Amazon S3 or a NoSQL database backed by Amazon DynamoDB. + +### Adding S3 storage + +```console +? Please select from one of the below mentioned services: +> Content (Images, audio, video, etc.) + NoSQL Database +? Please provide a friendly name for your resource that will be used to label this category in the project: +> mystorage +? Please provide bucket name: +> mybucket +``` + +Follow the prompts to provide your content storage's resource name. + + + +The storage resource created by Amplify CLI has retention enabled which prevents accidental deletion or loss of data. Hence, running `amplify remove storage` will not delete the storage resource and will need to be manually deleted on the AWS console. + + + +### S3 Access permissions + +Next, configure the access permissions for your Amazon S3 bucket. If you haven't set up the `auth` category already, the Amplify CLI will guide you through a workflow to enable the auth category. + +```console +? Restrict access by? +> Auth/Guest Users + Individual Groups + Both + Learn more +``` + + + +**NOTE:** Run `amplify update storage` to change the access permissions for your Amazon S3 bucket + + + +#### Auth/Guest Users access + +Select `Auth/Guest Users`, to scope permissions based on an individual user's authentication status. On the next question you'll be able to select if only authenticated users can access resources, or authenticated and guest users: + +``` +? Who should have access: +❯ Auth users only + Auth and guest users +``` + +Then you'll be prompted to set the access scopes for your authenticated and (if selected prior) unauthenticated users. + +```console +? What kind of access do you want for Authenticated users? +> ◉ create/update + ◯ read + ◯ delete +? What kind of access do you want for Guest users? + ◯ create/update +> ◉ read + ◯ delete +``` + +Granting access to authenticated users will allow the specified CRUD operations on objects in the bucket starting with the prefix `/public/`, `/protected/{cognito:sub}/`, and `/private/{cognito:sub}/`. `{cognito:sub}` is the sub of the Cognito identity of the authenticated user. + +Granting access to guest users will allow the specified CRUD operations on objects in the bucket starting with the prefix `/public/`. + +#### Individual Group access + +Select `Individual Groups` to scope access permissions based on [Cognito User Groups](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) + +```console +? Select groups: + ◉ EMPLOYEE +> ◉ MANAGER +``` + +Then select the CRUD operations you want to permit for each selected Cognito user group + +```console +? What kind of access do you want for EMPLOYEE users? + ◯ create/update +> ◉ read + ◯ delete +? What kind of access do you want for MANAGER users? + ◉ create/update + ◯ read +> ◉ delete +``` + +> Note: CRUD operations selected here will apply to ALL objects in the bucket, not just objects under a particular prefix. + +> Note: If you combine `Auth/Guest user access` and `Individual Group access`, users who are members of a group will only be granted the permissions of the group, and not the authenticated user permissions. + +### S3 Lambda trigger + +Lastly, you have the option of configuring a Lambda function that can execute in response to S3 events. + +```console +? Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) +``` + +Learn more about this workflow [here](/[platform]/tools/cli/usage/lambda-triggers/#s3-lambda-triggers). + +That's it! Your content storage is set up! Head to the [library's storage docs](/[platform]/build-a-backend/storage/set-up-storage/) to integrate this newly created S3 bucket into your app. + +### Adding a NoSQL database + +```console +? Please select from one of the below mentioned services: +> Content (Images, audio, video, etc.) + NoSQL Database +? Please provide a friendly name for your resource that will be used to label this category in the project: +> dynamo2e1dc4eb +? Please provide table name: +> dynamo2e1dc4eb +``` + +Follow the prompts to provide your NoSQL Database's resource name. Next, you'll go through a table-creation wizard. First, you'll create the columns of your table: + +```console +You can now add columns to the table. + +? What would you like to name this column: id +? Please choose the data type: string +? Would you like to add another column? Yes +``` + +Then, you'll need to specify your indexes. The concept behind "indexes", "partition key", "sort key" and "global secondary indexes" are explained in-depth [here](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey). + +```console +? Please choose partition key for the table: id +? Do you want to add a sort key to your table? (y/N) +``` + +```console +? Do you want to add a Lambda Trigger for your Table? (y/N) +``` + +If you want to configure a Lambda trigger for your Table, you'll have the option. Learn more about this workflow [here](/[platform]/tools/cli/usage/lambda-triggers/#dynamodb-lambda-triggers). + +That's it! Your NoSQL Database is set up! diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx new file mode 100644 index 00000000000..db4406cb2df --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx @@ -0,0 +1,260 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Import an S3 bucket or DynamoDB table', + description: 'Learn how you can import existing S3 bucket or DynamoDB table resources as a storage resource for other Amplify categories (API, Function, and more) using the Amplify CLI.', + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/storage/import/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +Import an existing S3 bucket or DynamoDB tables into your Amplify project. Get started by running `amplify import storage` command to search for & import an S3 or DynamoDB resource from your account. + +```bash +amplify import storage +``` + +Make sure to run `amplify push` to complete the import process and deploy this backend change to the cloud. + +The `amplify import storage` command will: + +- automatically populate your Amplify Library configuration files (aws-exports.js, amplifyconfiguration.json) with your chosen S3 bucket information +- provide your designated S3 bucket or DynamoDB table as a storage mechanism for all storage-dependent categories (API, Function, Predictions, and more) +- enable Lambda functions to access the chosen S3 or DynamoDB resource if you permit it + +This feature is particularly useful if you're trying to: + +- enable Amplify categories (such as API and Function) to access your existing storage resources; +- incrementally adopt Amplify for your application stack; +- independently manage S3 and DynamoDB resources while working with Amplify. + +> Note: Amplify does not manage the lifecycle of an imported resource. + +## Import an existing S3 bucket + +Select the "S3 bucket - Content (Images, audio, video, etc.)" option when you've run `amplify import storage`. + +Run `amplify push` to complete the import procedure. + +> Amplify projects are limited to exactly one S3 bucket. + +### Connect to an imported S3 bucket with Amplify Libraries + +By default, Amplify Libraries assumes that S3 buckets are configured with the following access patterns: + +- `public/` - Accessible by all users of your app +- `protected/{user_identity_id}/` - Readable by all users, but writable only by the creating user +- `private/{user_identity_id}/` - Only accessible for the individual user + +You can either configure your IAM role to use the Amplify-recommended policies or in your Amplify libraries configuration [overwrite the default storage path behavior](/gen1/[platform]/build-a-backend/storage/configure-access/#customize-object-key-path). + +It is highly recommended to review your S3 bucket's CORS settings. Review the [recommendation guide here](/[platform]/build-a-backend/storage/set-up-storage/#amazon-s3-bucket-cors-policy-setup). + +### Configuring IAM role to use Amplify-recommended policies + +If you're using an imported S3 bucket with an imported Cognito resource, then you'll need to update the policy of your Cognito Identity Pool's authenticated and unauthenticated role. Create new __managed policies__ (not *inline policies*) for these roles with the following statements: + +> Make sure to replace `{YOUR_S3_BUCKET_NAME}` with your S3 bucket's name. + +#### Unauthenticated role policies + +- IAM policy statement for `public/`: + +```json +{ + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/public/*" + ], + "Effect": "Allow" +} +``` + +- IAM policy statement for read access to `public/`, `protected/`, and `private/`: + +```json +{ + "Action": [ + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/*" + ], + "Effect": "Allow" +}, +{ + "Condition": { + "StringLike": { + "s3:prefix": [ + "public/", + "public/*", + "protected/", + "protected/*" + ] + } + }, + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}" + ], + "Effect": "Allow" +} +``` + +#### Authenticated role policies + +- IAM policy statement for `public/`: + +```json +{ + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/public/*" + ], + "Effect": "Allow" +} +``` + +- IAM policy statement for `protected/`: + +```json +{ + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/${cognito-identity.amazonaws.com:sub}/*" + ], + "Effect": "Allow" +} +``` + +- IAM policy statement for `private/`: + +```json +{ + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/private/${cognito-identity.amazonaws.com:sub}/*" + ], + "Effect": "Allow" +} +``` + +- IAM policy statement for read access to `public/`, `protected/`, and `private/`: + +```json +{ + "Action": [ + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/*" + ], + "Effect": "Allow" +}, +{ + "Condition": { + "StringLike": { + "s3:prefix": [ + "public/", + "public/*", + "protected/", + "protected/*", + "private/${cognito-identity.amazonaws.com:sub}/", + "private/${cognito-identity.amazonaws.com:sub}/*" + ] + } + }, + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}" + ], + "Effect": "Allow" +} +``` + +## Import an existing DynamoDB table + +Select the "DynamoDB table - NoSQL Database" option when you've run `amplify import storage`. In order to successfully import your DynamoDB table, your DynamoDB table needs to be located within the same region as your Amplify project. + +Run `amplify push` to complete the import procedure. + +> Amplify projects can contain multiple DynamoDB tables. + +## Multi-environment support + +When you create a new environment through `amplify env add`, Amplify CLI will assume by default that you're managing your app's storage resources outside of an Amplify project. You'll be asked to either import a different S3 bucket or DynamoDB tables or maintain the same imported storage resource. + +If you want to have Amplify manage your storage resources in a new environment, run `amplify remove storage` to unlink the imported storage resources and `amplify add storage` to create new Amplify-managed S3 buckets and DynamoDB tables in the new environment. + +## Unlink an existing S3 bucket or DynamoDB table + +In order to unlink your existing Storage resource run `amplify remove storage`. This will only unlink the S3 bucket or DynamoDB table referenced from the Amplify project. It will not delete the S3 bucket or DynamoDB table itself. + +Run `amplify push` to complete the unlink procedure. + +## Configure environment variables for Amplify Hosting builds + +In order to successfully build your application with Amplify Hosting add the following environment variables to your build environment: + +|Environment Variable|Description|Imported Resource|Required +|-|-|-|-| +|AMPLIFY_STORAGE_BUCKET_NAME|The name of the S3 bucket being imported for storage|S3 bucket|Yes +|AMPLIFY_STORAGE_REGION|The AWS region in which the S3 bucket or the DynamoDB table is located (for example: us-east-1, us-west-2, etc.)|S3 bucket or DynamoDB table|Yes +|AMPLIFY_STORAGE_TABLES|The name of the storage resource and DynamoDB table being imported for storage|DynamoDB table|Yes + + +The value of the AMPLIFY_STORAGE_TABLES environment variable needs to be in a json format such as: + +``` +{ + "STORAGE_RESOURCE_NAME_1":"DDB_TABLE_NAME_1", + "STORAGE_RESOURCE_NAME_2":"DDB_TABLE_NAME_2" // If you are importing more than a single DynamoDB table +} +``` +The values for the `STORAGE_RESOURCE_NAME` and `DDB_TABLE_NAME` fields can be retrieved from the amplify/team-provider-info.json file. + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx new file mode 100644 index 00000000000..294302bab3a --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx @@ -0,0 +1,114 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Modify Amplify-generated resources', + description: "The 'amplify override storage' command generates a developer-configurable 'overrides' TypeScript file which provides Amplify-generated S3 and DynamoDB resources as CDK constructs. For example, developers can run the 'amplify override storage' command to enable Transfer Acceleration for Amplify-generated S3 buckets.", + platforms: [ + 'flutter', + ], + canonicalObjects: [ + { + platforms: [ + 'flutter', + ], + canonicalPath: '/javascript/build-a-backend/storage/modify-amplify-generated-resources/' + } + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +Amplify Storage uses underlying AWS services and resources such as S3 and DynamoDB. You can customize these underlying resources for your specific use case. + +```bash +amplify override storage +``` + +Run the command above to override Amplify-generated storage resources including the S3 bucket, DynamoDB tables, and more. + +The command creates a new `overrides.ts` file under `amplify/backend/storage//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). + +## Customize Amplify-generated S3 resources + +Apply all the overrides in the `override(...)` function. For example to enable versioning on your S3 bucket: + +```ts +import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyS3ResourceTemplate) { + resources.s3Bucket.versioningConfiguration = { + status: 'Enabled' + }; +} +``` + +You can override the following S3-related resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [s3Bucket](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html) | The S3 bucket that Amplify generates for file storage upon `amplify add storage` | +| [s3AuthPublicPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `public/*` prefix | +| [s3AuthProtectedPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `protected/*` prefix | +| [s3AuthPrivatePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `private/*` prefix | +| [s3AuthUploadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `uploads/*` prefix | +| [s3AuthReadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' read access | +| [s3GuestPublicPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' write access to `public/*` prefix | +| [s3GuestUploadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' write access to `uploads/*` prefix | +| [s3GuestReadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' read access | + +
+ +For example, you can use `amplify override storage` to add additional PUT and GET access IAM policy statements to the S3 bucket's default public Auth policy: + +```ts +import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyS3ResourceTemplate) { + resources.s3AuthPublicPolicy.policyDocument.Statement = [ + ...resources.s3AuthPublicPolicy.policyDocument.Statement, + { + Effect: 'Allow', + Action: ['s3:PutObject', 's3:PutObjectAcl', 's3:GetObject'], + Resource: '' + } + ]; +} +``` + +Please refer to the [IAM documentation](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html) for more information on actions, resources, and condition keys for Amazon S3. + +## Customize Amplify-generated DynamoDB tables + +Apply all the overrides in the `override(...)` function. For example to enable time-to-live specification on your DynamoDB table: + +```ts +import { AmplifyDDBResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; + +export function override(resources: AmplifyDDBResourceTemplate) { + resources.dynamoDBTable.timeToLiveSpecification = { + attributeName: 'ttl', + enabled: true + }; +} +``` + +You can override the following DynamoDB resources that Amplify generates: + +| Amplify-generated resource | Description | +| --- | --- | +| [dynamoDBTable](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The DynamoDB table that Amplify creates upon `amplify add storage` | diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx index 9b57008d7ba..1d45842db60 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx @@ -54,6 +54,16 @@ import js from '/src/fragments/lib-v1/storage/js/transfer-acceleration.mdx'; }} /> +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + + +When you use Transfer Acceleration, additional data transfer charges might apply. For more information about pricing, see [Amazon S3 pricing](https://aws.amazon.com/s3/pricing/). + + + You can enable [Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html) for fast and secure transfer of files over long distances between your end user device and the S3 bucket. You can override the storage resource for this configuration and then leverage the `useAccelerateEndpoint` parameter to use the accelerated S3 endpoint. From fd83de1744ee800019ec28f1768b3b122765348b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 6 May 2024 23:28:33 -0700 Subject: [PATCH 25/88] chore: fix datastore v1 gen1 --- .../getting-started/60_initDataStore.mdx | 1 + .../native_common/getting-started.mdx | 4 +++ .../datastore/native_common/real-time.mdx | 10 ++++++++ .../datastore/native_common/relational.mdx | 6 +++++ .../native_common/setup-auth-rules.mdx | 2 +- .../lib/analytics/flutter/record.mdx | 25 ++++++++++++++----- 6 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/60_initDataStore.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/60_initDataStore.mdx index f8d91d89a30..4bab5bf10e3 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/60_initDataStore.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/60_initDataStore.mdx @@ -27,6 +27,7 @@ class _MyAppState extends State { final datastorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance); await Amplify.addPlugin(datastorePlugin); + try { await Amplify.configure(amplifyconfig); } on AmplifyAlreadyConfiguredException { diff --git a/src/fragments/lib-v1/datastore/native_common/getting-started.mdx b/src/fragments/lib-v1/datastore/native_common/getting-started.mdx index 73877ece557..c8651d3ca13 100644 --- a/src/fragments/lib-v1/datastore/native_common/getting-started.mdx +++ b/src/fragments/lib-v1/datastore/native_common/getting-started.mdx @@ -190,6 +190,10 @@ import android25 from '/src/fragments/lib-v1/datastore/android/getting-started/7 +import flutter26 from '/src/fragments/lib-v1/datastore/flutter/getting-started/60_saveSnippet.mdx'; + + + ### Reading from the database To read from the database, the simplest approach is to query for all records of a given model type. diff --git a/src/fragments/lib-v1/datastore/native_common/real-time.mdx b/src/fragments/lib-v1/datastore/native_common/real-time.mdx index 8b151c32650..6cb54ebba04 100644 --- a/src/fragments/lib-v1/datastore/native_common/real-time.mdx +++ b/src/fragments/lib-v1/datastore/native_common/real-time.mdx @@ -2,6 +2,16 @@ You can subscribe to changes on your Models. This reacts dynamically to updates of data to the underlying Storage Engine, which could be the result of GraphQL Subscriptions as well as Queries or Mutations that run against the backing AppSync API if you are synchronizing with the cloud. + +**NOTE:** AWS AppSync has an [adjustable limit of 100 subscriptions per connection](https://docs.aws.amazon.com/general/latest/gr/appsync.html). DataStore automatically subscribes to create, update, and delete mutations for all models. + +This means that GraphQL APIs with DataStore enabled are limited to 33 models and DynamoDB tables. + +(3 **subscriptions** * 33 **models** = 99 **subscriptions per connection**). + +However, You can [request a service limit increase](https://console.aws.amazon.com/servicequotas/home/services/appsync/quotas/L-AA33EB36) from AWS AppSync to meet the real-time requirements of your application. + + import js0 from '/src/fragments/lib-v1/datastore/js/real-time/observe-snippet.mdx'; + +Join table records must be deleted prior to deleting the associated records. For example, for a many-to-many relationship between Posts and Tags, delete the PostTags join record prior to deleting a Post or Tag. + + + ```graphql enum PostStatus { ACTIVE diff --git a/src/fragments/lib-v1/datastore/native_common/setup-auth-rules.mdx b/src/fragments/lib-v1/datastore/native_common/setup-auth-rules.mdx index 798e46eaf63..b6c1c26a202 100644 --- a/src/fragments/lib-v1/datastore/native_common/setup-auth-rules.mdx +++ b/src/fragments/lib-v1/datastore/native_common/setup-auth-rules.mdx @@ -55,7 +55,7 @@ The following are commonly used patterns for static group authorization. For mor ```graphql type YourModel @model @auth(rules: [{ allow: groups, - groups: ["Admin"] }]) { + groups: ["Admin"] }]) { ... } ``` diff --git a/src/fragments/lib/analytics/flutter/record.mdx b/src/fragments/lib/analytics/flutter/record.mdx index 4b57f62b977..f544a439cdb 100644 --- a/src/fragments/lib/analytics/flutter/record.mdx +++ b/src/fragments/lib/analytics/flutter/record.mdx @@ -30,12 +30,25 @@ However, it can take upwards of 30 minutes for the event to display in the Filte Events have default configuration to flush out to the network every 30 seconds. To modify the default value, assign the `autoFlushEventsInterval` property to your desired duration. The example below configures the plugin to flush events every 10 seconds. -```dart -final plugin = AmplifyAnalyticsPinpoint( - options: const AnalyticsPinpointPluginOptions( - autoFlushEventsInterval: Duration(seconds: 10), - ), -); +```json +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "analytics": { + "plugins": { + "awsPinpointAnalyticsPlugin": { + "pinpointAnalytics": { + "appId": "AppID", + "region": "Region" + }, + "pinpointTargeting": { + "region": "Region" + }, + "autoFlushEventsInterval": 10 + } + } + } +} ``` > **Note** From 1387357a0d03c49d7e312d13c151187cefa4fce5 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 11:24:42 -0700 Subject: [PATCH 26/88] chore: cleanup broken links --- .../getting-started/30_initAnalytics.mdx | 2 +- .../flutter/getting_started/10_preReq.mdx | 2 +- .../30_confirm_custom_challenge.mdx | 2 +- .../signin_next_steps/50_reset_password.mdx | 2 +- .../signin_next_steps/60_confirm_signup.mdx | 2 +- .../signin_with_custom_flow/10_cli_setup.mdx | 2 +- .../signin_next_steps/common.mdx | 4 + .../flutter/sync/50-selectiveSync.mdx | 2 +- .../flutter/getting-started/10_preReq.mdx | 4 +- .../lib-v1/graphqlapi/flutter/mutate-data.mdx | 2 +- .../getting_started/getting-started.mdx | 2 +- .../index.mdx | 2 +- .../custom-business-logic/index.mdx | 4 +- .../batch-put-custom-resolver/index.mdx | 4 +- .../query-with-sorting/index.mdx | 4 +- .../index.mdx | 24 +- .../customize-authorization-rules/index.mdx | 4 +- .../index.mdx | 2 +- .../graphqlapi/mutate-data/index.mdx | 2 +- .../graphqlapi/query-data/index.mdx | 2 +- .../search-and-result-aggregations/index.mdx | 2 +- .../graphqlapi/subscribe-data/index.mdx | 2 +- .../restapi/configure-rest-api copy/index.mdx | 290 ------------------ .../restapi/configure-rest-api/index.mdx | 2 +- .../storage/configure-storage/index.mdx | 6 +- .../build-a-backend/storage/import/index.mdx | 2 +- 26 files changed, 46 insertions(+), 332 deletions(-) delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api copy/index.mdx diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx index 63341f691c5..282b63bbd91 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx @@ -18,7 +18,7 @@ Future _configureAmplify() async { -When running your app on MacOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/[platform]/start/project-setup/platform-setup/#enable-keychain). +When running your app on MacOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/prev/start/project-setup/platform-setup/#enable-keychain). diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx index 3f857db3479..a679116b9c0 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx @@ -1,3 +1,3 @@ A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated. -Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. +Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/prev/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx index 72ec86e6801..0d8ac9517b2 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/30_confirm_custom_challenge.mdx @@ -1,4 +1,4 @@ -If the next step is `confirmSignInWithCustomChallenge`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you configured as part of a [custom sign in flow](/[platform]/build-a-backend/auth/sign-in-custom-flow/). +If the next step is `confirmSignInWithCustomChallenge`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you configured as part of a [custom sign in flow](/gen1/[platform]/prev/build-a-backend/auth/sign-in-custom-flow/). For example, your custom challenge Lambda may pass a prompt to the frontend which requires the user to enter a secret code. diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx index ba09c389051..2430f319473 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/50_reset_password.mdx @@ -2,7 +2,7 @@ If the next step is `resetPassword`, Amplify Auth requires that the user reset t Use the `resetPassword` API to guide the user through resetting their password, then call `Amplify.Auth.signIn` when that's complete to restart the sign-in flow. -See the [reset password](/[platform]/build-a-backend/auth/manage-passwords/) docs for more information. +See the [reset password](/gen1/[platform]/prev/build-a-backend/auth/manage-passwords/) docs for more information. ```dart Future _handleSignInResult(SignInResult result) async { diff --git a/src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx b/src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx index ce940a3c8b6..4d2ef6a204b 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_next_steps/60_confirm_signup.mdx @@ -2,7 +2,7 @@ If the next step is `resetPassword`, Amplify Auth requires that the user confirm Use the `resendSignUpCode` API to send a new sign up code to the registered email or phone number, followed by `confirmSignUp` to complete the sign up. -See the [confirm sign up](/[platform]/build-a-backend/auth/enable-sign-in/#register-a-user) docs for more information. +See the [confirm sign up](/gen1/[platform]/prev/build-a-backend/auth/enable-sign-in/#register-a-user) docs for more information. diff --git a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx index 976db3220e8..308288ce93d 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx @@ -212,4 +212,4 @@ Once finished, run `amplify push` to publish your changes. The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). -If you have already configured custom auth without the aid of the Amplify CLI, you can use the custom auth flow by changing the `authenticationFlowType` value in your [Amplify configuration](/gen1/[platform]/prev/build-a-backend/auth/existing-resources/) to `CUSTOM_AUTH`. +If you have already configured custom auth without the aid of the Amplify CLI, you can use the custom auth flow by changing the `authenticationFlowType` value in your [Amplify configuration](/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/) to `CUSTOM_AUTH`. diff --git a/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx b/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx index 9c122d613df..54813f058cf 100644 --- a/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx @@ -1,3 +1,7 @@ +import flutterMaintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you ran `amplify add auth`. Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter in the signin result. import ios0 from '/src/fragments/lib-v1/auth/ios/signin_next_steps/10_signin.mdx'; diff --git a/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx b/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx index a1bc7cf8940..b6291a7d6de 100644 --- a/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx +++ b/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx @@ -8,7 +8,7 @@ Selective sync works by applying predicates to the base and delta sync queries, -Note that selective sync is applied on top of authorization rules you’ve defined on your schema with the `@auth` directive. For more information see the [Setup authorization rules](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/) section. +Note that selective sync is applied on top of authorization rules you’ve defined on your schema with the `@auth` directive. For more information see the [Setup authorization rules](/gen1/[platform]/prev/build-a-backend/more-features/datastore/authz-rules-setup/) section. diff --git a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx index bced735fceb..dbde1529351 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx @@ -1,4 +1,4 @@ -* [Install and configure Amplify CLI](/[platform]/tools/cli/start/set-up-cli/) +* [Install and configure Amplify CLI](/gen1/[platform]/prev/tools/cli/start/set-up-cli/) * A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated The following are also required, depending on which platforms you are targeting: @@ -9,4 +9,4 @@ * Any Windows OS meeting Flutter minimums * macOS version 10.15 or higher * Any Ubuntu Linux distribution meeting Flutter minimums - * For a full example please follow the [project setup walkthrough](/[platform]/start/project-setup/create-application/) + * For a full example please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) diff --git a/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx b/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx index fed67caca55..ace9ee8ea40 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx @@ -2,7 +2,7 @@ In this guide, you will learn how to create, update, and delete your data using Before you begin, you will need: -- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/) +- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/) ## Run mutations to create, update, and delete application data diff --git a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx index eb7fdc908e2..ffd229d7cb1 100644 --- a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx +++ b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx @@ -34,7 +34,7 @@ To use Push Notifications with Amplify, you have the option to either have the A -> Prerequisite: [Install and configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/) +> Prerequisite: [Install and configure the Amplify CLI](/gen1/[platform]/prev/tools/cli/start/set-up-cli/) diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx index 5299f7d9142..a193e7eb2d3 100644 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx @@ -549,7 +549,7 @@ type Post } ``` -For more information on field-level auth rules please refer to our documentation on [Field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules). +For more information on field-level auth rules please refer to our documentation on [Field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules). ## Deploy your API diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx index 0921819b749..3cc58b32c50 100644 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx +++ b/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx @@ -57,7 +57,7 @@ type Query { - [HTTP resolver](#http-resolver): call an HTTP endpoint upon a query or mutation - [AppSync JavaScript or VTL resolver](#appsync-javascript-or-vtl-resolver) (most advanced): use AppSync's JavaScript resolver or AppSync's VTL mapping templates to customize the query and mutation logic -3. Secure your custom query or mutation with [field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) +3. Secure your custom query or mutation with [field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) - Note: Dynamic authorization rules are not supported on a custom query or mutation. ## Lambda function resolver @@ -847,7 +847,7 @@ responseMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, " ## Add authorization rules to custom queries and mutations -Authorization rules can be applied with the `@auth` directive in the same way as field-level authorization rules. See [Field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules) for details. +Authorization rules can be applied with the `@auth` directive in the same way as field-level authorization rules. See [Field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules) for details. In the example below, `myCustomMutation` can only be executed by signed-in customers who are authenticated with IAM: diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx index f5cffd36ab9..89692baeb1d 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx @@ -54,7 +54,7 @@ input BatchCreateTodo { } ``` -2. [Create a custom resource for your resolver](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) and use the following code snippets as a guide to get started +2. [Create a custom resource for your resolver](/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) and use the following code snippets as a guide to get started 2. Follow the steps for creating a custom resolver: ```bash @@ -152,4 +152,4 @@ By using CloudFormation parameters, you contextualize your custom resolvers to t 3. Run `amplify push` and deploy your API -The full documentation for custom resolvers [is available here](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) +The full documentation for custom resolvers [is available here](/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx index 188ef04376a..d5d585c2781 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx @@ -70,7 +70,7 @@ type ModelTodoConnection { By default, the `listTodos` query will return the `items` array **unordered**. Many times you will need these items to be ordered by title, by creation date, or in some other way. -To enable this, you can use the [@index](/[platform]/build-a-backend/graphqlapi/data-modeling/) directive. This directive will allow you to set a custom `sortKey` on any field in your API. +To enable this, you can use the [@index](/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/) directive. This directive will allow you to set a custom `sortKey` on any field in your API. ### Implementation @@ -133,4 +133,4 @@ query todosByDateDescending { } ``` -To learn more about the `@index` directive, check out the documentation [here](/[platform]/build-a-backend/graphqlapi/data-modeling/) +To learn more about the `@index` directive, check out the documentation [here](/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx index c1568912191..fb66819d93b 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx @@ -35,7 +35,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -The following content requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). +The following content requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). @@ -123,7 +123,7 @@ You can use the `:variable` notation to reference input variables from the query Amplify’s GraphQL API operates on a deny-by-default basis. The `{ allow: public }` auth rule in the example schema above designates that anyone using an API Key is authorized to execute the query. -Review [Authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) to limit access to these queries and mutations based on API Key, Amazon Cognito User Pool, OpenID Connect, AWS Identity and Access Management (IAM), or a custom Lambda function. +Review [Authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) to limit access to these queries and mutations based on API Key, Amazon Cognito User Pool, OpenID Connect, AWS Identity and Access Management (IAM), or a custom Lambda function. @@ -260,7 +260,7 @@ In addition, the target security group(s) must have two outbound rules set up: -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). @@ -397,7 +397,7 @@ The SQL statements defined in the `.sql` files will be executed as if they were -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). @@ -553,7 +553,7 @@ type Post } ``` -For more information on field-level auth rules please refer to our documentation on [Field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules). +For more information on field-level auth rules please refer to our documentation on [Field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules). ## Deploy your API @@ -570,7 +570,7 @@ cdk deploy -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). @@ -728,7 +728,7 @@ type Ingredient @refersTo(name: "Ingredients") @model { } ``` -In our example, we'll add a public authorization rule, meaning anyone with an API key can create, read, update, and delete records from the database. Review [Customize authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) to see the full scope of model-level authorization capabilities. +In our example, we'll add a public authorization rule, meaning anyone with an API key can create, read, update, and delete records from the database. Review [Customize authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) to see the full scope of model-level authorization capabilities. ```diff input AMPLIFY { @@ -764,7 +764,7 @@ cdk deploy -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). @@ -893,7 +893,7 @@ cdk deploy -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). +This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). @@ -1013,6 +1013,6 @@ To return the actual SQL error instead of a generic error from GraphQL responses Our recommended next steps include using the GraphQL API to mutate and query data on app clients or how to customize the authorization rules for your custom queries and mutations. Some resources that will help with this work include: -- [Create, update, and delete application data](/[platform]/build-a-backend/graphqlapi/mutate-data/) -- [Read application data](/[platform]/build-a-backend/graphqlapi/query-data/) -- [Customize Authorization Rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) +- [Create, update, and delete application data](/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/) +- [Read application data](/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/) +- [Customize Authorization Rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx index 1cd3af95b35..9082451329a 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx @@ -552,7 +552,7 @@ Your Lambda authorization function needs to return the following JSON: } ``` -Review the Amplify Library documentation to set the custom authorization token for [GraphQL API](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules#aws-lambda) and [DataStore](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/#configure-custom-authorization-logic-with-aws-lambda). +Review the Amplify Library documentation to set the custom authorization token for [GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules#aws-lambda) and [DataStore](/gen1/[platform]/prev/build-a-backend/more-features/datastore/authz-rules-setup/#configure-custom-authorization-logic-with-aws-lambda). ## Configure multiple authorization rules @@ -595,7 +595,7 @@ In the example above: - any user (signed in or not, verified by IAM) is allowed to read all posts - owners are allowed to create, read, update, and delete their own posts. -If you are using DataStore and have multiple authorization rules, you can let DataStore automatically determine the best authorization mode client-side. Review how to [Configure Multiple Authorization Types](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/#configure-multiple-authorization-types) on DataStore for more details. +If you are using DataStore and have multiple authorization rules, you can let DataStore automatically determine the best authorization mode client-side. Review how to [Configure Multiple Authorization Types](/gen1/[platform]/prev/build-a-backend/more-features/datastore/authz-rules-setup/#configure-multiple-authorization-types) on DataStore for more details. ## Field-level authorization rules diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx index 577a673050a..fb93e0d0a27 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx @@ -47,7 +47,7 @@ Run the command above to override Amplify-generated GraphQL API resources includ -If you need to customize a specific Amplify-generated VTL resolver, review [Override Amplify-generated resolvers](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#override-amplify-generated-resolvers) first. +If you need to customize a specific Amplify-generated VTL resolver, review [Override Amplify-generated resolvers](/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/#override-amplify-generated-resolvers) first. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/index.mdx index 582f5f2ff25..12007c02301 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/index.mdx @@ -58,7 +58,7 @@ In this guide, you will learn how to create, update, and delete your data using Before you begin, you will need: -- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/) +- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/) ## Run mutations to create, update, and delete application data diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/index.mdx index 38274b8aabb..64e69379031 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/index.mdx @@ -35,7 +35,7 @@ You can read application data using the Amplify GraphQL client. In this guide, w Before you begin, you will need: -- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/) +- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/) - Data already created to view ## List and get your data diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx index adf0c4a8c57..79f15b91cca 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx @@ -51,7 +51,7 @@ type Student @model @searchable { } ``` -> Once the `@searchable` directive is added, all new records added to the model are streamed to OpenSearch. To backfill existing data, see [Backfill OpenSearch index from DynamoDB table](/[platform]/build-a-backend/graphqlapi/troubleshooting/#backfill-opensearch-index-from-dynamodb-table). +> Once the `@searchable` directive is added, all new records added to the model are streamed to OpenSearch. To backfill existing data, see [Backfill OpenSearch index from DynamoDB table](/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/#backfill-opensearch-index-from-dynamodb-table). ## Search and filter data diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx index 9fd2e89d703..75864a69620 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx @@ -35,7 +35,7 @@ In this guide, we will outline the benefits of enabling real-time data integrati Before you begin, you will need: -- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/) +- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/) - Data already created to modify ## Set up a real-time subscription diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api copy/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api copy/index.mdx deleted file mode 100644 index 661b7680e18..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api copy/index.mdx +++ /dev/null @@ -1,290 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure REST API', - description: "Use Amplify CLI's simple guided workflow to add REST APIs to cloud-based web and mobile apps.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/restapi/configure-rest-api/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -The Amplify CLI provides a guided workflow to easily add, develop, test and manage REST APIs to access your AWS resources from your web and mobile applications. - -A REST API or HTTP endpoint will be composed by one or more paths. Eg: `/items`. Each path will use a Lambda function to handle HTTP requests and responses. Amplify CLI creates a single resource in Amazon API Gateway so you can handle all routes, HTTP Methods and paths, with a single Lambda function via a Lambda Proxy integration. HTTP proxy integrations forward all requests and responses directly through to your HTTP endpoint. - -Amplify CLI let's you choose either an existing Lambda function or create a new one. To kickstart your implementation, you can choose between the following templates: - -- Serverless ExpressJS function -- CRUD function for DynamoDB - -> Lambda templates use [serverless-express](https://github.com/awslabs/aws-serverless-express) and provide the building blocks to start your REST API development. - -> See the list of all [supported Lambda runtimes](/[platform]/build-a-backend/functions/set-up-function/). - -Amplify CLI allows you to restrict REST API access to - -- Only authenticated users; or -- Authenticated and Guest users -- User Pool Groups - -See a description of these user types below - -| User type | Description | -| --- | --- | -| Authenticated user | User needs to sign in to use the REST API | -| Guest user | User doesn't need to sign in to use the REST API | -| User Pool Group | User needs to sign in and belong to the User Pool Group to use the REST API | - -For each user type you can further specify what actions it has access to. - -| User type | Actions | Http Method | Authentication Provider | -| --- | --- | --- | --- | -| Authenticated user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | -| Guest user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | -| User Pool Group | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | - -REST APIs have support for [multiple environments](/[platform]/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. - -Because Amplify environments could be in separate AWS accounts, you cannot use the environment feature of API Gateway. Each Amplify environment will have a separate API Gateway resource associated with it. For example: - -```console -https://.execute-api.eu-west-2.amazonaws.com/dev/items -https://.execute-api.eu-west-2.amazonaws.com/prod/items -``` - -## Create a REST API - -Navigate into the root of a JavaScript, iOS, or Android project and run: - -```bash -amplify init -``` - -Follow the wizard to create a new app. After finishing the wizard run: - -```bash -amplify add api -``` - -Select the following options: - -- Please select from one of the below mentioned services: **REST** -- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsApi** -- Provide a path (e.g., /book/\{isbn}): **/items** - -This will be the configuration for `/items` path in API Gateway: - -```console -/ - |_ /items Main resource. Eg: /items - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser - |_ /\{proxy+} Proxy resource. Eg: /items/, /items/id, items/object/\{id} - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser -``` - -By default Amplify CLI creates a greedy path variable `/items/\{proxy+}` that catches all child resources for a path and forwards them to your Lambda. This will match all child routes including `/items/id` and `/items/object/id`. - -- Choose a Lambda source **Create a new Lambda function** -- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsLambda** -- Provide the AWS Lambda function name: **itemsLambda** -- Choose the runtime that you want to use: **NodeJS** -- Choose the function template that you want to use: **Serverless ExpressJS function** - -The Lambda function template **Serverless ExpressJS function** implements route handlers for `GET`, `POST`, `PUT` and `DELETE` Http Methods and paths for `/items` and `/items/*`. Some possible routes examples include: - -```console -GET /items List all items -GET /items/1 Load an item by id -POST /items Create an item -PUT /items Update an item -DELETE /items/1 Delete an item by id -``` - -- Do you want to access other resources in this project from your Lambda function? **No** -- Do you want to invoke this function on a recurring schedule? **No** -- Do you want to configure Lambda layers for this function? **No** -- Do you want to edit the local lambda function now? **Yes** - -> You are not going to change this template but it's good that you have it open as you follow the next steps. - -- Press enter to continue -- Restrict API access **Yes** -- Who should have access? **Authenticated and Guest users** -- What kind of access do you want for Authenticated users? **create, read, update, delete** -- What kind of access do you want for Guest users? **read** - -When configuration of your API is complete, the CLI displays a message confirming that you have configured local CLI metadata for this category. You can confirm this by running `amplify status`. Finally deploy your changes to the cloud: - -Amplify CLI restricts API access combining Amazon Cognito for authentication and AWS IAM (Identity and Access Management) for granting execution permissions on routes. - -- Do you want to add another path? **No** - -Deploy your new API. - -```bash -amplify push -``` - -At the end of this command you can take note of your new REST API url. - -```console -REST API endpoint: https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev -``` - -> REST APIs follow this pattern `https://{restapi-id}.execute-api.\{region}.amazonaws.com/\{environment}/\{path}`. - -Let's see an overview of all the resources created by Amplify CLI. - -```console -REST - |_ /items (path) - |_ itemsApi (Amazon API Gateway) - |_ itemsLambda (AWS Lambda) - |_ Logs (Amazon CloudWatch) -``` - -## Create REST API and restrict specific routes to specific User Pool Groups - -If your app uses User Pool Groups to manage different user types and would like to restrict access of specific routes to specific User Pool Groups. You can accomplish this by the following flow: - -- Create API route. -- Add API route handler function. -- Restrict-access to the API route to the User Pool Group. - -> The following example flow assumes the existence of two User Pool Groups : AdminUsers and GuestUsers for a Book store. The app would like to limit admin functionality like updating book records to the AdminUsers User Pool Group, while borrowing and returning books would be limited to the GuestUsers User Pool Group. -> -> - Path : /book/admin is restricted to AdminUsers and commands are handled by the bookAdminHandler lambda function -> - Path : /book/guest is restricted to GuestUsers and commands are handled by the bookGuestHandler lambda function - -```bash -amplify add api -$> ? Select from one of the below mentioned services: REST -$> ✔ Provide a friendly name for your resource to be used as a label for this category in the project: · mybookapi -$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/admin -$> ✔ Choose a Lambda source · Create a new Lambda function -$> ? Provide an AWS Lambda function name: bookAdminHandler -$> ? Choose the runtime that you want to use: NodeJS -$> ? Choose the function template that you want to use: Hello World -$> ? Do you want to configure advanced settings? No -$> ? Do you want to edit the local lambda function now? No -Successfully added resource bookAdminHandler locally. -$> ✔ Restrict API access? (Y/n) · yes -$> ✔ Restrict access by: · Individual Groups -$> ✔ Select groups: AdminUsers -$> ✔ What permissions do you want to grant to AdminUsers users? · create, read, update, delete -$> ✔ Do you want to add another path? (y/N) · yes -$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/guest -$> ✔ Choose a Lambda source · Create a new Lambda function -$> ? Provide an AWS Lambda function name: bookGuestHandler -$> ? Choose the runtime that you want to use: NodeJS -$> ? Choose the function template that you want to use: Hello World -$> ? Do you want to configure advanced settings? No -$> ? Do you want to edit the local lambda function now? No -Successfully added resource bookGuestHandler locally. -$> ✔ Restrict API access? (Y/n) · yes -$> ✔ Restrict access by: Individual Groups -$> ✔ Select groups: GuestUsers -$> ✔ What permissions do you want to grant to GuestUsers users? create, read, update -$> ✔ Do you want to add another path? (y/N) No -✅ Successfully added resource mybookapi locally -``` - -At the end of this command you can verify the routes and their respective User Pool Group restrictions in the `cli-inputs.json` file at the following path. - -```bash - /amplify/backend/api//cli-inputs.json -``` - -## REST endpoint that triggers new Lambda functions - -During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths. - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source Create a new Lambda function -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda -? Provide the AWS Lambda function name: itemsLambda -? Choose the function template that you want to use: - CRUD function for Amazon DynamoDB -❯ Serverless ExpressJS function -``` - -## REST endpoint that triggers existing Lambda functions - -During the CLI setup, you'll be guided through to use your own Lambda functions which you've initialized as a part of your CLI project using the `amplify add function` command. This would allow you to have custom logic in your Lambda function and not use the predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) templates generated by the CLI as in the examples above. - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source - Create a new Lambda function -❯ Use a Lambda function already added in the current Amplify project -``` - -## Set up a REST API with Amazon DynamoDB - -During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths with support for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command). - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source Create a new Lambda function -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda -? Provide the AWS Lambda function name: itemsLambda -? Choose the function template that you want to use: -❯ CRUD function for Amazon DynamoDB - Serverless ExpressJS function -``` - -In the example above with `/items` path, the following API will be created for you: - -1. GET /items/[ID] will return a list containing the item at the [ID]. If the item does not exist then an empty array is returned. -2. GET /items/object/[ID] will return a single item at [ID]. If the item does not exist then an empty object is returned. -3. PUT /items with your item in the request body will create or update the item. -4. POST /items with your item in the request body will create or update the item. -5. DELETE /items/object/[ID] will delete the item. - -When you have a sort key, you can append it to the end of the path, for example: `GET /items/object/[ID]/[SORT_KEY_ID]` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx index 661b7680e18..5dcc0e6ea7c 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx @@ -68,7 +68,7 @@ For each user type you can further specify what actions it has access to. | Guest user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | | User Pool Group | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | -REST APIs have support for [multiple environments](/[platform]/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. +REST APIs have support for [multiple environments](/gen1/[platform]/prev/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. Because Amplify environments could be in separate AWS accounts, you cannot use the environment feature of API Gateway. Each Amplify environment will have a separate API Gateway resource associated with it. For example: diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx index 9a7dea61c1d..b618c232a89 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx @@ -115,7 +115,7 @@ Granting access to guest users will allow the specified CRUD operations on objec #### Individual Group access -Select `Individual Groups` to scope access permissions based on [Cognito User Groups](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) +Select `Individual Groups` to scope access permissions based on [Cognito User Groups](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) ```console ? Select groups: @@ -148,7 +148,7 @@ Lastly, you have the option of configuring a Lambda function that can execute in ? Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) ``` -Learn more about this workflow [here](/[platform]/tools/cli/usage/lambda-triggers/#s3-lambda-triggers). +Learn more about this workflow [here](/gen1/[platform]/prev/tools/cli/usage/lambda-triggers/#s3-lambda-triggers). That's it! Your content storage is set up! Head to the [library's storage docs](/[platform]/build-a-backend/storage/set-up-storage/) to integrate this newly created S3 bucket into your app. @@ -185,6 +185,6 @@ Then, you'll need to specify your indexes. The concept behind "indexes", "partit ? Do you want to add a Lambda Trigger for your Table? (y/N) ``` -If you want to configure a Lambda trigger for your Table, you'll have the option. Learn more about this workflow [here](/[platform]/tools/cli/usage/lambda-triggers/#dynamodb-lambda-triggers). +If you want to configure a Lambda trigger for your Table, you'll have the option. Learn more about this workflow [here](/gen1/[platform]/prev/tools/cli/usage/lambda-triggers/#dynamodb-lambda-triggers). That's it! Your NoSQL Database is set up! diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx index db4406cb2df..acf062881db 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx @@ -71,7 +71,7 @@ By default, Amplify Libraries assumes that S3 buckets are configured with the fo - `protected/{user_identity_id}/` - Readable by all users, but writable only by the creating user - `private/{user_identity_id}/` - Only accessible for the individual user -You can either configure your IAM role to use the Amplify-recommended policies or in your Amplify libraries configuration [overwrite the default storage path behavior](/gen1/[platform]/build-a-backend/storage/configure-access/#customize-object-key-path). +You can either configure your IAM role to use the Amplify-recommended policies or in your Amplify libraries configuration [overwrite the default storage path behavior](/gen1/[platform]/prev/build-a-backend/storage/configure-access/#customize-object-key-path). It is highly recommended to review your S3 bucket's CORS settings. Review the [recommendation guide here](/[platform]/build-a-backend/storage/set-up-storage/#amazon-s3-bucket-cors-policy-setup). From 0e44f08fd01d20f03be87f23cc4cce8522f8a73b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 12:46:58 -0700 Subject: [PATCH 27/88] chore: add maintenance banner to all push notification pages --- .../push-notifications/app-badge-count/index.mdx | 4 ++++ .../push-notifications/enable-rich-notifications/index.mdx | 4 ++++ .../push-notifications/identify-user/index.mdx | 4 ++++ .../prev/build-a-backend/push-notifications/index.mdx | 4 ++++ .../push-notifications/interact-with-notifications/index.mdx | 4 ++++ .../push-notifications/receive-device-token/index.mdx | 4 ++++ .../push-notifications/request-permissions/index.mdx | 4 ++++ .../push-notifications/set-up-push-notifications/index.mdx | 4 ++++ .../push-notifications/set-up-push-service/index.mdx | 4 ++++ .../push-notifications/test-notifications/index.mdx | 4 ++++ 10 files changed, 40 insertions(+) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx index 72fae1dafd1..8030af50937 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx @@ -22,6 +22,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import appBadgeCount from '/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/app-badge-count.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx index 7825769fd6c..362441b4753 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx @@ -22,6 +22,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import enableRichNotifications from '/src/fragments/lib-v1/push-notifications/react-native/enable_rich_notifications/enable-rich-notifications.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx index e89be8248b6..b6bcfae599a 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx @@ -22,6 +22,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import identifyUser from '/src/fragments/lib-v1/push-notifications/react-native/identify_user/identify-user.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx index 7d47c934064..d3f9efd4e0a 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx @@ -12,6 +12,10 @@ export const meta = { route: '/gen1/[platform]/prev/build-a-backend/push-notifications' }; +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + export const getStaticPaths = async () => { return getCustomStaticPath(meta.platforms); }; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx index a2352a39102..67a3ef21677 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx @@ -22,6 +22,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import interactWithNotifications from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/interact-with-notifications.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx index 8c2f5ce75bd..e936c035119 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx @@ -22,6 +22,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import receiveDeviceToken from '/src/fragments/lib-v1/push-notifications/react-native/receive-device-token.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx index 951bb6581da..5c75784f9ef 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx @@ -22,6 +22,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import requestPermissions from '/src/fragments/lib-v1/push-notifications/react-native/request-permissions.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx index cdd99af5786..e3ea256651c 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx @@ -22,6 +22,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import gettingStarted from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/getting-started.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx index b25258258b1..687b586db07 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx @@ -23,6 +23,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import crossPlatformSetupService from '/src/fragments/lib/push-notifications/common/setup_push_service/cross-platform-setup.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx index 27fd205214d..07fdf7aff34 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx @@ -22,6 +22,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import testing from '/src/fragments/lib/push-notifications/common/testing.mdx'; From 77f1c30240528021763d85bb861c6c130539fc04 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 12:50:41 -0700 Subject: [PATCH 28/88] chore: add maintenance banner on all sub headings --- src/pages/gen1/[platform]/prev/build-a-backend/auth/index.mdx | 4 ++++ .../gen1/[platform]/prev/build-a-backend/functions/index.mdx | 4 ++++ .../gen1/[platform]/prev/build-a-backend/graphqlapi/index.mdx | 4 ++++ .../prev/build-a-backend/more-features/analytics/index.mdx | 4 ++++ .../prev/build-a-backend/more-features/datastore/index.mdx | 4 ++++ .../[platform]/prev/build-a-backend/more-features/index.mdx | 4 ++++ .../gen1/[platform]/prev/build-a-backend/restapi/index.mdx | 4 ++++ .../[platform]/prev/build-a-backend/troubleshooting/index.mdx | 4 ++++ .../troubleshooting/upgrade-amplify-packages/index.mdx | 4 ++++ 9 files changed, 36 insertions(+) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/index.mdx index 4382a6d8ead..69d0fd67864 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/index.mdx @@ -33,4 +33,8 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx index be5e840404b..4bc56c816be 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx @@ -33,4 +33,8 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/index.mdx index 973dc7474cf..ccdbe62961f 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/index.mdx @@ -33,4 +33,8 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/more-features/analytics/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/more-features/analytics/index.mdx index 2790f29420c..e3fbfded423 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/more-features/analytics/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/more-features/analytics/index.mdx @@ -33,4 +33,8 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/more-features/datastore/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/more-features/datastore/index.mdx index 70a9612afbc..3230864bb41 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/more-features/datastore/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/more-features/datastore/index.mdx @@ -33,4 +33,8 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/more-features/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/more-features/index.mdx index 5f463294830..4c015a086d0 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/more-features/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/more-features/index.mdx @@ -33,4 +33,8 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/index.mdx index 0a22b77ff18..f2343ca7d97 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/index.mdx @@ -33,4 +33,8 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/troubleshooting/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/troubleshooting/index.mdx index 53e4cd06e10..dd89802b73e 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/troubleshooting/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/troubleshooting/index.mdx @@ -31,4 +31,8 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/troubleshooting/upgrade-amplify-packages/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/troubleshooting/upgrade-amplify-packages/index.mdx index d019e64c7ac..45f17f0ca55 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/troubleshooting/upgrade-amplify-packages/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/troubleshooting/upgrade-amplify-packages/index.mdx @@ -27,6 +27,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import flutter1 from '/src/fragments/lib-v1/troubleshooting/flutter/upgrading.mdx'; From 66ade8c1bc305d609ca0d509d585e8f1c756aad6 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 12:59:52 -0700 Subject: [PATCH 29/88] chore: add missing maintenance banners to auth section --- .../prev/build-a-backend/auth/admin-actions/index.mdx | 4 ++++ .../prev/build-a-backend/auth/advanced-workflows/index.mdx | 4 ++++ .../build-a-backend/auth/existing-resources-no-cli/index.mdx | 4 ++++ .../build-a-backend/auth/import-existing-resources/index.mdx | 4 ++++ .../[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx | 4 ++++ .../prev/build-a-backend/auth/override-cognito/index.mdx | 4 ++++ 6 files changed, 24 insertions(+) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx index ea08192ffa6..e6124862644 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx @@ -21,6 +21,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + Admin Actions allow you to execute queries and operations against users and groups in your Cognito user pool. For example, the ability to list all users in a Cognito User Pool may provide useful for the administrative panel of an app if the logged-in user is a member of a specific Group called "Admins". diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/advanced-workflows/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/advanced-workflows/index.mdx index 962b8c09604..0f5a7137a50 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/advanced-workflows/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/advanced-workflows/index.mdx @@ -27,6 +27,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + ## Subscribing Events diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx index c178748008e..926b171dba6 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx @@ -20,6 +20,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import android0 from '/src/fragments/lib/auth/existing-resources.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx index ecf4d878240..5f816c4516d 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx @@ -28,6 +28,10 @@ export function getStaticProps(context) { } }; } + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + Import existing Amazon Cognito resources into your Amplify project. Get started by running `amplify import auth` command to search for & import an existing Cognito User Pool & Identity Pool in your account. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx index e76b28341fe..b855e9df6cf 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx @@ -27,6 +27,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + The Auth category supports Multi-factor Authentication (MFA) for user sign-in flows. MFA is an extra layer of security used to make sure that users trying to gain access to an account are who they say they are. It requires users to provide additional information to verify their identity. Amplify Auth supports the MFA methods with Time-based-One-Time Passwords (TOTP) as well as text messages (SMS). In this guide we will review how you can set up MFA using TOTP and SMS and the tradeoffs between these methods to help you choose the right set up for your application. We’ll also review how to set up MFA to remember a device and reduce sign-in friction for your users. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx index 6b6303d0afd..9903a214370 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx @@ -28,6 +28,10 @@ export function getStaticProps(context) { } }; } + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + ```bash From 5df9bb7234dd0922fe76e92b9a0deef69c9cfdc3 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 13:08:48 -0700 Subject: [PATCH 30/88] chore: add existing resources section --- src/directory/directory.mjs | 11 + .../existing-resources/cdk/index.mdx | 839 ++++++++++++++ .../existing-resources/cli/index.mdx | 1016 +++++++++++++++++ .../existing-resources/index.mdx | 33 + 4 files changed, 1899 insertions(+) create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 611eafb265b..8d947a34928 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -2497,6 +2497,17 @@ export const directory = { } ] }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx', + children: [ + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx' + } + ] + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/server-side-rendering/index.mdx' }, diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx new file mode 100644 index 00000000000..e444198ec2d --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx @@ -0,0 +1,839 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Connect to existing AWS resources built with the CDK', + description: "Connect a new app to AWS resources built with the CDK.", + platforms: [ + 'flutter', + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +This guide shows you how to connect a new app to AWS resources you've already created using the [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/). The AWS CDK is an open-source software development framework for defining cloud infrastructure as code with modern programming languages. This infrastructure is then deployed through [AWS CloudFormation](https://aws.amazon.com/cloudformation/). + + + +In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then create and connect a React web app to the GraphQL API. + + + + + +In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then build and integrate a Flutter app with the GraphQL API. + + + +Before you begin, you will need: + + + +* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. +* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. +* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. + + + + + +* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. +* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. +* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. +* Flutter and its command-line tools [installed](https://docs.flutter.dev/get-started/install) and configured. + + + +## Build a GraphQL API using the Amplify Data CDK construct + +The CDK provides a simple way to define cloud infrastructure in code. In this section, we will use the CDK to build out the backend resources for our application. + +**Step 1:** Create a folder for the CDK app by running the following command in your terminal. + +```bash title="Terminal" showLineNumbers={false} +mkdir cdk-backend +``` + +**Step 2:** Navigate to the `cdk-backend` folder and create a new CDK project by running the `cdk init` command and specifying your preferred language. + +```bash title="Terminal" +cd cdk-backend +cdk init --language typescript +``` + +**Step 3:** Open the newly created CDK project using VS Code, or your preferred IDE. + +**Step 4:** In your terminal, navigate to the `cdk_backend` root folder, and install the AWS Amplify Data package by running the following command. + +```bash title="Terminal" showLineNumbers={false} +npm install @aws-amplify/data-construct +``` + +**Step 5:** Update the `cdk_backend/lib/cdk-backend-stack.ts` file as shown in the following code to use the `AmplifyData` construct to create an AWS AppSync API. + +```ts title="lib/cdk-backend-stack.ts" +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { + AmplifyData, + AmplifyDataDefinition +} from '@aws-amplify/data-construct'; + +export class CdkBackendStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // highlight-start + new AmplifyData(this, 'AmplifyCdkData', { + definition: AmplifyDataDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + id: ID! + name: String! + description: String + complete: Boolean + } + `), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { + expires: cdk.Duration.days(30) + } + } + }); + // highlight-end + } +} +``` + +**Step 6:** Deploy the CDK stacks by running the following command. + +```bash showLineNumbers={false} +cdk deploy +``` + +**Step 7:** The CDK will prepare the resources for deployment and will display the following prompt. Enter **Y** and press **Enter**. + +The CDK preparing for deployment. + +The CDK will deploy the stacks and display the following confirmation. Note the details of the deployed API; we’re going to use them in the next section. + +CDK deploying the stacks. + +Now that you have built the backend API with the CDK, you can connect a frontend. + + + +## Build a React app and connect to the GraphQL API + +In this section, we will connect a React web app to our existing GraphQL API. First, we will create a new React project and install the necessary Amplify packages. Next, we will use the Amplify CLI to generate GraphQL code matching our API structure. Then, we will add React components to perform queries and mutations to manage to-do items in our API. After that, we will configure the Amplify library with details of our backend API. Finally, we will run the application to demonstrate full CRUD functionality with our existing API. + +**Step 1:** Create a React app by running the following command in your terminal. + +```bash title="Terminal" showLineNumbers={false} +npx create-react-app react-amplify-connect +``` + +**Step 2:** Open the newly created React app using VS Code, or your preferred IDE. + +**Step 3:** Install the `aws-amplify`, `@aws-amplify/ui-react`, and `@aws-amplify/cli` packages by running the following commands. + +```bash title="Terminal" showLineNumbers={false} +npm install aws-amplify @aws-amplify/ui-react @aws-amplify/cli +``` + +**Step 4:** Use the awsAppsyncApiId and awsAppsyncRegion values of the CDK stack you created previously to generate the GraphQL client helper code by running the following command. + +awsAppsyncApiId and awsAppsyncRegion values highlighted within the outputs of the CDK stack. + +```react showLineNumbers={false} +npx @aws-amplify/cli codegen add --apiId --region +``` + +**Step 5:** Accept the default values for the prompts. + +```console title="Terminal" showLineNumbers={false} +? Choose the type of app that you're building javascript +? What javascript framework are you using react +✔ Getting API details +? Choose the code generation language target javascript +? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js +? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes +? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 +✔ Downloaded the schema +✔ Generated GraphQL operations successfully and saved at src/graphql +``` + +The Amplify CLI will create the GraphQL client helper code inside the `src/graphql` folder. + +mutations.js, queries.js, and subscriptions.js within the graphql folder. + +**Step 6:** Update the `App.js` file with the following code to create a form with a button to create to-dos, as well as a way to fetch and render the to-do list. + +```jsx title="src/App.js" +import { Amplify} from 'aws-amplify' +import '@aws-amplify/ui-react/styles.css'; +import { useEffect, useState } from 'react'; +import { generateClient } from 'aws-amplify/api'; +import { createTodo } from './graphql/mutations'; +import { listTodos } from './graphql/queries'; + +Amplify.configure({ + API: { + GraphQL: { + // highlight-start + endpoint: '', + region: '', + defaultAuthMode: 'apiKey', + apiKey: '' + // highlight-end + } + } +}); + +const initialState = { name: '', description: '' }; +const client = generateClient(); + +const App = () => { + const [formState, setFormState] = useState(initialState); + const [todos, setTodos] = useState([]); + + useEffect(() => { + fetchTodos(); + }, []); + + function setInput(key, value) { + setFormState({ ...formState, [key]: value }); + } + + async function fetchTodos() { + try { + const todoData = await client.graphql({ + query: listTodos + }); + const todos = todoData.data.listTodos.items; + setTodos(todos); + } catch (err) { + console.log('error fetching todos'); + } + } + + async function addTodo() { + try { + if (!formState.name || !formState.description) return; + const todo = { ...formState }; + setTodos([...todos, todo]); + setFormState(initialState); + await client.graphql({ + query: createTodo, + variables: { + input: todo + } + }); + } catch (err) { + console.log('error creating todo:', err); + } + } + + return ( +
+

Amplify Todos

+ setInput('name', event.target.value)} + style={styles.input} + value={formState.name} + placeholder="Name" + /> + setInput('description', event.target.value)} + style={styles.input} + value={formState.description} + placeholder="Description" + /> + + {todos.map((todo, index) => ( +
+

{todo.name}

+

{todo.description}

+
+ ))} +
+ ); +}; + +const styles = { + container: { + width: 400, + margin: '0 auto', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + padding: 20 + }, + todo: { marginBottom: 15 }, + input: { + border: 'none', + backgroundColor: '#ddd', + marginBottom: 10, + padding: 8, + fontSize: 18 + }, + todoName: { fontSize: 20, fontWeight: 'bold' }, + todoDescription: { marginBottom: 0 }, + button: { + backgroundColor: 'black', + color: 'white', + outline: 'none', + fontSize: 18, + padding: '12px 0px' + } +}; + +export default App; +``` + + +**Step 7:** Run the app using the following command. + +```bash title="Terminal" showLineNumbers={false} +npm start +``` + +**Step 8:** Use the form to create a few to-do items. + + + +In this section, we generated GraphQL code, created React components, configured Amplify, and connected the app to the API. This enabled full CRUD functionality with our backend through queries and mutations. + +
+ + + +## Build a Flutter app and connect to the GraphQL API + +In this section, we will connect a Flutter mobile app to the GraphQL API we created with the CDK. First, we will initialize a Flutter project, define models matching our schema, and use Amplify to integrate CRUD operations. Then we will add UI pages to manage to-do items with queries and mutations. Finally, we will configure Amplify with our backend details and run the app to demonstrate full functionality with our existing API. + +**Step 1:** Create a Flutter app by running the following command in your terminal. + +```bash title="Terminal" showLineNumbers={false} +flutter create flutter_todo_app --platforms=web +``` + +**Step 2:** Open the newly created Flutter app by running the following commands in your terminal. + +```bash title="Terminal" +cd flutter_todo_app +code . -r +``` + +**Step 3**: Update the `pubspec.yaml` file in the app’s root directory to add the required dependencies, as shown in the following code. + +{/* cSpell:disable */} +```yaml title="pubspec.yaml" +name: flutter_todo_app +description: "A new Flutter project." +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: '>=3.2.0 <4.0.0' + +// highlight-start +dependencies: + flutter: + sdk: flutter + amplify_flutter: ^1.0.0 + amplify_api: ^1.0.0 + go_router: ^6.5.5 + cupertino_icons: ^1.0.2 +// highlight-end + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true +``` +{/* cSpell:enable */} + +**Step 4:** Run the following command in your terminal to install the dependencies you added to the `pubspec.yaml` file. + +```bash title="Terminal" showLineNumbers={false} +flutter pub get +``` + +**Step 5:** Install the @aws-amplify/cli packages by running the following command. + +```bash title="Terminal" showLineNumbers={false} +npm install @aws-amplify/cli +``` + +**Step 6:** Create a new folder and name it `graphql`. Inside it, create the file `schema.graphql`. + +The schema.graphql file inside the graphql folder. + +**Step 7:** Update the file `schema.graphql`, as shown in the following example, to define the Todo data model, similar to what you used for the CDK app. + +```graphql title="schema.graphql" +type Todo @model @auth(rules: [{ allow: public }]) { + id: ID! + name: String! + description: String + complete: Boolean +} +``` + +**Step 8:** Run the following command to generate the GraphQL client helper models inside the `lib/models` folder. + +```bash showLineNumbers={false} +npx @aws-amplify/cli codegen models --model-schema ./graphql --target flutter --output-dir ./lib/models +``` + +The ModelProvider.dart and Todo.dart files within the models folder. + +**Step 9:** Create the file `todo_item_page.dart` inside the `lib` folder and update it with the following code to present a form to the user for creating a to-do item. Once submitted, the form will initiate a GraphQL mutation to add or modify the item in the database. + +```dart title="todo_item_page.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../models/ModelProvider.dart'; + +class ToDoItemPage extends StatefulWidget { + const ToDoItemPage({ + required this.todoItem, + super.key, + }); + + final Todo? todoItem; + + @override + State createState() => _ToDoItemPageState(); +} + +class _ToDoItemPageState extends State { + final _formKey = GlobalKey(); + final TextEditingController _nameController = TextEditingController(); + final TextEditingController _descriptionController = TextEditingController(); + + late final String _nameText; + late bool _isDone; + + bool get _isCreate => _todoItem == null; + Todo? get _todoItem => widget.todoItem; + + @override + void initState() { + super.initState(); + + final todoItem = _todoItem; + if (todoItem != null) { + _nameController.text = todoItem.name; + _descriptionController.text = todoItem.description ?? ''; + + _nameText = 'Update to-do Item'; + _isDone = todoItem.complete ?? false; + } else { + _nameText = 'Create to-do Item'; + _isDone = false; + } + } + + @override + void dispose() { + _nameController.dispose(); + _descriptionController.dispose(); + + super.dispose(); + } + + Future submitForm() async { + if (!_formKey.currentState!.validate()) { + return; + } + + // If the form is valid, submit the data + final name = _nameController.text; + final description = _descriptionController.text; + final complete = _isDone; + + // highlight-start + if (_isCreate) { + // Create a new todo item + final newEntry = Todo( + name: name, + description: description.isNotEmpty ? description : null, + complete: complete, + ); + final request = ModelMutations.create(newEntry); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Create result: $response'); + } else { + // Update todoItem instead + final updateToDoItem = _todoItem!.copyWith( + name: name, + description: description.isNotEmpty ? description : null, + complete: complete, + ); + final request = ModelMutations.update(updateToDoItem); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Update result: $response'); + } + // highlight-end + + // Navigate back to homepage after create/update executes + if (mounted) { + context.pop(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_nameText), + ), + body: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _nameController, + decoration: const InputDecoration( + labelText: 'Name (required)', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a name'; + } + return null; + }, + ), + TextFormField( + controller: _descriptionController, + decoration: const InputDecoration( + labelText: 'Description', + ), + ), + SwitchListTile( + title: const Text('Done'), + value: _isDone, + onChanged: (bool value) { + setState(() { + _isDone = value; + }); + }, + secondary: const Icon(Icons.done_all_outlined), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: submitForm, + child: Text(_nameText), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} +``` + +**Step 10:** Create the file `home_page.dart.dart` inside the `lib` folder and update it with the following code. This page will use a GraphQL query to retrieve the list of to-do items and display them in a ListView widget. The page will also allow the user to delete a to-do item by using a GraphQL mutation. + +```dart title="home_page.dart.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../models/ModelProvider.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + var _todoItems = []; + + @override + void initState() { + super.initState(); + _refreshTodoItems(); + } + + // highlight-start + Future _refreshTodoItems() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (response.hasErrors) { + safePrint('errors: ${response.errors}'); + return; + } + setState(() { + _todoItems = todos!.whereType().toList(); + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + } + } + + Future _deleteToDoItem(Todo todoItem) async { + final request = ModelMutations.delete(todoItem); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Delete response: $response'); + await _refreshTodoItems(); + } + // highlight-end + + Future _openToDoItem({Todo? todoItem}) async { + await context.pushNamed('manage', extra: todoItem); + // Refresh the entries when returning from the + // todo item screen. + await _refreshTodoItems(); + } + + Widget _buildRow({ + required String name, + required String description, + required bool isDone, + TextStyle? style, + }) { + return Row( + children: [ + Expanded( + child: Text( + name, + textAlign: TextAlign.center, + style: style, + ), + ), + Expanded( + child: Text( + description, + textAlign: TextAlign.center, + style: style, + ), + ), + Expanded( + child: isDone ? const Icon(Icons.done) : const SizedBox(), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton( + // Navigate to the page to create new todo item + onPressed: _openToDoItem, + child: const Icon(Icons.add), + ), + appBar: AppBar( + title: const Text('To-Do List'), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.only(top: 25), + child: RefreshIndicator( + onRefresh: _refreshTodoItems, + child: Column( + children: [ + if (_todoItems.isEmpty) + const Text('Use the \u002b sign to add new to-do items') + else + const SizedBox(height: 30), + _buildRow( + name: 'Name', + description: 'Description', + isDone: false, + style: Theme.of(context).textTheme.titleMedium, + ), + const Divider(), + Expanded( + child: ListView.builder( + itemCount: _todoItems.length, + itemBuilder: (context, index) { + final todoItem = _todoItems[index]; + return Dismissible( + key: ValueKey(todoItem), + background: const ColoredBox( + color: Colors.red, + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Align( + alignment: Alignment.centerRight, + child: Icon(Icons.delete, color: Colors.white), + ), + ), + ), + onDismissed: (_) => _deleteToDoItem(todoItem), + child: ListTile( + onTap: () => _openToDoItem( + todoItem: todoItem, + ), + title: _buildRow( + name: todoItem.name, + description: todoItem.description ?? '', + isDone: todoItem.complete ?? false, + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} +``` + +**Step 11:** Update `main.dart` to configure Amplify using the details of the GraphQL API you created using the CDK app in the previous section. + +```dart title="main.dart" +import 'package:amplify_api/amplify_api.dart'; + +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'models/ModelProvider.dart'; +import 'home_page.dart'; +import 'todo_item_page.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await _configureAmplify(); + runApp(const MyApp()); +} + +Future _configureAmplify() async { + try { + final api = AmplifyAPI(modelProvider: ModelProvider.instance); + + await Amplify.addPlugins([api]); + const amplifyconfig = '''{ + "api": { + "plugins": { + "awsAPIPlugin": { + "flutter_todo_app": { + "endpointType": "GraphQL", + // highlight-start + "endpoint": "", + "region": "", + "authorizationType": "API_KEY", + "apiKey": "" + // highlight-end + } + } + } + } +}'''; + + await Amplify.configure(amplifyconfig); + + safePrint('Successfully configured'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // GoRouter configuration + static final _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomePage(), + ), + GoRoute( + path: '/manage-todo-item', + name: 'manage', + builder: (context, state) => ToDoItemPage( + todoItem: state.extra as Todo?, + ), + ), + ], + ); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: _router, + debugShowCheckedModeBanner: false, + builder: (context, child) { + return child!; + }, + ); + } +} +``` + +**Step 12:** Run the app in the Chrome browser using the following command. + +```bash title="Terminal" showLineNumbers={false} +flutter run -d chrome +``` + + + + + +## Conclusion + +Congratulations! You used the AWS Amplify Data CDK construct to create a GraphQL API backend using AWS AppSync. You then connected your app to that API using the Amplify libraries. If you have any feedback, leave a [GitHub issue](https://github.com/aws-amplify/docs/issues) or join our [Discord Community](https://discord.gg/amplify)! + +## Clean up resources + +Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the CDK app created above. + +```bash title="Terminal" showLineNumbers={false} + cdk destroy +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx new file mode 100644 index 00000000000..76b23ac7ce8 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx @@ -0,0 +1,1016 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Connect to existing AWS resources with Amplify CLI', + description: "Use the Amplify CLI to connect existing AWS resources to a new app.", + platforms: [ + 'flutter', + ] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + +The AWS Amplify CLI (Command Line Interface) CLI provides a simple workflow for provisioning cloud resources like authentication, databases, and storage for apps through the command line. + + +In this guide, you will learn how to connect a new Flutter mobile app to backend resources you've already created using the Amplify CLI. + + + +Connecting a web app? We also offer a version of this guide for integrating existing backends with React using the Amplify CLI. Check out the [React guide](/react/build-a-backend/existing-resources/cli/). + + + + + +In this guide, you will learn how to connect a new React web app to backend resources you've already created using the Amplify CLI. + + + +Connecting a mobile app? We also offer a version of this guide for integrating existing backends with Flutter using the Amplify CLI. Check out the [Flutter guide](/flutter/build-a-backend/existing-resources/cli/). + + + + + +## Connect mobile app to existing AWS resources + +This guide will walk you through connecting a new Flutter app to AWS resources created with Amplify for an existing Flutter app. If you don't already have an existing app, you can follow this [Flutter tutorial](https://docs.amplify.aws/start/getting-started/setup/q/integration/flutter/) to create a budget tracker app that uses Amplify Auth and API resources. + +Before you begin, you will need: + +* An existing Flutter app +* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. +* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. +* Flutter [installed](https://docs.flutter.dev/get-started/install) and configured. +* A text editor combined with Flutter’s command-line tools. For this guide, we will use VS Code, but you can use your preferred IDE. + +### Find the AWS backend details +Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app. + +**Step 1:** In your existing app, open the file `/amplify/team-provider-info.json`. + +The team-provider-info.json file within the file directory of the Amplify app. + +**Step 2:** In the `team-provider-info.json` file, note the following: + +1. The environment you want to use +2. The `AmplifyAppId` for the required environment + +{/* cSpell:disable */} +The environment and AmplifyAppId in team-provider-info.json file. +{/* cSpell:enable */} + +### Create the Flutter app +Now that we have gathered the necessary backend details, we can start building out the new Flutter app. + +**Step 1:** Create a Flutter app by running the following command in your terminal. + +```bash showLineNumbers={false} + +flutter create amplify_connect_resources + +``` + + + +**Step 2:** Open the newly created Flutter app using VS Code by running the following commands in your terminal. + +``` +cd amplify_connect_resources +code . -r +``` + +![Open the created app using VS Code.](/images/existing-resources/app-vscode-mobile-cli.png) + + +**Step 3:** Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal. + +```bash showLineNumbers={false} +amplify pull --appId --envName +``` + + +**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. + +Accept the default values for the prompts about the default editor, type of app, and storage location of the configuration file. Then answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. + +{/* cSpell:disable */} +``` +? Select the authentication method you want to use: AWS profile + +For more information on AWS Profiles, see: +https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html + +? Please choose the profile you want to use AwsWest1 +Amplify AppID found: dorapq24trw9r. Amplify App name is:amplifyBudget +Backend environment dev found in Amplify Console app: amplifyBudget +? Choose your default editor: Visual Studio Code +✔ Choose the type of app that you're building · flutter +Please tell us about your project +? Where do you want to store your configuration file? ./lib/ +? Do you plan on modifying this backend? Yes +⠦ Fetching updates to backend environment: dev from the cloud.⠋ Building resource api/amplifyBudget +⠹ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully. + +Edit your schema at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema +✔ Successfully pulled backend environment dev from the cloud. +✅ + +✅ Successfully pulled backend environment dev from the cloud. +Run 'amplify pull' to sync future upstream changes. +``` +{/* cSpell:enable */} + +The Amplify CLI will add a new folder named `amplify` to the app's root folder, which contains the Amplify project and backend details. It will also add a new Dart file, `amplifyconfiguration.dart`, to the `lib/` folder. The app will use this file at runtime to locate and connect to the backend resources you have provisioned. + +The Amplify folder and amplifyconfiguration.dart file within the file directory of the Amplify app. + +**Step 5:** Update the file `pubspec.yaml` in the app root directory to add the required packages. In this example, we will use the same packages as the app created in this guide. To do this, update the `pubspec.yaml` as shown in the following. + +{/* cSpell:disable */} +```yaml title="pubspec.yaml" +name: budget_tracker +description: A new Flutter project. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: '>=3.1.0 <4.0.0' +dependencies: +// highlight-start + amplify_api: ^1.0.0 + amplify_auth_cognito: ^1.0.0 + amplify_authenticator: ^1.0.0 + amplify_flutter: ^1.0.0 +// highlight-end + flutter: + sdk: flutter +// highlight-next-line + go_router: ^6.5.5 + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true + +``` +{/* cSpell:enable */} + + +**Step 6**: To enable type-safe interaction with the GraphQL schema, use this command to generate the required Dart files. + +```bash showLineNumbers={false} + +amplify codegen models + +``` +The Amplify CLI will generate the Dart files in the `lib/models` folder. + +The models folder within the file directory of the Amplify app. + +**Step 7:** Update the `main.dart` file with the following code to introduce the Amplify Authenticator and integrate Amplify API with your app to create, update, query, and delete `BudgetEntry` items. Typically, you would break this file up into smaller modules but we've kept it as a single file for this guide. + +```bash title="main.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_authenticator/amplify_authenticator.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'amplifyconfiguration.dart'; +import 'models/ModelProvider.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await _configureAmplify(); + runApp(const MyApp()); +} + +// highlight-start +Future _configureAmplify() async { + + try { + + final api = AmplifyAPI(modelProvider: ModelProvider.instance); + final auth = AmplifyAuthCognito(); + await Amplify.addPlugins([api, auth]); + await Amplify.configure(amplifyconfig); + + safePrint('Successfully configured'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } +} +// highlight-end + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // GoRouter configuration + static final _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomeScreen(), + ), + GoRoute( + path: '/manage-budget-entry', + name: 'manage', + builder: (context, state) => ManageBudgetEntryScreen( + budgetEntry: state.extra as BudgetEntry?, + ), + ), + ], + ); + + @override + Widget build(BuildContext context) { + return Authenticator( + child: MaterialApp.router( + routerConfig: _router, + debugShowCheckedModeBanner: false, + // highlight-next-line + builder: Authenticator.builder(), + ), + ); + } +} + +class LoadingScreen extends StatelessWidget { + const LoadingScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } +} + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + var _budgetEntries = []; + + @override + void initState() { + super.initState(); + _refreshBudgetEntries(); + } + + Future _refreshBudgetEntries() async { + try { + final request = ModelQueries.list(BudgetEntry.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (response.hasErrors) { + safePrint('errors: ${response.errors}'); + return; + } + setState(() { + _budgetEntries = todos!.whereType().toList(); + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + } + } + + Future _deleteBudgetEntry(BudgetEntry budgetEntry) async { + final request = ModelMutations.delete(budgetEntry); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Delete response: $response'); + await _refreshBudgetEntries(); + } + + Future _navigateToBudgetEntry({BudgetEntry? budgetEntry}) async { + await context.pushNamed('manage', extra: budgetEntry); + // Refresh the entries when returning from the + // budget entry screen. + await _refreshBudgetEntries(); + } + + double _calculateTotalBudget(List items) { + var totalAmount = 0.0; + for (final item in items) { + totalAmount += item?.amount ?? 0; + } + return totalAmount; + } + + Widget _buildRow({ + required String title, + required String description, + required String amount, + TextStyle? style, + }) { + return Row( + children: [ + Expanded( + child: Text( + title, + textAlign: TextAlign.center, + style: style, + ), + ), + Expanded( + child: Text( + description, + textAlign: TextAlign.center, + style: style, + ), + ), + Expanded( + child: Text( + amount, + textAlign: TextAlign.center, + style: style, + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton( + // Navigate to the page to create new budget entries + onPressed: _navigateToBudgetEntry, + child: const Icon(Icons.add), + ), + appBar: AppBar( + title: const Text('Budget Tracker'), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.only(top: 25), + child: RefreshIndicator( + onRefresh: _refreshBudgetEntries, + child: Column( + children: [ + if (_budgetEntries.isEmpty) + const Text('Use the \u002b sign to add new budget entries') + else + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Show total budget from the list of all BudgetEntries + Text( + 'Total Budget: \$ ${_calculateTotalBudget(_budgetEntries).toStringAsFixed(2)}', + style: const TextStyle(fontSize: 24), + ) + ], + ), + const SizedBox(height: 30), + _buildRow( + title: 'Title', + description: 'Description', + amount: 'Amount', + style: Theme.of(context).textTheme.titleMedium, + ), + const Divider(), + Expanded( + child: ListView.builder( + itemCount: _budgetEntries.length, + itemBuilder: (context, index) { + final budgetEntry = _budgetEntries[index]; + return Dismissible( + key: ValueKey(budgetEntry), + background: const ColoredBox( + color: Colors.red, + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Align( + alignment: Alignment.centerRight, + child: Icon(Icons.delete, color: Colors.white), + ), + ), + ), + onDismissed: (_) => _deleteBudgetEntry(budgetEntry), + child: ListTile( + onTap: () => _navigateToBudgetEntry( + budgetEntry: budgetEntry, + ), + title: _buildRow( + title: budgetEntry.title, + description: budgetEntry.description ?? '', + amount: + '\$ ${budgetEntry.amount.toStringAsFixed(2)}', + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class ManageBudgetEntryScreen extends StatefulWidget { + const ManageBudgetEntryScreen({ + required this.budgetEntry, + super.key, + }); + + final BudgetEntry? budgetEntry; + + @override + State createState() => + _ManageBudgetEntryScreenState(); +} + +class _ManageBudgetEntryScreenState extends State { + final _formKey = GlobalKey(); + final TextEditingController _titleController = TextEditingController(); + final TextEditingController _descriptionController = TextEditingController(); + final TextEditingController _amountController = TextEditingController(); + + late final String _titleText; + + bool get _isCreate => _budgetEntry == null; + BudgetEntry? get _budgetEntry => widget.budgetEntry; + + @override + void initState() { + super.initState(); + + final budgetEntry = _budgetEntry; + if (budgetEntry != null) { + _titleController.text = budgetEntry.title; + _descriptionController.text = budgetEntry.description ?? ''; + _amountController.text = budgetEntry.amount.toStringAsFixed(2); + _titleText = 'Update budget entry'; + } else { + _titleText = 'Create budget entry'; + } + } + + @override + void dispose() { + _titleController.dispose(); + _descriptionController.dispose(); + _amountController.dispose(); + super.dispose(); + } + + Future submitForm() async { + if (!_formKey.currentState!.validate()) { + return; + } + + // If the form is valid, submit the data + final title = _titleController.text; + final description = _descriptionController.text; + final amount = double.parse(_amountController.text); + + if (_isCreate) { + // Create a new budget entry + final newEntry = BudgetEntry( + title: title, + description: description.isNotEmpty ? description : null, + amount: amount, + ); + final request = ModelMutations.create(newEntry); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Create result: $response'); + } else { + // Update budgetEntry instead + final updateBudgetEntry = _budgetEntry!.copyWith( + title: title, + description: description.isNotEmpty ? description : null, + amount: amount, + ); + final request = ModelMutations.update(updateBudgetEntry); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Update result: $response'); + } + + // Navigate back to homepage after create/update executes + if (mounted) { + context.pop(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_titleText), + ), + body: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _titleController, + decoration: const InputDecoration( + labelText: 'Title (required)', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a title'; + } + return null; + }, + ), + TextFormField( + controller: _descriptionController, + decoration: const InputDecoration( + labelText: 'Description', + ), + ), + TextFormField( + controller: _amountController, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + decoration: const InputDecoration( + labelText: 'Amount (required)', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an amount'; + } + final amount = double.tryParse(value); + if (amount == null || amount <= 0) { + return 'Please enter a valid amount'; + } + return null; + }, + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: submitForm, + child: Text(_titleText), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} + +``` + + +**Step 8:** Run the app using the following command, and use the authentication flow to create a user. Then create a few budget items. + + +```bash showLineNumbers={false} + +flutter run + + +``` + + + + + +### Conclusion + +Congratulations! Your new Flutter app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management and a scalable GraphQL API backed by Amazon DynamoDB. + + +### Clean up resources + +Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app. + +```bash showLineNumbers={false} +amplify delete + +``` + +If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the [Build & connect backend section](/[platform]/build-a-backend/) for guides on how to add and connect other backend resources. + + + +## Connect web app to existing AWS resources + +This guide will walk you through connecting a new React web app to the AWS resources created with Amplify for an existing React app. If you don't already have an existing app, you can follow this [React tutorial](https://docs.amplify.aws/react/start/getting-started/introduction/) to create a to-do app that uses Amplify Auth, API, and Hosting resources. + +Before you begin, you will need: + +* An existing React app +* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. +* The Amplify CLI [installed](https://docs.amplify.aws/cli/start/install/) and configured. +* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. + +### Find the AWS backend details +Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app. + +**Step 1:** In your existing app, open the file `/amplify/team-provider-info.json` . + +The team-provider-info.json file within the file directory of the Amplify app. + +**Step 2:** In the `team-provider-info.json` file, note the following: + +1. The environment you want to use +2. The `AmplifyAppId` for the required environment + +{/* cSpell:disable */} +The environment and AmplifyAppId in team-provider-info.json file. +{/* cSpell:enable */} + +### Create the React app +Now that we have gathered the necessary backend details, we can start building out the new React app. + +**Step 1:** Create a React app by running the following command in your terminal. + +```bash showLineNumbers={false} +npx create-react-app react-amplify-connect +``` + +**Step 2:** Open the newly created React app using VS Code by running the following commands in your terminal. + +``` +cd react-amplify-connect +code . -r +``` + +![Open the created app using VS Code.](/images/existing-resources/app-vscode-web-cli.png) + +**Step 3:** Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal. + +```bash showLineNumbers={false} +amplify pull --appId --envName +``` + +**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. + +Accept the default values for the prompts and make sure to answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. + +{/* cSpell:disable */} +``` +? Select the authentication method you want to use: AWS profile + +For more information on AWS Profiles, see: +https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html + +? Please choose the profile you want to use AwsWest1 +Amplify AppID found: dfn3u8j1nvzjc. Amplify App name is: reactamplified +Backend environment dev found in Amplify Console app: reactamplified +? Choose your default editor: Visual Studio Code +✔ Choose the type of app that you're building · javascript +Please tell us about your project +? What javascript framework are you using react +? Source Directory Path: src +? Distribution Directory Path: build +? Build Command: npm run-script build +? Start Command: npm run-script start +? Do you plan on modifying this backend? Yes +⠋ Fetching updates to backend environment: dev from the cloud. +⚠️ WARNING: your GraphQL API currently allows public create, read, update, and delete access to all models via an API Key. To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql/authorization-rules + +⠇ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully. + +Edit your schema at /Users/malakamm/development/react-amplify-connect/amplify/backend/api/reactamplified/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/react-amplify-connect/amplify/backend/api/reactamplified/schema +✔ Successfully pulled backend environment dev from the cloud. +Browserslist: caniuse-lite is outdated. Please run: + npx update-browserslist-db@latest + Why you should do it regularly: https://github.com/browserslist/update-db#readme +✅ + +✅ Successfully pulled backend environment dev from the cloud. +Run 'amplify pull' to sync future upstream changes. +``` +{/* cSpell:enable */} + +The Amplify CLI will add a new folder named `amplify` to the app's root folder, which contains the Amplify project and backend details. + +The amplify folder within the file directory of the Amplify app. + +**Step 5**: Use the following command to generate the GraphQL statements. + +```bash showLineNumbers={false} +amplify codegen add +``` + +**Step 6:** Accept the default values of the prompts. + +``` + +? Choose the code generation language target javascript +? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js +? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes +? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 +✔ Generated GraphQL operations successfully and saved at src/graphql + +``` + +The CLI will add a new folder named `graphql` to the app's root folder, which contains the GraphQL statements. + +The graphql folder within the file directory of the Amplify app. + +**Step 7:** Install the aws-amplify and @aws-amplify/ui-react packages by running the following commands. + +```bash showLineNumbers={false} +npm install aws-amplify @aws-amplify/ui-react +``` + +**Step 8:** Update the `App.js` file with the following code to create a login flow using Amplify UI and a form with a button to create to-dos, as well as a way to fetch and render the list of to-dos. + +```bash title="src/App.js" +import React, { useEffect, useState } from 'react' +import { Amplify, API, graphqlOperation } from 'aws-amplify' +import { createTodo } from './graphql/mutations' +import { listTodos } from './graphql/queries' +import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; + +// highlight-start +import awsExports from "./aws-exports"; +Amplify.configure(awsExports); +// highlight-end + +const initialState = { name: '', description: '' } + +const App = ({ signOut, user }) => { + const [formState, setFormState] = useState(initialState) + const [todos, setTodos] = useState([]) + + useEffect(() => { + fetchTodos() + }, []) + + function setInput(key, value) { + setFormState({ ...formState, [key]: value }) + } + + // highlight-start + async function fetchTodos() { + try { + const todoData = await API.graphql(graphqlOperation(listTodos)) + const todos = todoData.data.listTodos.items + setTodos(todos) + } catch (err) { console.log('error fetching todos') } + } + + async function addTodo() { + try { + if (!formState.name || !formState.description) return + const todo = { ...formState } + setTodos([...todos, todo]) + setFormState(initialState) + await API.graphql(graphqlOperation(createTodo, {input: todo})) + } catch (err) { + console.log('error creating todo:', err) + } + } + // highlight-end + + return ( +
+ Hello {user.username} + +

Amplify Todos

+ setInput('name', event.target.value)} + style={styles.input} + value={formState.name} + placeholder="Name" + /> + setInput('description', event.target.value)} + style={styles.input} + value={formState.description} + placeholder="Description" + /> + + { + todos.map((todo, index) => ( +
+

{todo.name}

+

{todo.description}

+
+ )) + } +
+ ) +} + +const styles = { + container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 }, + todo: { marginBottom: 15 }, + input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 }, + todoName: { fontSize: 20, fontWeight: 'bold' }, + todoDescription: { marginBottom: 0 }, + button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' } +} + +export default withAuthenticator(App); + + +``` + + +**Step 9:** Run the app using the following command. + +```bash showLineNumbers={false} +npm start +``` + +**Step 10:** Change the UI of the app as shown in the following to update the placeholder text of the to-do form and to use the user’s email for the hello message. + +```bash title="src/App.js" +... + return ( +
+ // highlight-next-line + Hello {*user*.attributes.email} + +

Amplify Todos

+ setInput('name', event.target.value)} + style={styles.input} + value={formState.name} + // highlight-next-line + placeholder="ToDo Name" + /> + setInput('description', event.target.value)} + style={styles.input} + value={formState.description} + // highlight-next-line + placeholder="ToDo Description" + /> + + { + todos.map((todo, index) => ( +
+

{todo.name}

+

{todo.description}

+
+ )) + } +
+ ) +.... +``` + +The `App.js` file should now look like the following code snippet. + +```bash title="src/App.js" +import React, { useEffect, useState } from 'react' +import { Amplify, API, graphqlOperation } from 'aws-amplify' +import { createTodo } from './graphql/mutations' +import { listTodos } from './graphql/queries' +import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; + +import awsExports from "./aws-exports"; +Amplify.configure(awsExports); + +const initialState = { name: '', description: '' } + +const App = ({ signOut, user }) => { + const [formState, setFormState] = useState(initialState) + const [todos, setTodos] = useState([]) + + useEffect(() => { + fetchTodos() + }, []) + + function setInput(key, value) { + setFormState({ ...formState, [key]: value }) + } + + async function fetchTodos() { + try { + const todoData = await API.graphql(graphqlOperation(listTodos)) + const todos = todoData.data.listTodos.items + setTodos(todos) + } catch (err) { console.log('error fetching todos') } + } + + async function addTodo() { + try { + if (!formState.name || !formState.description) return + const todo = { ...formState } + setTodos([...todos, todo]) + setFormState(initialState) + await API.graphql(graphqlOperation(createTodo, {input: todo})) + } catch (err) { + console.log('error creating todo:', err) + } + } + + return ( +
+ Hello {user.attributes.email} + +

Amplify Todos

+ setInput('name', event.target.value)} + style={styles.input} + value={formState.name} + placeholder="ToDo Name" + /> + setInput('description', event.target.value)} + style={styles.input} + value={formState.description} + placeholder="ToDo Description" + /> + + { + todos.map((todo, index) => ( +
+

{todo.name}

+

{todo.description}

+
+ )) + } +
+ ) +} + +const styles = { + container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 }, + todo: { marginBottom: 15 }, + input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 }, + todoName: { fontSize: 20, fontWeight: 'bold' }, + todoDescription: { marginBottom: 0 }, + button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' } +} + +export default withAuthenticator(App); + +``` + +**Step 11:** Use the following command to publish your changes. Amplify CLI will publish the changes and display the app URL. + +```bash showLineNumbers={false} +amplify publish +``` + +Amplify CLI publish the app and display a URL. + +**Step 12:** Use the URL to run the app in the browser. + + + +### Conclusion + +Congratulations! Your new React app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management, a scalable GraphQL API backed by Amazon DynamoDB, and a hosting service for publishing your app to the cloud. + +### Clean up resources + +Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app. + +```bash showLineNumbers={false} +amplify delete +``` + +If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the [Build & connect backend section](/[platform]/build-a-backend/) for guides on how to add and connect other backend resources. +
diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx new file mode 100644 index 00000000000..713948a4b91 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx @@ -0,0 +1,33 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; +import { getChildPageNodes } from '@/utils/getChildPageNodes'; + +export const meta = { + title: 'Existing AWS resources', + description: + 'Use the Amplify CLI or AWS CDK to connect to existing AWS resources.', + platforms: [ + 'flutter', + ], + route: '/[platform]/prev/build-a-backend/existing-resources' +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + const childPageNodes = getChildPageNodes(meta.route); + return { + props: { + platform: context.params.platform, + meta, + childPageNodes + } + }; +} + +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + + From 7b7db83258cc39625c68764159344a6eb2104d1e Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 13:15:41 -0700 Subject: [PATCH 31/88] chore: remove duplicated paragraph in auth section and updated callout on remember a device --- .../lib-v1/auth/common/device_features/common.mdx | 8 ++++++++ .../auth/native_common/access_credentials/common.mdx | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/fragments/lib-v1/auth/common/device_features/common.mdx b/src/fragments/lib-v1/auth/common/device_features/common.mdx index efcd509ceb0..5a7499ab28b 100644 --- a/src/fragments/lib-v1/auth/common/device_features/common.mdx +++ b/src/fragments/lib-v1/auth/common/device_features/common.mdx @@ -1,13 +1,21 @@ + + +The [device tracking and remembering](https://aws.amazon.com/blogs/mobile/tracking-and-remembering-devices-using-amazon-cognito-your-user-pools/) features are currently not available within the library when using the federated OAuth flow with Cognito User Pools or Hosted UI. + + + Remembering a device is useful in conjunction with Multi-Factor Authentication (MFA). If MFA is enabled for an Amazon Cognito user pool, end users have to type in a security code received via e-mail or SMS each time they want to sign in. This increases security but comes at the expense of the user's experience. Remembering a device allows the second factor requirement to be automatically met when the user signs in on that device, thereby reducing friction in the user experience. ## Configure Auth Category + Device remembering functionality does not work if you use one of the web UI sign in methods. + To enable remembered device functionality, open the Cognito User Pool console. To do this, **go to your project directory** and **issue the command**: diff --git a/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx b/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx index dd5f85a26df..8182803b1e9 100644 --- a/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx @@ -2,7 +2,9 @@ An intentional decision with Amplify Auth was to avoid any public methods exposi With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. + However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by casting the result of fetchAuthSession as follows: + import android0 from '/src/fragments/lib-v1/auth/android/access_credentials/10_fetchAuthSession.mdx'; From e271b646e89bae9a81f1c7a9006a11b39222c851 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 13:33:01 -0700 Subject: [PATCH 32/88] chore: fix code snippet in graphql authorization modes section --- .../graphqlapi/customize-authorization-modes/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx index 848c216c447..da491867f15 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx @@ -48,6 +48,6 @@ import reactnative0 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; -import flutter3 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; +import flutter3 from '/src/fragments/lib-v1/graphqlapi/native_common/authz/common.mdx'; From ecf201eda609a1af67955c291ade6857df8577de Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 13:47:59 -0700 Subject: [PATCH 33/88] chore: fix code snippets in rest api section --- .../graphqlapi/flutter/authz/20_oidc.mdx | 2 +- .../lib-v1/restapi/flutter/delete.mdx | 18 ++++++++---------- src/fragments/lib-v1/restapi/flutter/fetch.mdx | 17 +++++++---------- .../flutter/getting-started/20_installLib.mdx | 8 ++++---- .../flutter/getting-started/40_postTodo.mdx | 13 ++++--------- .../lib-v1/restapi/flutter/update.mdx | 11 +++++------ 6 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx index 3e8bd488f27..27eeea6dd7f 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx @@ -11,6 +11,6 @@ class CustomOIDCProvider extends OIDCAuthProvider { } ``` -import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; +import warning from "/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx"; diff --git a/src/fragments/lib-v1/restapi/flutter/delete.mdx b/src/fragments/lib-v1/restapi/flutter/delete.mdx index 4876e72f0d2..cb77bc8e9c1 100644 --- a/src/fragments/lib-v1/restapi/flutter/delete.mdx +++ b/src/fragments/lib-v1/restapi/flutter/delete.mdx @@ -1,17 +1,15 @@ ## DELETE requests ```dart - Future deleteTodo() async { - try { - const options = RestOptions(path: '/todo/1'); - final restOperation = Amplify.API.delete(restOptions: options); - final response = await restOperation.response; - print('DELETE call succeeded'); - print(response.body); - } on ApiException catch (e) { - print('DELETE call failed: $e'); - } +Future deleteTodo() async { + try { + final restOperation = Amplify.API.delete('todo/1'); + final response = await restOperation.response; + print('DELETE call succeeded: ${response.decodeBody()}'); + } on ApiException catch (e) { + print('DELETE call failed: $e'); } +} ``` diff --git a/src/fragments/lib-v1/restapi/flutter/fetch.mdx b/src/fragments/lib-v1/restapi/flutter/fetch.mdx index e694249c2cd..4e6a7b3e073 100644 --- a/src/fragments/lib-v1/restapi/flutter/fetch.mdx +++ b/src/fragments/lib-v1/restapi/flutter/fetch.mdx @@ -5,12 +5,11 @@ To make a GET request use `Amplify.API.get`: ```dart Future getTodo() async { try { - const options = RestOptions(path: '/todo'); - final restOperation = Amplify.API.get(restOptions: options); + final restOperation = Amplify.API.get('todo'); final response = await restOperation.response; - print('GET call succeeded: ${response.body}'); + print('GET call succeeded: ${response.decodeBody()}'); } on ApiException catch (e) { - print('GET call failed: $e'); + print('GET call failed: $e'); } } ``` @@ -56,14 +55,12 @@ Then you can use query parameters in your path as follows: ```dart Future getTodo() async { try { - const options = RestOptions( - path: '/todo', - queryParameters: {'q' : 'test'}, + final restOperation = Amplify.API.get( + 'todo', + queryParameters: {'q': 'test'}, ); - final restOperation = Amplify.API.get(restOptions: options); final response = await restOperation.response; - print('GET call succeeded'); - print(response.body); + print('GET call succeeded: ${response.decodeBody()}'); } on ApiException catch (e) { print('GET call failed: $e'); } diff --git a/src/fragments/lib-v1/restapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/restapi/flutter/getting-started/20_installLib.mdx index 846577c8c89..8c53d52005e 100644 --- a/src/fragments/lib-v1/restapi/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/restapi/flutter/getting-started/20_installLib.mdx @@ -2,12 +2,12 @@ Add the following dependencies to your `pubspec.yaml` file and install dependenc ```yaml environment: - sdk: ">=2.15.0 <3.0.0" + sdk: ">=2.18.0 <4.0.0" dependencies: flutter: sdk: flutter - amplify_flutter: ^0.6.0 - amplify_api: ^0.6.0 - amplify_auth_cognito: ^0.6.0 + amplify_flutter: ^1.0.0 + amplify_api: ^1.0.0 + amplify_auth_cognito: ^1.0.0 ``` diff --git a/src/fragments/lib-v1/restapi/flutter/getting-started/40_postTodo.mdx b/src/fragments/lib-v1/restapi/flutter/getting-started/40_postTodo.mdx index 865100e4080..6da2cc0630b 100644 --- a/src/fragments/lib-v1/restapi/flutter/getting-started/40_postTodo.mdx +++ b/src/fragments/lib-v1/restapi/flutter/getting-started/40_postTodo.mdx @@ -1,20 +1,15 @@ ```dart -import 'dart:typed_data'; - Future postTodo() async { try { - final options = RestOptions( - path: '/todo', - body: Uint8List.fromList('{\'name\':\'Mow the lawn\'}'.codeUnits) - ); final restOperation = Amplify.API.post( - restOptions: options + 'todo', + body: HttpPayload.json({'name': 'Mow the lawn'}), ); final response = await restOperation.response; print('POST call succeeded'); - print(response.body); + print(response.decodeBody()); } on ApiException catch (e) { print('POST call failed: $e'); } } -``` \ No newline at end of file +``` diff --git a/src/fragments/lib-v1/restapi/flutter/update.mdx b/src/fragments/lib-v1/restapi/flutter/update.mdx index 97fec79acf4..1b8ae841c41 100644 --- a/src/fragments/lib-v1/restapi/flutter/update.mdx +++ b/src/fragments/lib-v1/restapi/flutter/update.mdx @@ -5,16 +5,15 @@ To update an item via the API endpoint: ```dart Future updateTodo() async { try { - final options = RestOptions( - path: '/todo/1', - body: Uint8List.fromList('{\'name\':\'Mow the lawn\'}'.codeUnits), + final restOperation = Amplify.API.put( + 'todo/1', + body: HttpPayload.json({'name': 'Mow the lawn'}), ); - final restOperation = Amplify.API.put(restOptions: options); final response = await restOperation.response; print('PUT call succeeded'); - print(response.body); + print(response.decodeBody()); } on ApiException catch (e) { print('PUT call failed: $e'); } } -``` \ No newline at end of file +``` From 0dd2ee03389e2233d9bac44591f61691686f9d4b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 14:16:34 -0700 Subject: [PATCH 34/88] chore: fix function links flutter v1 gen 1 --- src/directory/directory.mjs | 6 +++--- .../[platform]/prev/build-a-backend/functions/index.mdx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 8d947a34928..c212ae44ee2 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -2452,13 +2452,13 @@ export const directory = { path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx' }, { - path: 'src/pages/gen1/[platform]/build-a-backend/functions/build-options/index.mdx' + path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx' }, { - path: 'src/pages/gen1/[platform]/build-a-backend/functions/configure-options/index.mdx' + path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx' }, { - path: 'src/pages/gen1/[platform]/build-a-backend/functions/graphql-from-lambda/index.mdx' + path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx' } ] }, diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx index 4bc56c816be..d5d8b2ddc40 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx @@ -7,7 +7,7 @@ export const meta = { platforms: [ 'flutter', ], - route: '/[platform]/build-a-backend/functions', + route: '/gen1/[platform]/prev/build-a-backend/functions', canonicalObjects: [ { platforms: [ From 25423d24f672e6eec4c892d5ff85ba49a1625987 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 14:28:23 -0700 Subject: [PATCH 35/88] chore: fix storage snippets --- src/fragments/lib-v1/storage/existing-resources.mdx | 2 +- .../storage/native_common/configureaccess/common.mdx | 6 +++--- .../prev/build-a-backend/storage/copy/index.mdx | 4 ++++ .../build-a-backend/storage/get-properties/index.mdx | 4 ++++ .../[platform]/prev/build-a-backend/storage/index.mdx | 11 +++++++++++ 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/fragments/lib-v1/storage/existing-resources.mdx b/src/fragments/lib-v1/storage/existing-resources.mdx index 6452da3f114..ec806533cfd 100644 --- a/src/fragments/lib-v1/storage/existing-resources.mdx +++ b/src/fragments/lib-v1/storage/existing-resources.mdx @@ -6,7 +6,7 @@ amplify import storage For more details, see how to [Use an existing S3 bucket or DynamoDB table](/gen1/[platform]/build-a-backend/storage/import/). -If you are not using the Amplify CLI, an existing Amazon S3 bucket can be used by referencing it in your `amplifyconfiguration.json` file. +If you are not using the Amplify CLI, an existing Amazon S3 bucket can be used by referencing it in your `amplifyconfiguration` file. When you are not using Amplify CLI, adding existing Amazon S3 bucket to your application may require configuring bucket access permissions. e.g. Enabling read/write access to the cognito user pool that you are using with the Amplify Auth category. diff --git a/src/fragments/lib-v1/storage/native_common/configureaccess/common.mdx b/src/fragments/lib-v1/storage/native_common/configureaccess/common.mdx index a4640ee03b6..a3d8fd9f709 100644 --- a/src/fragments/lib-v1/storage/native_common/configureaccess/common.mdx +++ b/src/fragments/lib-v1/storage/native_common/configureaccess/common.mdx @@ -30,9 +30,9 @@ import flutter2 from '/src/fragments/lib-v1/storage/flutter/configureaccess/10_p -This will upload with the prefix `/protected/[IDENTITY_ID]/` followed by the key. +This will upload with the prefix `/protected/[IDENTITY_ID]/` followed by the `key`. -For other users to read the file, you must specify the user ID of the creating user in the passed options. +For other users to read the file, you must specify the access level as `protected` and the identity ID of the user who uploaded it in the options. import ios3 from '/src/fragments/lib-v1/storage/ios/configureaccess/20_protected_download.mdx'; @@ -62,7 +62,7 @@ import flutter8 from '/src/fragments/lib-v1/storage/flutter/configureaccess/30_p -This will upload with the prefix `/private/[IDENTITY_ID]/`, followed by the key. +This will upload with the prefix `/private/[IDENTITY_ID]/`, followed by the `key`. For the user to read the file, specify the same access level (`private`) and key you used to upload: diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx index cfaa834f46a..cd87d4d4d1e 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx @@ -40,6 +40,10 @@ export function getStaticProps(context) { }; } +import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; + + + import js0 from '/src/fragments/lib-v1/storage/js/copy.mdx'; + import js0 from '/src/fragments/lib-v1/storage/js/get-properties.mdx'; ![Image](/images/s3_overview.jpg) +
## S3 Core Concepts From 4a32cecd8540b958424a7ac5698e4e877765a6ce Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 14:37:11 -0700 Subject: [PATCH 36/88] chore: fix childnodes for best practices and existing aws resources --- .../prev/build-a-backend/existing-resources/index.mdx | 2 +- .../prev/build-a-backend/graphqlapi/best-practice/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx index 713948a4b91..161ca3b71b9 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx @@ -8,7 +8,7 @@ export const meta = { platforms: [ 'flutter', ], - route: '/[platform]/prev/build-a-backend/existing-resources' + route: '/gen1/[platform]/prev/build-a-backend/existing-resources' }; export const getStaticPaths = async () => { diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx index b4b3efda726..ad7f704d76a 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx @@ -7,7 +7,7 @@ export const meta = { platforms: [ 'flutter', ], - route: '/[platform]/build-a-backend/graphqlapi/best-practice', + route: '/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice', canonicalObjects: [ { platforms: [ From 2229aa297357f10085fb49c3c2c12cb0a8b870af Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 15:38:21 -0700 Subject: [PATCH 37/88] chore: fixed looking for legacy docs links --- .../build-a-backend/graphqlapi/client-code-generation/index.mdx | 2 +- .../graphqlapi/connect-machine-learning-services/index.mdx | 2 +- .../build-a-backend/graphqlapi/custom-business-logic/index.mdx | 2 +- .../graphqlapi/customize-authorization-rules/index.mdx | 2 +- .../prev/build-a-backend/graphqlapi/data-modeling/index.mdx | 2 +- .../graphqlapi/search-and-result-aggregations/index.mdx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx index 0de45608999..27e72469f51 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx @@ -33,7 +33,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - + "Codegen" generates native code for Swift (iOS), Java (Android), and JavaScript that represent your GraphQL API's data models. It can also generate GraphQL statements (queries, mutations, and subscriptions) so that you don't have to hand code them. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx index 9bf799a2f95..54d262205c7 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx @@ -33,7 +33,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - + Amplify allows you to identify text on an image, identify labels on an image, translate text, and synthesize speech from text with the `@predictions` directive. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx index 2950fd5f6ce..6d483c9ffeb 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx @@ -34,7 +34,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; Define your custom business logic in a Lambda function resolver, HTTP resolver, or an AppSync JavaScript or VTL resolver and expose them in a GraphQL query or mutation. Extend or override Amplify-generated GraphQL resolvers to optimize for your specific use cases. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx index 9082451329a..550e08cb5c3 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx @@ -44,7 +44,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - + Use the `@auth` directive to configure authorization rules for public, sign-in user, per user, and per user group data access. **Authorization rules operate on the deny-by-default principle**. Meaning that if an authorization rule is not specifically configured, it is denied. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx index 1d29c31e22b..6f66ba7b691 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx @@ -33,7 +33,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - + Amplify automatically creates Amazon DynamoDB database tables for GraphQL types annotated with the `@model` directive in your GraphQL schema. You can create relations between the data models via the `@hasOne`, `@hasMany`, `@belongsTo`, and `@manyToMany` directives. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx index 79f15b91cca..29727d4c43f 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx @@ -33,7 +33,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - + Add the `@searchable` directive to an `@model` type to enable OpenSearch-based data search and result aggregations. This gives you the ability to: From 745696a66f8db949540955d4b637bb77a8b7dc0c Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 15:43:01 -0700 Subject: [PATCH 38/88] chore: fixed here links in configure storage --- .../prev/build-a-backend/storage/configure-storage/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx index b618c232a89..e450da12a4b 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx @@ -148,7 +148,7 @@ Lastly, you have the option of configuring a Lambda function that can execute in ? Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) ``` -Learn more about this workflow [here](/gen1/[platform]/prev/tools/cli/usage/lambda-triggers/#s3-lambda-triggers). +Learn more about this workflow [here](/gen1/[platform]/tools/cli/usage/lambda-triggers/#s3-lambda-triggers). That's it! Your content storage is set up! Head to the [library's storage docs](/[platform]/build-a-backend/storage/set-up-storage/) to integrate this newly created S3 bucket into your app. @@ -185,6 +185,6 @@ Then, you'll need to specify your indexes. The concept behind "indexes", "partit ? Do you want to add a Lambda Trigger for your Table? (y/N) ``` -If you want to configure a Lambda trigger for your Table, you'll have the option. Learn more about this workflow [here](/gen1/[platform]/prev/tools/cli/usage/lambda-triggers/#dynamodb-lambda-triggers). +If you want to configure a Lambda trigger for your Table, you'll have the option. Learn more about this workflow [here](/gen1/[platform]/tools/cli/usage/lambda-triggers/#dynamodb-lambda-triggers). That's it! Your NoSQL Database is set up! From a87861abdb1ffa6d579db4fa065a60758bda6bb6 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 15:48:17 -0700 Subject: [PATCH 39/88] chore: fix configure amplify cli links --- .../lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx | 2 +- .../common/getting_started/getting-started.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx index dbde1529351..5c43efbc7e6 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx @@ -1,4 +1,4 @@ -* [Install and configure Amplify CLI](/gen1/[platform]/prev/tools/cli/start/set-up-cli/) +* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) * A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated The following are also required, depending on which platforms you are targeting: diff --git a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx index ffd229d7cb1..38194bc28f3 100644 --- a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx +++ b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx @@ -34,7 +34,7 @@ To use Push Notifications with Amplify, you have the option to either have the A -> Prerequisite: [Install and configure the Amplify CLI](/gen1/[platform]/prev/tools/cli/start/set-up-cli/) +> Prerequisite: [Install and configure the Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) From 994da31b8d534e4432a4f210a42487e320f71821 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 16:01:17 -0700 Subject: [PATCH 40/88] chore: fix tools links --- .../prev/build-a-backend/existing-resources/cdk/index.mdx | 2 +- .../prev/build-a-backend/existing-resources/cli/index.mdx | 6 +++--- .../graphqlapi/connect-api-to-existing-database/index.mdx | 2 +- .../graphqlapi/custom-business-logic/index.mdx | 4 ++-- .../build-a-backend/restapi/configure-rest-api/index.mdx | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx index e444198ec2d..c3c0f2cfdcf 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx @@ -52,7 +52,7 @@ Before you begin, you will need: * An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. +* The Amplify CLI [installed](/gen1/[platform]/tools/cli/start/set-up-cli/) and configured. * A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. * Flutter and its command-line tools [installed](https://docs.flutter.dev/get-started/install) and configured. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx index 76b23ac7ce8..c89acf7d066 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx @@ -32,7 +32,7 @@ In this guide, you will learn how to connect a new Flutter mobile app to backend -Connecting a web app? We also offer a version of this guide for integrating existing backends with React using the Amplify CLI. Check out the [React guide](/react/build-a-backend/existing-resources/cli/). +Connecting a web app? We also offer a version of this guide for integrating existing backends with React using the Amplify CLI. Check out the [React guide](/gen1/react/build-a-backend/existing-resources/cli/). @@ -56,7 +56,7 @@ Before you begin, you will need: * An existing Flutter app * An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. +* The Amplify CLI [installed](/gen1/[platform]/tools/cli/start/set-up-cli/) and configured. * Flutter [installed](https://docs.flutter.dev/get-started/install) and configured. * A text editor combined with Flutter’s command-line tools. For this guide, we will use VS Code, but you can use your preferred IDE. @@ -106,7 +106,7 @@ amplify pull --appId --envName ``` -**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. +**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. Accept the default values for the prompts about the default editor, type of app, and storage location of the configuration file. Then answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx index fb66819d93b..82fb41406a0 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx @@ -527,7 +527,7 @@ type Post @model @refersTo(name: "posts") @auth(rules: [{ allow: public }]) { } ``` -For more information on each rule please refer to our documentation on [Authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules). +For more information on each rule please refer to our documentation on [Authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules). ### Field-level authorization rules diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx index 6d483c9ffeb..470efc896c4 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx @@ -61,7 +61,7 @@ type Query { - [HTTP resolver](#http-resolver): call an HTTP endpoint upon a query or mutation - [AppSync JavaScript or VTL resolver](#appsync-javascript-or-vtl-resolver) (most advanced): use AppSync's JavaScript resolver or AppSync's VTL mapping templates to customize the query and mutation logic -3. Secure your custom query or mutation with [field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) +3. Secure your custom query or mutation with [field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) - Note: Dynamic authorization rules are not supported on a custom query or mutation. ## Lambda function resolver @@ -851,7 +851,7 @@ responseMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, " ## Add authorization rules to custom queries and mutations -Authorization rules can be applied with the `@auth` directive in the same way as field-level authorization rules. See [Field-level authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules) for details. +Authorization rules can be applied with the `@auth` directive in the same way as field-level authorization rules. See [Field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules) for details. In the example below, `myCustomMutation` can only be executed by signed-in customers who are authenticated with IAM: diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx index 5dcc0e6ea7c..808d3d59af5 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx @@ -68,7 +68,7 @@ For each user type you can further specify what actions it has access to. | Guest user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | | User Pool Group | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | -REST APIs have support for [multiple environments](/gen1/[platform]/prev/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. +REST APIs have support for [multiple environments](/gen1/[platform]/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. Because Amplify environments could be in separate AWS accounts, you cannot use the environment feature of API Gateway. Each Amplify environment will have a separate API Gateway resource associated with it. For example: From 5fe87cabb7a6df376fb3a5e91a58984480d78eea Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 17:08:11 -0700 Subject: [PATCH 41/88] chore: fix android pages --- src/fragments/lib-v1/datastore/native_common/real-time.mdx | 2 ++ .../build-a-backend/auth/existing-resources-no-cli/index.mdx | 2 +- .../prev/build-a-backend/restapi/set-up-rest-api/index.mdx | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fragments/lib-v1/datastore/native_common/real-time.mdx b/src/fragments/lib-v1/datastore/native_common/real-time.mdx index 6cb54ebba04..26112dbea33 100644 --- a/src/fragments/lib-v1/datastore/native_common/real-time.mdx +++ b/src/fragments/lib-v1/datastore/native_common/real-time.mdx @@ -2,6 +2,7 @@ You can subscribe to changes on your Models. This reacts dynamically to updates of data to the underlying Storage Engine, which could be the result of GraphQL Subscriptions as well as Queries or Mutations that run against the backing AppSync API if you are synchronizing with the cloud. + **NOTE:** AWS AppSync has an [adjustable limit of 100 subscriptions per connection](https://docs.aws.amazon.com/general/latest/gr/appsync.html). DataStore automatically subscribes to create, update, and delete mutations for all models. @@ -11,6 +12,7 @@ This means that GraphQL APIs with DataStore enabled are limited to 33 models and However, You can [request a service limit increase](https://console.aws.amazon.com/servicequotas/home/services/appsync/quotas/L-AA33EB36) from AWS AppSync to meet the real-time requirements of your application. + import js0 from '/src/fragments/lib-v1/datastore/js/real-time/observe-snippet.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx index 926b171dba6..9d633f1e56d 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx @@ -4,7 +4,7 @@ export const meta = { title: 'Use existing resources without the CLI', description: 'Configure the Amplify Libraries to use existing Amazon Cognito resources by referencing them in your configuration.', - platforms: ['flutter', 'swift', 'android'] + platforms: ['flutter'] }; export const getStaticPaths = async () => { diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/set-up-rest-api/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/set-up-rest-api/index.mdx index fbed8439a56..b161915a875 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/set-up-rest-api/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/set-up-rest-api/index.mdx @@ -39,7 +39,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; import ios1 from '/src/fragments/lib-v1/restapi/native_common/getting-started/common.mdx'; -; + import android_maintenance from '/src/fragments/lib-v1/android-maintenance.mdx'; From 40920f968a49e7a552d90f8a756ad744bf590c99 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 17:36:46 -0700 Subject: [PATCH 42/88] chore: update pubspec.yaml snippets to use the new version of flutter --- .../flutter/getting_started/20_installLib.mdx | 13 +++++++++++-- .../common/getting_started/getting-started.mdx | 2 +- .../flutter/getting-started/40_install_lib.mdx | 17 +++++++++++++++++ .../flutter/getting-started/20_installLib.mdx | 6 +++--- .../flutter/getting-started/20_installLib.mdx | 6 +++--- .../datastore/flutter/sync/10-installPlugin.mdx | 2 +- .../graphqlapi/flutter/authz/10_userpool.mdx | 6 +++--- .../flutter/getting-started/20_installLib.mdx | 4 ++-- .../create-application/60_dependencies.mdx | 6 +++--- .../flutter/getting_started/40_install_lib.mdx | 6 +++--- .../flutter/getting-started/20_installLib.mdx | 6 +++--- .../flutter/getting-started/20_installLib.mdx | 8 ++++---- .../start/getting-started/flutter/setup.mdx | 10 +++++----- .../analytics/set-up-analytics/index.mdx | 6 +++--- .../build-a-backend/auth/set-up-auth/index.mdx | 6 +++--- .../build-a-backend/data/set-up-data/index.mdx | 6 +++--- .../storage/set-up-storage/index.mdx | 6 +++--- src/pages/[platform]/start/quickstart/index.mdx | 8 ++++---- .../existing-resources/cdk/index.mdx | 4 ++-- .../existing-resources/cli/index.mdx | 8 ++++---- src/pages/gen2/start/mobile-support/index.mdx | 8 ++++---- 21 files changed, 85 insertions(+), 59 deletions(-) create mode 100644 src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx index 91716d5c21f..54ce36d9654 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx @@ -1,5 +1,14 @@ Add the following dependency to your **app**'s `pubspec.yaml` along with others you added above in **Prerequisites**: -import flutter0 from "/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx"; +```yaml +environment: + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" - \ No newline at end of file +dependencies: + flutter: + sdk: flutter + + amplify_flutter: ^1.0.0 + amplify_auth_cognito: ^1.0.0 +``` diff --git a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx index 38194bc28f3..c54174e4bd4 100644 --- a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx +++ b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx @@ -85,7 +85,7 @@ import reactNativeExistingResources from '/src/fragments/lib/push-notifications/ ## Install Amplify Libraries import androidInstallLib from '/src/fragments/lib/push-notifications/android/getting_started/40_install_lib.mdx'; -import flutterInstallLib from '/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx'; +import flutterInstallLib from '/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx'; import iosInstallLib from '/src/fragments/lib/push-notifications/ios/getting_started/60_install_lib.mdx'; import reactNativeInstallLib from '/src/fragments/lib/push-notifications/react-native/getting_started/40_install_lib.mdx'; diff --git a/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx b/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx new file mode 100644 index 00000000000..f7b05a3157c --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx @@ -0,0 +1,17 @@ +In your project directory, you should first install the necessary dependencies for using Amplify Push Notifications. + +1. Open `pubspec.yaml` at the root of your Flutter project with a text editor. + +2. Add the necessary libraries into the `dependencies` block: + +```yaml +environment: + sdk: '>=2.18.0 <4.0.0' + +dependencies: + amplify_auth_cognito: ^1.0.0 + amplify_flutter: ^1.0.0 + amplify_push_notifications_pinpoint: ^1.0.0 + flutter: + sdk: flutter +``` diff --git a/src/fragments/lib/analytics/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/analytics/flutter/getting-started/20_installLib.mdx index c88f89f169f..2be296731a8 100644 --- a/src/fragments/lib/analytics/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/analytics/flutter/getting-started/20_installLib.mdx @@ -9,7 +9,7 @@ environment: sdk: '>=2.18.0 <4.0.0' dependencies: - amplify_analytics_pinpoint: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_flutter: ^1.0.0 + amplify_analytics_pinpoint: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_flutter: ^2.0.0 ``` diff --git a/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx index 07c5c650583..0e0a09e07ca 100644 --- a/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx @@ -11,8 +11,8 @@ dependencies: flutter: sdk: flutter - amplify_datastore: ^1.0.0-supports-only-mobile - amplify_flutter: ^1.0.0 + amplify_datastore: 2.0.0-supports-only-mobile + amplify_flutter: ^2.0.0 ``` ### Update The Android Project @@ -38,4 +38,4 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } -``` \ No newline at end of file +``` diff --git a/src/fragments/lib/datastore/flutter/sync/10-installPlugin.mdx b/src/fragments/lib/datastore/flutter/sync/10-installPlugin.mdx index 7a970096c6d..9363a06e762 100644 --- a/src/fragments/lib/datastore/flutter/sync/10-installPlugin.mdx +++ b/src/fragments/lib/datastore/flutter/sync/10-installPlugin.mdx @@ -5,7 +5,7 @@ Although DataStore presents a distinct API, its cloud synchronization functional Make sure you have the following plugin dependency in your `pubspec.yaml`. ```yaml -amplify_api: ^1.0.0 +amplify_api: ^2.0.0 ``` Locate your Amplify initialization code, and add an `AmplifyAPI()` plugin. Your initialization code should already include an `AmplifyDataStore()` plugin from previous steps. Note the new `import` statement for API towards the top of the file. diff --git a/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx b/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx index b9c5f7d3812..a4506a67fe6 100644 --- a/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx +++ b/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx @@ -8,10 +8,10 @@ dependencies: flutter: sdk: flutter - amplify_flutter: ^1.0.0 - amplify_api: ^1.0.0 + amplify_flutter: ^2.0.0 + amplify_api: ^2.0.0 # Be sure that this is added - amplify_auth_cognito: ^1.0.0 + amplify_auth_cognito: ^2.0.0 ``` Afterwards add the following code to your app before you configure Amplify: diff --git a/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx index bd8d918655c..64f1d56954a 100644 --- a/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx @@ -7,6 +7,6 @@ environment: dependencies: flutter: sdk: flutter - amplify_flutter: ^1.0.0 - amplify_api: ^1.0.0 + amplify_flutter: ^2.0.0 + amplify_api: ^2.0.0 ``` diff --git a/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx b/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx index 1ec7f28bd14..a74c76ca481 100644 --- a/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx +++ b/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx @@ -7,6 +7,6 @@ dependencies: flutter: sdk: flutter - amplify_flutter: ^1.0.0 - amplify_auth_cognito: ^1.0.0 -``` \ No newline at end of file + amplify_flutter: ^2.0.0 + amplify_auth_cognito: ^2.0.0 +``` diff --git a/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx b/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx index f7b05a3157c..81d62c005a9 100644 --- a/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx +++ b/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx @@ -9,9 +9,9 @@ environment: sdk: '>=2.18.0 <4.0.0' dependencies: - amplify_auth_cognito: ^1.0.0 - amplify_flutter: ^1.0.0 - amplify_push_notifications_pinpoint: ^1.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_flutter: ^2.0.0 + amplify_push_notifications_pinpoint: ^2.0.0 flutter: sdk: flutter ``` diff --git a/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx index b2f66c3f692..594a96340a3 100644 --- a/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx @@ -7,7 +7,7 @@ environment: dependencies: flutter: sdk: flutter - amplify_flutter: ^1.0.0 - amplify_api: ^1.0.0 - amplify_auth_cognito: ^1.0.0 + amplify_flutter: ^2.0.0 + amplify_api: ^2.0.0 + amplify_auth_cognito: ^2.0.0 ``` diff --git a/src/fragments/lib/storage/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/storage/flutter/getting-started/20_installLib.mdx index a88dceb3b9f..80a5bd07d91 100644 --- a/src/fragments/lib/storage/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/storage/flutter/getting-started/20_installLib.mdx @@ -9,7 +9,7 @@ dependencies: flutter: sdk: flutter - amplify_auth_cognito: ^1.0.0 - amplify_flutter: ^1.0.0 - amplify_storage_s3: ^1.0.0 -``` \ No newline at end of file + amplify_auth_cognito: ^2.0.0 + amplify_flutter: ^2.0.0 + amplify_storage_s3: ^2.0.0 +``` diff --git a/src/fragments/start/getting-started/flutter/setup.mdx b/src/fragments/start/getting-started/flutter/setup.mdx index d596ba937db..67a92e40c17 100644 --- a/src/fragments/start/getting-started/flutter/setup.mdx +++ b/src/fragments/start/getting-started/flutter/setup.mdx @@ -16,13 +16,13 @@ Amplify Flutter is distributed via [pub.dev](https://pub.dev/packages/amplify_fl ```yaml environment: - sdk: ">=2.18.0 <3.0.0" + sdk: ">=2.18.0 <4.0.0" dependencies: - amplify_api: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_authenticator: ^1.0.0 - amplify_flutter: ^1.0.0 + amplify_api: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_authenticator: ^2.0.0 + amplify_flutter: ^2.0.0 flutter: sdk: flutter go_router: ^6.5.5 diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx index f4ff0a66bae..7859dd8c79e 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx @@ -172,9 +172,9 @@ environment: sdk: '>=2.18.0 <4.0.0' dependencies: - amplify_analytics_pinpoint: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_flutter: ^1.0.0 + amplify_analytics_pinpoint: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_flutter: ^2.0.0 ```
diff --git a/src/pages/[platform]/build-a-backend/auth/set-up-auth/index.mdx b/src/pages/[platform]/build-a-backend/auth/set-up-auth/index.mdx index 1b00914f9a9..b4b14e451bb 100644 --- a/src/pages/[platform]/build-a-backend/auth/set-up-auth/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/set-up-auth/index.mdx @@ -373,9 +373,9 @@ environment: flutter: ">=3.3.0" dependencies: - amplify_flutter: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_authenticator: ^1.0.0 + amplify_flutter: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_authenticator: ^2.0.0 ``` and run the following command to download the libraries. diff --git a/src/pages/[platform]/build-a-backend/data/set-up-data/index.mdx b/src/pages/[platform]/build-a-backend/data/set-up-data/index.mdx index 6e6ae3352a5..9140e2379fe 100644 --- a/src/pages/[platform]/build-a-backend/data/set-up-data/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/set-up-data/index.mdx @@ -293,12 +293,12 @@ From your project root directory, find and modify your **pubspec.yaml** and add ```yaml title="pubspec.yaml" environment: - sdk: ">=2.18.0 <3.0.0" + sdk: ">=2.18.0 <4.0.0" dependencies: // highlight-start - amplify_api: ^1.0.0 - amplify_flutter: ^1.0.0 + amplify_api: ^2.0.0 + amplify_flutter: ^2.0.0 // highlight-end flutter: sdk: flutter diff --git a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx index 79a3723399c..b9249d78725 100644 --- a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx @@ -435,9 +435,9 @@ dependencies: flutter: sdk: flutter - amplify_auth_cognito: ^1.0.0 - amplify_flutter: ^1.0.0 - amplify_storage_s3: ^1.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_flutter: ^2.0.0 + amplify_storage_s3: ^2.0.0 ``` ### Configure Amplify in project diff --git a/src/pages/[platform]/start/quickstart/index.mdx b/src/pages/[platform]/start/quickstart/index.mdx index 52a33af50d1..ba55c5817fa 100644 --- a/src/pages/[platform]/start/quickstart/index.mdx +++ b/src/pages/[platform]/start/quickstart/index.mdx @@ -1352,9 +1352,9 @@ To use the Authenticator, you need to add the following dependencies to your pro ```yaml title="pubspec.yaml" dependencies: - amplify_flutter: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_authenticator: ^1.0.0 + amplify_flutter: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_authenticator: ^2.0.0 ``` You will add: @@ -1475,7 +1475,7 @@ Once you are done, add the API dependencies to your project. You will add `ampli ```yaml title="pubspec.yaml" dependencies: - amplify_api: ^1.0.0 + amplify_api: ^2.0.0 ``` diff --git a/src/pages/gen1/[platform]/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/gen1/[platform]/build-a-backend/existing-resources/cdk/index.mdx index 0169b641809..76d4b541503 100644 --- a/src/pages/gen1/[platform]/build-a-backend/existing-resources/cdk/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/existing-resources/cdk/index.mdx @@ -361,8 +361,8 @@ environment: dependencies: flutter: sdk: flutter - amplify_flutter: ^1.0.0 - amplify_api: ^1.0.0 + amplify_flutter: ^2.0.0 + amplify_api: ^2.0.0 go_router: ^6.5.5 cupertino_icons: ^1.0.2 // highlight-end diff --git a/src/pages/gen1/[platform]/build-a-backend/existing-resources/cli/index.mdx b/src/pages/gen1/[platform]/build-a-backend/existing-resources/cli/index.mdx index 8fba6c855e5..348dccd08a9 100644 --- a/src/pages/gen1/[platform]/build-a-backend/existing-resources/cli/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/existing-resources/cli/index.mdx @@ -156,10 +156,10 @@ environment: sdk: '>=3.1.0 <4.0.0' dependencies: // highlight-start - amplify_api: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_authenticator: ^1.0.0 - amplify_flutter: ^1.0.0 + amplify_api: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_authenticator: ^2.0.0 + amplify_flutter: ^2.0.0 // highlight-end flutter: sdk: flutter diff --git a/src/pages/gen2/start/mobile-support/index.mdx b/src/pages/gen2/start/mobile-support/index.mdx index 911f2a97d4f..847d7c22ab7 100644 --- a/src/pages/gen2/start/mobile-support/index.mdx +++ b/src/pages/gen2/start/mobile-support/index.mdx @@ -547,9 +547,9 @@ To use the Amplify UI libraries, you need to add the following dependencies to y ```yaml dependencies: - amplify_flutter: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_authenticator: ^1.0.0 + amplify_flutter: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_authenticator: ^2.0.0 ``` You will add: @@ -664,7 +664,7 @@ For using GraphQL API, you need to add the following dependencies to your pubspe ```yaml dependencies: - amplify_api: ^1.0.0 + amplify_api: ^2.0.0 ``` You will add `amplify_api` to connect your application with the Amplify API. From 4abbae37f2e39f78a63ceada7f014db7b16542a2 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 19:58:34 -0700 Subject: [PATCH 43/88] chore: fix inline filters for swift v1 pages --- src/fragments/lib-v1/auth/common/device_features/common.mdx | 5 ++--- src/fragments/lib-v1/auth/native_common/signin/common.mdx | 4 ++-- .../lib-v1/auth/native_common/signin_next_steps/common.mdx | 6 ++++++ src/fragments/lib-v1/auth/native_common/signout/common.mdx | 6 +++--- src/fragments/lib-v1/graphqlapi/existing-resources.mdx | 2 +- .../graphqlapi/native_common/advanced-workflows/common.mdx | 4 ++-- .../graphqlapi/native_common/getting-started/common.mdx | 2 +- src/fragments/lib-v1/storage/flutter/upload.mdx | 2 +- .../lib/graphqlapi/native_common/getting-started/common.mdx | 2 +- 9 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/fragments/lib-v1/auth/common/device_features/common.mdx b/src/fragments/lib-v1/auth/common/device_features/common.mdx index 5a7499ab28b..be89a7351b8 100644 --- a/src/fragments/lib-v1/auth/common/device_features/common.mdx +++ b/src/fragments/lib-v1/auth/common/device_features/common.mdx @@ -10,7 +10,7 @@ Remembering a device allows the second factor requirement to be automatically me ## Configure Auth Category - + Device remembering functionality does not work if you use one of the web UI sign in methods. @@ -105,10 +105,9 @@ import flutter11 from '/src/fragments/lib-v1/auth/flutter/device_features/30_fet import flutter13 from '/src/fragments/lib-v1/auth/flutter/device_features/40_trackDevice.mdx'; - + ## Known Limitations When using the federated OAuth flow with Cognito User Pools, the [device tracking and remembering](https://aws.amazon.com/blogs/mobile/tracking-and-remembering-devices-using-amazon-cognito-your-user-pools/) features are currently not available within the library. - diff --git a/src/fragments/lib-v1/auth/native_common/signin/common.mdx b/src/fragments/lib-v1/auth/native_common/signin/common.mdx index 070c81b3032..cc12352a627 100644 --- a/src/fragments/lib-v1/auth/native_common/signin/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signin/common.mdx @@ -44,7 +44,7 @@ import flutter8 from '/src/fragments/lib-v1/auth/flutter/signin/20_confirmSignUp - + You will know the sign up flow is complete if you see the following in your console window: ```console @@ -68,7 +68,7 @@ import flutter11 from '/src/fragments/lib-v1/auth/flutter/signin/30_signIn.mdx'; - + You will know the sign in flow is complete if you see the following in your console window: ```console diff --git a/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx b/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx index 54813f058cf..c3310606b28 100644 --- a/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signin_next_steps/common.mdx @@ -18,19 +18,25 @@ import flutter1 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/20_co + ### Confirm signin with TOTP MFA + import flutter7 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/80_totp.mdx'; + ### Continue signin with MFA Selection + import flutter8 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/90_mfa_selection.mdx'; + ### Continue signin with TOTP Setup + import flutter9 from '/src/fragments/lib-v1/auth/flutter/signin_next_steps/100_totp_setup.mdx'; diff --git a/src/fragments/lib-v1/auth/native_common/signout/common.mdx b/src/fragments/lib-v1/auth/native_common/signout/common.mdx index cd9e45bf6fc..f20aa7668f5 100644 --- a/src/fragments/lib-v1/auth/native_common/signout/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signout/common.mdx @@ -1,4 +1,4 @@ - + Invoke the `signOut` api to sign out a user from the Auth category. You can only have one user signed in at a given time. @@ -18,7 +18,7 @@ import flutter2 from '/src/fragments/lib-v1/auth/flutter/signout/10_local_signou - + Calling signOut without any options will just delete the local cache and keychain of the user. If you would like to sign out of all devices, invoke the signOut api with advanced options. [Amazon Cognito now supports token revocation](https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-cognito-now-supports-targeted-sign-out-through-refresh-token-revocation/) and the latest Amplify version will revoke Amazon Cognito tokens if the application is online. This means that the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. @@ -60,7 +60,7 @@ import flutter5 from '/src/fragments/lib-v1/auth/flutter/signout/20_global_signo - + Calling signout with `globalSignOut = true` will invalidate all the Cognito User Pool tokens of the signed in user. If the user is signed into a device, they won't be authorized to perform a task that requires a valid token when a global signout is called from some other device. They need to sign in again to get valid tokens. diff --git a/src/fragments/lib-v1/graphqlapi/existing-resources.mdx b/src/fragments/lib-v1/graphqlapi/existing-resources.mdx index 34b1ee6b309..6ce899b8f1f 100644 --- a/src/fragments/lib-v1/graphqlapi/existing-resources.mdx +++ b/src/fragments/lib-v1/graphqlapi/existing-resources.mdx @@ -20,7 +20,7 @@ Existing AWS AppSync resources can be used with the Amplify Libraries by referen diff --git a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx index 632403335d4..3fff244ec8a 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx @@ -138,7 +138,7 @@ import flutter5 from '/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflow - + ## Combining Multiple Operations @@ -146,7 +146,7 @@ import flutter5 from '/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflow ## Combining multiple GraphQL operations in a single request - + When you want to perform more than one operation in a single request, you can place them within the same document. For example, to retrieve a Post and a Todo diff --git a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx index 825344b080e..5ced147f81d 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx @@ -141,7 +141,7 @@ Congratulations! You've created a `Todo` object in your database. Check out the - [Update data](/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/) - [Subscribe to data](/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/) - [Concepts](/gen1/[platform]/prev/build-a-backend/graphqlapi/api-graphql-concepts/) - + - [Configure authorization modes](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes) diff --git a/src/fragments/lib-v1/storage/flutter/upload.mdx b/src/fragments/lib-v1/storage/flutter/upload.mdx index 6d374b8e202..e185ed2b4a7 100644 --- a/src/fragments/lib-v1/storage/flutter/upload.mdx +++ b/src/fragments/lib-v1/storage/flutter/upload.mdx @@ -10,7 +10,7 @@ To upload to S3 from a file, specify the `key` to upload the file to and the `lo To upload to S3 from a file, specify the key and the local file to be uploaded. A file can be created locally, or retrieved from the user's device using a package such as [image_picker](https://pub.dev/packages/image_picker) or [file_picker](https://pub.dev/packages/file_picker). - + ### Upload a local file diff --git a/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx index 9a2e55c8f1d..fa7be102d72 100644 --- a/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx @@ -141,7 +141,7 @@ Congratulations! You've created a `Todo` object in your database. Check out the - [Update data](/gen1/[platform]/build-a-backend/graphqlapi/mutate-data/) - [Subscribe to data](/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/) - [Concepts](/gen1/[platform]/build-a-backend/graphqlapi/api-graphql-concepts/) - + - [Configure authorization modes](/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules) From abb2086e2b38c8196d82dacfd69dce114a5aea4b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 7 May 2024 22:55:04 -0700 Subject: [PATCH 44/88] chore: update storage v2 snippets to use path --- src/fragments/lib/storage/flutter/copy.mdx | 23 ++++--------------- .../lib/storage/flutter/get-properties.mdx | 8 ++----- .../storage/transfer-acceleration/index.mdx | 6 ++--- .../storage/transfer-acceleration/index.mdx | 6 ++--- 4 files changed, 13 insertions(+), 30 deletions(-) diff --git a/src/fragments/lib/storage/flutter/copy.mdx b/src/fragments/lib/storage/flutter/copy.mdx index df196cb69e9..41f724a09b7 100644 --- a/src/fragments/lib/storage/flutter/copy.mdx +++ b/src/fragments/lib/storage/flutter/copy.mdx @@ -1,28 +1,15 @@ You can copy an existing file to a different location in your S3 bucket. User who initiates a copy operation should have read permission on the copy source file. ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future copyGuestFileToPrivate({ - required String sourceKey, - required String destinationKey, -}) async { +Future copy() async { try { final result = await Amplify.Storage.copy( - source: StorageItemWithAccessLevel( - storageItem: StorageItem(key: sourceKey), - accessLevel: StorageAccessLevel.guest, - ), - destination: StorageItemWithAccessLevel( - storageItem: StorageItem(key: destinationKey), - accessLevel: StorageAccessLevel.private, - ), + source: const StoragePath.fromString('album/2024/1.jpg'), + destination: const StoragePath.fromString('shared/2024/1.jpg'), ).result; - - safePrint('Copied file: ${result.copiedItem.key}'); + safePrint('Copied file: ${result.copiedItem.path}'); } on StorageException catch (e) { - safePrint('Error copying file: ${e.message}'); - rethrow; + safePrint(e); } } ``` diff --git a/src/fragments/lib/storage/flutter/get-properties.mdx b/src/fragments/lib/storage/flutter/get-properties.mdx index a2d601a3778..97169de98cc 100644 --- a/src/fragments/lib/storage/flutter/get-properties.mdx +++ b/src/fragments/lib/storage/flutter/get-properties.mdx @@ -1,18 +1,14 @@ You can get file properties and metadata without downloading the file using `Amplify.Storage.getProperties`. ```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - Future getFileProperties() async { try { final result = await Amplify.Storage.getProperties( - key: 'example.txt', + path: const StoragePath.fromString('example.txt'), ).result; - safePrint('File size: ${result.storageItem.size}'); } on StorageException catch (e) { - safePrint('Could not retrieve properties: ${e.message}'); - rethrow; + safePrint(e.message); } } ``` diff --git a/src/pages/gen1/[platform]/build-a-backend/storage/transfer-acceleration/index.mdx b/src/pages/gen1/[platform]/build-a-backend/storage/transfer-acceleration/index.mdx index 07fee3ffa11..4bc7ff48205 100644 --- a/src/pages/gen1/[platform]/build-a-backend/storage/transfer-acceleration/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/storage/transfer-acceleration/index.mdx @@ -254,12 +254,12 @@ Set `useAccelerateEndpoint` to `true` in the corresponding Storage S3 plugin opt ```dart import 'package:amplify_storage_s3/amplify_storage_s3.dart'; -Future uploadFileUsingAcceleration(String filePath, String key) async { +Future uploadFileUsingAcceleration(String filePath, String path) async { final localFile = AWSFile.fromPath(filePath); try { final uploadFileOperation = Amplify.Storage.uploadFile( localFile: localFile, - key: key, + path: const StoragePath.fromString(path), options: const StorageUploadFileOptions( pluginOptions: S3UploadFilePluginOptions( useAccelerateEndpoint: true, @@ -268,7 +268,7 @@ Future uploadFileUsingAcceleration(String filePath, String key) async { ); final result = await uploadFileOperation.result; - safePrint('Uploaded file: ${result.uploadedItem.key}'); + safePrint('Uploaded file: ${result.uploadedItem.path}'); } on StorageException catch (error) { safePrint('Something went wrong uploading file: ${error.message}'); } diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx index 1d45842db60..1c63c960772 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx @@ -113,12 +113,12 @@ Set `useAccelerateEndpoint` to `true` in the corresponding Storage S3 plugin opt ```dart import 'package:amplify_storage_s3/amplify_storage_s3.dart'; -Future uploadFileUsingAcceleration(String filePath, String key) async { +Future uploadFileUsingAcceleration(String filePath, String path) async { final localFile = AWSFile.fromPath(filePath); try { final uploadFileOperation = Amplify.Storage.uploadFile( localFile: localFile, - key: key, + path: const StoragePath.fromString(path), options: const StorageUploadFileOptions( pluginOptions: S3UploadFilePluginOptions( useAccelerateEndpoint: true, @@ -127,7 +127,7 @@ Future uploadFileUsingAcceleration(String filePath, String key) async { ); final result = await uploadFileOperation.result; - safePrint('Uploaded file: ${result.uploadedItem.key}'); + safePrint('Uploaded file: ${result.uploadedItem.path}'); } on StorageException catch (error) { safePrint('Something went wrong uploading file: ${error.message}'); } From f674ef3928962302e77a7bc4f2ae3e9174ee1c19 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Wed, 8 May 2024 12:28:44 -0400 Subject: [PATCH 45/88] chore: add storage path for flutter (#7536) --- .../storage/storagepath/index.mdx | 24 ++++++++++++++++--- .../storage/storagepath/index.mdx | 24 ++++++++++++++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx b/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx index 4b0927d0347..fb46c6884c2 100644 --- a/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx @@ -5,7 +5,8 @@ export const meta = { description: "Learn more about constructing a StoragePath to use on Amplify Storage APIs", platforms: [ 'swift', - 'android' + 'android', + 'flutter' ] }; @@ -22,7 +23,7 @@ export function getStaticProps(context) { }; } - + You can use `StoragePath` to access, upload to, or download from to any path in your S3 bucket. The Amplify Gen 1 CLI automatically creates the following directories: - `public/`: Accessible by all users of your application - `protected//`: Readable by all users (you need to specify the identityID of the user who uploaded the file). Writable only by the creating user @@ -66,7 +67,14 @@ StoragePath.fromString("public/exampleFile.txt")
- + +```dart +// Resolves to "public/exampleFile.txt" +const StoragePath.fromString('public/exampleFile.txt'); +``` + + + ## Create a StoragePath with user’s IdentityId You may want to construct a StoragePath that contains the Amplify Auth user’s IdentityId. We’ve created a helper function that injects the user’s IdentityId when a Storage API is called, since fetching an IdentityId from the Auth plugin is not synchronous. @@ -105,3 +113,13 @@ StoragePath.fromIdentityID { identityId in + + +```dart +// If the user's identityId was "123", +// the StoragePath would resolve to "private/123/exampleFile.txt" +StoragePath.fromIdentityId( + (String identityId) => 'private/$identityId/exampleFile.txt', +}; +``` + diff --git a/src/pages/gen1/[platform]/build-a-backend/storage/storagepath/index.mdx b/src/pages/gen1/[platform]/build-a-backend/storage/storagepath/index.mdx index 4b0927d0347..fb46c6884c2 100644 --- a/src/pages/gen1/[platform]/build-a-backend/storage/storagepath/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/storage/storagepath/index.mdx @@ -5,7 +5,8 @@ export const meta = { description: "Learn more about constructing a StoragePath to use on Amplify Storage APIs", platforms: [ 'swift', - 'android' + 'android', + 'flutter' ] }; @@ -22,7 +23,7 @@ export function getStaticProps(context) { }; } - + You can use `StoragePath` to access, upload to, or download from to any path in your S3 bucket. The Amplify Gen 1 CLI automatically creates the following directories: - `public/`: Accessible by all users of your application - `protected//`: Readable by all users (you need to specify the identityID of the user who uploaded the file). Writable only by the creating user @@ -66,7 +67,14 @@ StoragePath.fromString("public/exampleFile.txt") - + +```dart +// Resolves to "public/exampleFile.txt" +const StoragePath.fromString('public/exampleFile.txt'); +``` + + + ## Create a StoragePath with user’s IdentityId You may want to construct a StoragePath that contains the Amplify Auth user’s IdentityId. We’ve created a helper function that injects the user’s IdentityId when a Storage API is called, since fetching an IdentityId from the Auth plugin is not synchronous. @@ -105,3 +113,13 @@ StoragePath.fromIdentityID { identityId in + + +```dart +// If the user's identityId was "123", +// the StoragePath would resolve to "private/123/exampleFile.txt" +StoragePath.fromIdentityId( + (String identityId) => 'private/$identityId/exampleFile.txt', +}; +``` + From c65f32ddbeb7b9af0e52371351ede68d830fa9f9 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 8 May 2024 10:40:15 -0700 Subject: [PATCH 46/88] chore: remove environment section of pubspec snippets --- .../analytics/flutter/getting-started/20_installLib.mdx | 3 --- .../lib-v1/auth/flutter/getting_started/20_installLib.mdx | 4 ---- .../datastore/flutter/getting-started/20_installLib.mdx | 6 +----- .../lib-v1/graphqlapi/flutter/authz/10_userpool.mdx | 3 --- .../graphqlapi/flutter/getting-started/20_installLib.mdx | 3 --- .../flutter/getting-started/40_install_lib.mdx | 3 --- .../restapi/flutter/getting-started/20_installLib.mdx | 3 --- .../storage/flutter/getting-started/20_installLib.mdx | 4 ---- .../lib/analytics/flutter/getting-started/20_installLib.mdx | 3 --- .../lib/datastore/flutter/getting-started/20_installLib.mdx | 4 ---- src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx | 3 --- .../graphqlapi/flutter/getting-started/20_installLib.mdx | 3 --- .../flutter/create-application/60_dependencies.mdx | 4 ---- .../flutter/getting_started/40_install_lib.mdx | 3 --- .../lib/restapi/flutter/getting-started/20_installLib.mdx | 3 --- .../lib/storage/flutter/getting-started/20_installLib.mdx | 4 ---- src/fragments/start/getting-started/flutter/setup.mdx | 3 --- .../add-aws-services/analytics/set-up-analytics/index.mdx | 3 --- .../[platform]/build-a-backend/auth/set-up-auth/index.mdx | 4 ---- .../[platform]/build-a-backend/data/set-up-data/index.mdx | 3 --- .../build-a-backend/storage/set-up-storage/index.mdx | 4 ---- .../prev/build-a-backend/existing-resources/cdk/index.mdx | 3 --- .../prev/build-a-backend/existing-resources/cli/index.mdx | 2 -- .../build-a-backend/existing-resources/cdk/index.mdx | 3 --- .../build-a-backend/existing-resources/cli/index.mdx | 2 -- .../prev/build-a-backend/existing-resources/cdk/index.mdx | 3 --- .../prev/build-a-backend/existing-resources/cli/index.mdx | 2 -- 27 files changed, 1 insertion(+), 87 deletions(-) diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/20_installLib.mdx index c88f89f169f..f0406dcf953 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/20_installLib.mdx @@ -5,9 +5,6 @@ In your Flutter project directory, open **pubspec.yaml**. Add Analytics by adding these libraries into your dependencies block: ```yaml -environment: - sdk: '>=2.18.0 <4.0.0' - dependencies: amplify_analytics_pinpoint: ^1.0.0 amplify_auth_cognito: ^1.0.0 diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx index 54ce36d9654..b631e11c889 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/20_installLib.mdx @@ -1,10 +1,6 @@ Add the following dependency to your **app**'s `pubspec.yaml` along with others you added above in **Prerequisites**: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx index 07c5c650583..206bfaea652 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx @@ -3,10 +3,6 @@ Add the following dependencies to your `pubspec.yaml` file and install dependencies when asked: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - dependencies: flutter: sdk: flutter @@ -38,4 +34,4 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } -``` \ No newline at end of file +``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx index 87463a8cf3f..8871f1a55ba 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx @@ -1,9 +1,6 @@ In case you have not added the Cognito libraries to your application, be sure to add them: ```yaml - environment: - sdk: ">=2.18.0 <4.0.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/20_installLib.mdx index bd8d918655c..1915c594d61 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/20_installLib.mdx @@ -1,9 +1,6 @@ Add the following dependencies to your `pubspec.yaml` file and install dependencies when asked: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx b/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx index f7b05a3157c..56d59da635c 100644 --- a/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx +++ b/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx @@ -5,9 +5,6 @@ In your project directory, you should first install the necessary dependencies f 2. Add the necessary libraries into the `dependencies` block: ```yaml -environment: - sdk: '>=2.18.0 <4.0.0' - dependencies: amplify_auth_cognito: ^1.0.0 amplify_flutter: ^1.0.0 diff --git a/src/fragments/lib-v1/restapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/restapi/flutter/getting-started/20_installLib.mdx index 8c53d52005e..37263d971e6 100644 --- a/src/fragments/lib-v1/restapi/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/restapi/flutter/getting-started/20_installLib.mdx @@ -1,9 +1,6 @@ Add the following dependencies to your `pubspec.yaml` file and install dependencies when asked. Please keep in mind that Auth plugin is needed for IAM authorization mode, which is default for REST API: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib-v1/storage/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/storage/flutter/getting-started/20_installLib.mdx index c4629c0e637..b98ab0d90aa 100644 --- a/src/fragments/lib-v1/storage/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/storage/flutter/getting-started/20_installLib.mdx @@ -1,10 +1,6 @@ Add the following dependency to your **app**'s `pubspec.yaml` along with others you added above in **Prerequisites**: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib/analytics/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/analytics/flutter/getting-started/20_installLib.mdx index 2be296731a8..c2ae6929167 100644 --- a/src/fragments/lib/analytics/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/analytics/flutter/getting-started/20_installLib.mdx @@ -5,9 +5,6 @@ In your Flutter project directory, open **pubspec.yaml**. Add Analytics by adding these libraries into your dependencies block: ```yaml -environment: - sdk: '>=2.18.0 <4.0.0' - dependencies: amplify_analytics_pinpoint: ^2.0.0 amplify_auth_cognito: ^2.0.0 diff --git a/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx index 0e0a09e07ca..08f767302e3 100644 --- a/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx @@ -3,10 +3,6 @@ Add the following dependencies to your `pubspec.yaml` file and install dependencies when asked: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx b/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx index a4506a67fe6..eead34a283a 100644 --- a/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx +++ b/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx @@ -1,9 +1,6 @@ In case you have not added the Cognito libraries to your application, be sure to add them: ```yaml - environment: - sdk: ">=2.18.0 <4.0.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx index 64f1d56954a..f743690b75b 100644 --- a/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx @@ -1,9 +1,6 @@ Add the following dependencies to your `pubspec.yaml` file and install dependencies when asked: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx b/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx index a74c76ca481..51d0fde18b1 100644 --- a/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx +++ b/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx @@ -1,8 +1,4 @@ ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx b/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx index 81d62c005a9..5b0501bb004 100644 --- a/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx +++ b/src/fragments/lib/push-notifications/flutter/getting_started/40_install_lib.mdx @@ -5,9 +5,6 @@ In your project directory, you should first install the necessary dependencies f 2. Add the necessary libraries into the `dependencies` block: ```yaml -environment: - sdk: '>=2.18.0 <4.0.0' - dependencies: amplify_auth_cognito: ^2.0.0 amplify_flutter: ^2.0.0 diff --git a/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx index 594a96340a3..47fa517c3ee 100644 --- a/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx @@ -1,9 +1,6 @@ Add the following dependencies to your `pubspec.yaml` file and install dependencies when asked. Please keep in mind that Auth plugin is needed for IAM authorization mode, which is default for REST API: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/lib/storage/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/storage/flutter/getting-started/20_installLib.mdx index 80a5bd07d91..0896d193a2f 100644 --- a/src/fragments/lib/storage/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/storage/flutter/getting-started/20_installLib.mdx @@ -1,10 +1,6 @@ Add the following dependency to your **app**'s `pubspec.yaml` along with others you added above in **Prerequisites**: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - dependencies: flutter: sdk: flutter diff --git a/src/fragments/start/getting-started/flutter/setup.mdx b/src/fragments/start/getting-started/flutter/setup.mdx index 67a92e40c17..0d2b24036ae 100644 --- a/src/fragments/start/getting-started/flutter/setup.mdx +++ b/src/fragments/start/getting-started/flutter/setup.mdx @@ -15,9 +15,6 @@ Amplify Flutter is distributed via [pub.dev](https://pub.dev/packages/amplify_fl 1. From your project root directory, find and modify your `pubspec.yaml` and add the Amplify plugins to the project dependencies. ```yaml - environment: - sdk: ">=2.18.0 <4.0.0" - dependencies: amplify_api: ^2.0.0 amplify_auth_cognito: ^2.0.0 diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx index 7859dd8c79e..1bcf07bd384 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx @@ -168,9 +168,6 @@ In your Flutter project directory, open **pubspec.yaml**. Add Analytics by adding these libraries into your dependencies block: ```yaml -environment: - sdk: '>=2.18.0 <4.0.0' - dependencies: amplify_analytics_pinpoint: ^2.0.0 amplify_auth_cognito: ^2.0.0 diff --git a/src/pages/[platform]/build-a-backend/auth/set-up-auth/index.mdx b/src/pages/[platform]/build-a-backend/auth/set-up-auth/index.mdx index b4b14e451bb..067309df213 100644 --- a/src/pages/[platform]/build-a-backend/auth/set-up-auth/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/set-up-auth/index.mdx @@ -368,10 +368,6 @@ flutter pub add amplify_authenticator or you can update your `pubspec.yaml` file with the following ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - dependencies: amplify_flutter: ^2.0.0 amplify_auth_cognito: ^2.0.0 diff --git a/src/pages/[platform]/build-a-backend/data/set-up-data/index.mdx b/src/pages/[platform]/build-a-backend/data/set-up-data/index.mdx index 9140e2379fe..54b043e2981 100644 --- a/src/pages/[platform]/build-a-backend/data/set-up-data/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/set-up-data/index.mdx @@ -292,9 +292,6 @@ Drag and drop the **AmplifyModels** folder into your Xcode project to add the ge From your project root directory, find and modify your **pubspec.yaml** and add the Amplify plugins to the project dependencies. ```yaml title="pubspec.yaml" -environment: - sdk: ">=2.18.0 <4.0.0" - dependencies: // highlight-start amplify_api: ^2.0.0 diff --git a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx index b9249d78725..eae4bbb90de 100644 --- a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx @@ -427,10 +427,6 @@ Note that because the storage category requires auth, you will need to either co Add the following dependency to your **app**'s `pubspec.yaml` along with others you added above in **Prerequisites**: ```yaml -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - dependencies: flutter: sdk: flutter diff --git a/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx index 61c80a2ef2f..d6b8761a673 100644 --- a/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx +++ b/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx @@ -348,9 +348,6 @@ description: "A new Flutter project." publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 -environment: - sdk: '>=3.2.0 <4.0.0' - // highlight-start dependencies: flutter: diff --git a/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx b/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx index 700b0c56e16..c3a7cac57c9 100644 --- a/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx +++ b/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx @@ -146,8 +146,6 @@ description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 -environment: - sdk: '>=3.1.0 <4.0.0' dependencies: // highlight-start amplify_api: ^1.0.0 diff --git a/src/pages/gen1/[platform]/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/gen1/[platform]/build-a-backend/existing-resources/cdk/index.mdx index 76d4b541503..ba212f89a13 100644 --- a/src/pages/gen1/[platform]/build-a-backend/existing-resources/cdk/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/existing-resources/cdk/index.mdx @@ -354,9 +354,6 @@ description: "A new Flutter project." publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 -environment: - sdk: '>=3.2.0 <4.0.0' - // highlight-start dependencies: flutter: diff --git a/src/pages/gen1/[platform]/build-a-backend/existing-resources/cli/index.mdx b/src/pages/gen1/[platform]/build-a-backend/existing-resources/cli/index.mdx index 348dccd08a9..ad9ae21a522 100644 --- a/src/pages/gen1/[platform]/build-a-backend/existing-resources/cli/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/existing-resources/cli/index.mdx @@ -152,8 +152,6 @@ description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 -environment: - sdk: '>=3.1.0 <4.0.0' dependencies: // highlight-start amplify_api: ^2.0.0 diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx index c3c0f2cfdcf..ac6c2c6c6ac 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx @@ -352,9 +352,6 @@ description: "A new Flutter project." publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 -environment: - sdk: '>=3.2.0 <4.0.0' - // highlight-start dependencies: flutter: diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx index c89acf7d066..d78b609c6ba 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx @@ -150,8 +150,6 @@ description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 -environment: - sdk: '>=3.1.0 <4.0.0' dependencies: // highlight-start amplify_api: ^1.0.0 From 112fa77ee5b923471db6c55cfb13ed0acdcf4b1c Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Wed, 8 May 2024 14:36:40 -0400 Subject: [PATCH 47/88] chore: flutter v2 cleanup (#7545) * chore: address upgrade guide comments * chore: remove references to flutter version --- .../lib/analytics/flutter/getting-started/10_preReq.mdx | 4 +--- .../lib/auth/flutter/getting_started/10_preReq.mdx | 2 -- .../lib/datastore/flutter/getting-started/10_preReq.mdx | 6 +----- .../lib/graphqlapi/flutter/getting-started/10_preReq.mdx | 3 +-- src/fragments/lib/project-setup/flutter/prereq/prereq.mdx | 1 - .../project-setup/flutter/upgrade-guide/upgrade-guide.mdx | 8 ++++---- .../lib/restapi/flutter/getting-started/10_preReq.mdx | 3 +-- .../lib/storage/flutter/getting-started/10_preReq.mdx | 4 +--- src/fragments/start/getting-started/flutter/prereq.mdx | 2 +- .../add-aws-services/analytics/set-up-analytics/index.mdx | 4 +--- .../customize-auth-lifecycle/custom-auth-flows/index.mdx | 1 - .../build-a-backend/storage/set-up-storage/index.mdx | 4 +--- src/pages/[platform]/start/quickstart/index.mdx | 2 -- .../start/getting-started/installation/index.mdx | 2 +- src/pages/gen2/start/mobile-support/index.mdx | 1 - 15 files changed, 13 insertions(+), 34 deletions(-) diff --git a/src/fragments/lib/analytics/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/analytics/flutter/getting-started/10_preReq.mdx index 2f5067ce258..25b3facc2a9 100644 --- a/src/fragments/lib/analytics/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/analytics/flutter/getting-started/10_preReq.mdx @@ -1,6 +1,4 @@ -- A Flutter application targeting Flutter SDK >=3.3.0 with Amplify libraries integrated - - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: - An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/fragments/lib/auth/flutter/getting_started/10_preReq.mdx b/src/fragments/lib/auth/flutter/getting_started/10_preReq.mdx index 1b967b963bb..f5aea597eb7 100644 --- a/src/fragments/lib/auth/flutter/getting_started/10_preReq.mdx +++ b/src/fragments/lib/auth/flutter/getting_started/10_preReq.mdx @@ -1,3 +1 @@ -A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated. - Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib/datastore/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/datastore/flutter/getting-started/10_preReq.mdx index 25eaf32221e..ee192c94cde 100644 --- a/src/fragments/lib/datastore/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/datastore/flutter/getting-started/10_preReq.mdx @@ -1,6 +1,2 @@ - [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - -- A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - - An iOS configuration targeting at least iOS 13.0 - - An Android configuration targeting at least Android API level 24 (Android 7.0) or above - - For a full example of please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) +- An iOS configuration targeting at least iOS 13.0 and an Android configuration targeting at least Android API level 24 (Android 7.0). For a full example of please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) diff --git a/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx index eb0c1c4160c..2898b280a8a 100644 --- a/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx @@ -1,7 +1,6 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) -* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 * An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/fragments/lib/project-setup/flutter/prereq/prereq.mdx b/src/fragments/lib/project-setup/flutter/prereq/prereq.mdx index 7742a29feec..fbc8bc76cf5 100644 --- a/src/fragments/lib/project-setup/flutter/prereq/prereq.mdx +++ b/src/fragments/lib/project-setup/flutter/prereq/prereq.mdx @@ -1,3 +1,2 @@ - Install a stable version of [Flutter](https://flutter.dev/docs/get-started/install). - - Use version 3.3.0 or higher - Setup your [IDE](https://flutter.dev/docs/get-started/editor?tab=androidstudio) diff --git a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx index 67fa078e953..5e02e5b5dce 100644 --- a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx +++ b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx @@ -1,8 +1,8 @@ -Amplify Flutter v2 has several changes that may require migration when upgrading from v1. +Amplify Flutter v2 has changes in Auth, Analytics, Storage, GraphQL API & DataStore that may require migration when upgrading from v1. ## Auth -#### Time based OTP Exception Handling +#### Time Based One Time Password (TOTP) Exception Handling In Amplify Flutter v1, `Amplify.Auth.verifyTotpSetup()` throws an `EnableSoftwareTokenMfaException` if the provided code was incorrect. In Amplify Flutter v2 a `CodeMismatchException` is thrown. @@ -37,7 +37,7 @@ In Amplify Flutter v1, the `autoFlushEventsInterval` value was configured manual In Amplify Flutter v1, files operations were performed based on a `key` and an `AccessLevel`. Amplify translated these values to a full file path. The `AccessLevel` (guest, protected, or private) determined what prefix to use (`“public/”`, `“protected//”`, or `“private//”`). Amplify Flutter v1 forms the full path using the prefix and the provided key. -Amplify Flutter v2 allows for full control of the path of the storage object. Storage paths can be constructed either from a static string or from the current users identity id using the StoragePath class. +Amplify Flutter v2 allows for full control of the path of the storage object. Storage paths can be constructed either from a static string or from the current users identity id using the `StoragePath` class. To migrate from v1 to v2, replace all uses of `key` with `path` and remove uses of `StorageAccessLevel`. The path should include the prefix that was previously added by Amplify automatically. @@ -110,7 +110,7 @@ Amplify.Storage.uploadFile( #### Prefix Resolver has been removed -With the introduction of StoragePath, there is no need to override the libraries behavior for the prefix. +With the introduction of `StoragePath`, there is no need for a prefix resolver. Simply specify the full path when constructing the `StoragePath`. #### Delimiter has been moved to the List API Options diff --git a/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx index eb0c1c4160c..2898b280a8a 100644 --- a/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx @@ -1,7 +1,6 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) -* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 * An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/fragments/lib/storage/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/storage/flutter/getting-started/10_preReq.mdx index 9338778fc30..094bbfa9d3d 100644 --- a/src/fragments/lib/storage/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/storage/flutter/getting-started/10_preReq.mdx @@ -1,6 +1,4 @@ -* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 * An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/fragments/start/getting-started/flutter/prereq.mdx b/src/fragments/start/getting-started/flutter/prereq.mdx index 7af15482fc0..21cb28b227b 100644 --- a/src/fragments/start/getting-started/flutter/prereq.mdx +++ b/src/fragments/start/getting-started/flutter/prereq.mdx @@ -1 +1 @@ -- [Flutter](https://flutter.dev/docs/get-started/install) version 3.3 or higher +- [Flutter](https://flutter.dev/docs/get-started/install) diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx index 1bcf07bd384..78130859cd0 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx @@ -60,9 +60,7 @@ For more information on how to use the `visionos-preview` branch, see [Platform -- A Flutter application targeting Flutter SDK >=3.3.0 with Amplify libraries integrated - - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: - An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/pages/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/index.mdx b/src/pages/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/index.mdx index 0224b44a403..5d1d5bd8478 100644 --- a/src/pages/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/index.mdx @@ -731,7 +731,6 @@ RxAmplify.Auth.signIn("username", "password", options) The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow. ## Prerequisites -A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated. Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#platform-setup) for more details on platform specific setup. diff --git a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx index eae4bbb90de..6477829867a 100644 --- a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx @@ -410,9 +410,7 @@ Note that because the storage category requires auth, you will need to either co ### Prerequisites -* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 * An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/pages/[platform]/start/quickstart/index.mdx b/src/pages/[platform]/start/quickstart/index.mdx index ba55c5817fa..aefef83ded8 100644 --- a/src/pages/[platform]/start/quickstart/index.mdx +++ b/src/pages/[platform]/start/quickstart/index.mdx @@ -1296,8 +1296,6 @@ Before you get started, make sure you have the following installed: - [git](https://git-scm.com/) v2.14.1 or later - You will also need to [create an AWS Account](https://portal.aws.amazon.com/billing/signup). Note that AWS Amplify is part of the [AWS Free Tier](https://aws.amazon.com/amplify/pricing/). - Configure your AWS account to use with Amplify [instructions](/[platform]/start/account-setup/). -- A Flutter version higher than 3.3.0 - You can follow the [official documentation](https://flutter.dev/docs/get-started/install) to install Flutter on your machine and check the [editor documentation](https://docs.flutter.dev/get-started/editor) for setting up your editor. diff --git a/src/pages/gen1/[platform]/start/getting-started/installation/index.mdx b/src/pages/gen1/[platform]/start/getting-started/installation/index.mdx index 829eaf905fe..8f6d251bd85 100644 --- a/src/pages/gen1/[platform]/start/getting-started/installation/index.mdx +++ b/src/pages/gen1/[platform]/start/getting-started/installation/index.mdx @@ -47,7 +47,7 @@ Before you begin, make sure you have the following installed: -- [Flutter](https://flutter.dev/docs/get-started/install) version 3.3 or higher +- [Flutter](https://flutter.dev/docs/get-started/install) diff --git a/src/pages/gen2/start/mobile-support/index.mdx b/src/pages/gen2/start/mobile-support/index.mdx index 847d7c22ab7..c6e49d3786c 100644 --- a/src/pages/gen2/start/mobile-support/index.mdx +++ b/src/pages/gen2/start/mobile-support/index.mdx @@ -481,7 +481,6 @@ For publishing the changes to cloud, you need to create a remote git repository. - For using Flutter with Amplify Gen2, you need to have a Flutter version higher than 3.3.0 and setup the editor of you You can follow the [official documentation](https://flutter.dev/docs/get-started/install) to install Flutter on your machine and check the [editor documentation](https://docs.flutter.dev/get-started/editor) for setting up your editor. From 8cce5e390eca7f8f5525964b0d04224c0ce61301 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 8 May 2024 11:48:41 -0700 Subject: [PATCH 48/88] fix: correct the sendUserAttributeConfirmationCode API name (#7547) --- .../lib-v1/auth/flutter/user_attributes/40_resend_code.mdx | 2 +- .../lib-v1/auth/native_common/user_attributes/common.mdx | 4 ++-- .../lib/auth/flutter/user_attributes/40_resend_code.mdx | 2 +- .../lib/auth/native_common/user_attributes/common.mdx | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx b/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx index 160240595ae..168c7783efe 100644 --- a/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx +++ b/src/fragments/lib-v1/auth/flutter/user_attributes/40_resend_code.mdx @@ -1,7 +1,7 @@ ```dart Future resendVerificationCode() async { try { - final result = await Amplify.Auth.resendUserAttributeConfirmationCode( + final result = await Amplify.Auth.sendUserAttributeConfirmationCode( userAttributeKey: AuthUserAttributeKey.email, ); _handleCodeDelivery(result.codeDeliveryDetails); diff --git a/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx b/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx index 68f816e8083..d57e6101c79 100644 --- a/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx @@ -46,9 +46,9 @@ import flutter8 from '/src/fragments/lib-v1/auth/flutter/user_attributes/30_conf -## Resend verification code +## Send user attribute verification code -If the code has expired or the user needs to resend the confirmation code, invoke the resend api as shown below: +If an attribute needs to be verified while the user is authenticated, invoke the send api as shown below: import ios9 from '/src/fragments/lib-v1/auth/ios/user_attributes/40_resend_code.mdx'; diff --git a/src/fragments/lib/auth/flutter/user_attributes/40_resend_code.mdx b/src/fragments/lib/auth/flutter/user_attributes/40_resend_code.mdx index 160240595ae..168c7783efe 100644 --- a/src/fragments/lib/auth/flutter/user_attributes/40_resend_code.mdx +++ b/src/fragments/lib/auth/flutter/user_attributes/40_resend_code.mdx @@ -1,7 +1,7 @@ ```dart Future resendVerificationCode() async { try { - final result = await Amplify.Auth.resendUserAttributeConfirmationCode( + final result = await Amplify.Auth.sendUserAttributeConfirmationCode( userAttributeKey: AuthUserAttributeKey.email, ); _handleCodeDelivery(result.codeDeliveryDetails); diff --git a/src/fragments/lib/auth/native_common/user_attributes/common.mdx b/src/fragments/lib/auth/native_common/user_attributes/common.mdx index c211bb825e2..159cc1035f3 100644 --- a/src/fragments/lib/auth/native_common/user_attributes/common.mdx +++ b/src/fragments/lib/auth/native_common/user_attributes/common.mdx @@ -46,9 +46,9 @@ import flutter8 from '/src/fragments/lib/auth/flutter/user_attributes/30_confirm -## Resend verification code +## Send user attribute verification code -If the code has expired or the user needs to resend the confirmation code, invoke the resend api as shown below: +If an attribute needs to be verified while the user is authenticated, invoke the send api as shown below: import ios9 from '/src/fragments/lib/auth/ios/user_attributes/40_resend_code.mdx'; From 02cb5409053b58773101083d5174a31183b1403b Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Thu, 9 May 2024 18:17:39 -0500 Subject: [PATCH 49/88] fix: added custom primary key migration --- cspell.json | 1 + .../flutter/upgrade-guide/upgrade-guide.mdx | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/cspell.json b/cspell.json index 98977719e51..8a08d7aa242 100644 --- a/cspell.json +++ b/cspell.json @@ -1122,6 +1122,7 @@ "resolver's", "resourcename", "Resources.S3Bucket.Properties.BucketName", + "respectprimarykeyattributesonconnectionfield", "RESTAPI", "RESTENDPOINT", "resubscription", diff --git a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx index 5e02e5b5dce..1ab3a2bc1a0 100644 --- a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx +++ b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx @@ -173,3 +173,36 @@ Or + Post.BLOG.eq(BlogModelIdentifier(id: "1234")) ``` + +#### Support For Only Custom Primary Key Enabled Back-Ends + +Amplify Flutter V2 no longer supports the Amplify CLI feature flag `respectprimarykeyattributesonconnectionfield` to be **disabled**. + +_If this feature flag is already enabled in your current project you can skip this section._ + +| `graphql.schema` | Feature Flag On | Feature Flag off | +| -----------------| :---------------: | ---------------- | +| **has `@primaryKey`**| ✅ | Start at step 1. | +| **no `@primaryKey`** | ✅ | Skip to step 4. | + +If the feature flag is turned off **AND** your model schema has the `@primaryKey` annotation, please follow these migration steps. + +If the feature flag is turned off, but your `graphql.schema` does not contain references to `@primaryKey`, jump to step 4. + +1. Remove `@primaryKey` references from your `graphql.schema` +2. Run `$ amplify push` +3. Run `$ amplify codegen models` +4. Enabled the feature flag: + 1. Locate `amplify/cli.json` inside your project + 2. Enable the feature flag: +```json +"features": { + "graphqltransformer": { + ... + "respectprimarykeyattributesonconnectionfield": true, + ... + }, +} +``` +5. Run `$ amplify push` +6. Run `$ amplify codegen models` From d0bca49318955d5cc09e04dfa904050f860d5853 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Fri, 10 May 2024 09:06:34 -0500 Subject: [PATCH 50/88] fix: CPK table verbiage --- .../project-setup/flutter/upgrade-guide/upgrade-guide.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx index 1ab3a2bc1a0..ceeb317e427 100644 --- a/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx +++ b/src/fragments/lib/project-setup/flutter/upgrade-guide/upgrade-guide.mdx @@ -181,9 +181,9 @@ Amplify Flutter V2 no longer supports the Amplify CLI feature flag `respectprima _If this feature flag is already enabled in your current project you can skip this section._ | `graphql.schema` | Feature Flag On | Feature Flag off | -| -----------------| :---------------: | ---------------- | -| **has `@primaryKey`**| ✅ | Start at step 1. | -| **no `@primaryKey`** | ✅ | Skip to step 4. | +| -----------------| --------------- | ---------------- | +| **has `@primaryKey`**| No further action required | Start at step 1. | +| **no `@primaryKey`** |No further action required | Skip to step 4. | If the feature flag is turned off **AND** your model schema has the `@primaryKey` annotation, please follow these migration steps. From 2b997447b287a5c3144347b22d18befb6aab7628 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 10 May 2024 09:56:52 -0700 Subject: [PATCH 51/88] chore: fix typo in admin_actions name, use one header for all platforms for graphql advanced workflows, fix formatting for inline filter in existing resources --- .../lib-v1/graphqlapi/existing-resources.mdx | 13 ++----------- .../native_common/advanced-workflows/common.mdx | 6 ------ .../auth/{admin_actions => admin-actions}/index.mdx | 0 3 files changed, 2 insertions(+), 17 deletions(-) rename src/pages/[platform]/prev/build-a-backend/auth/{admin_actions => admin-actions}/index.mdx (100%) diff --git a/src/fragments/lib-v1/graphqlapi/existing-resources.mdx b/src/fragments/lib-v1/graphqlapi/existing-resources.mdx index 6ce899b8f1f..ec626f0a5d4 100644 --- a/src/fragments/lib-v1/graphqlapi/existing-resources.mdx +++ b/src/fragments/lib-v1/graphqlapi/existing-resources.mdx @@ -18,23 +18,14 @@ Existing AWS AppSync resources can be used with the Amplify Libraries by referen } ``` - + - **API NAME**: Friendly name for the API (e.g., _api_) - **endpoint**: The HTTPS endpoint of the AWS AppSync API (e.g. `https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql`). [Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. `https://api.yourdomain.com/graphql`). Custom domain names can have any format, but must end with `/graphql` (see https://graphql.org/learn/serving-over-http/#uris-routes). - **region**: AWS Region where the resources are provisioned (e.g. _us-east-1_) - **authorizationType**: Authorization mode for accessing the API. This can be one of: `AMAZON_COGNITO_USER_POOLS`, `AWS_IAM`, `OPENID_CONNECT`, or `API_KEY`. Each mode requires additional configuration parameters. See [Configure authorization modes](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes) for details. - + - **API NAME**: Friendly name for the API (e.g., _api_) - **endpoint**: The HTTPS endpoint of the AWS AppSync API (e.g. `https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql`). [Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. `https://api.yourdomain.com/graphql`). Custom domain names can have any format, but must end with `/graphql` (see https://graphql.org/learn/serving-over-http/#uris-routes). - **region**: AWS Region where the resources are provisioned (e.g. _us-east-1_) diff --git a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx index 3fff244ec8a..34bec1704bf 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx @@ -138,13 +138,7 @@ import flutter5 from '/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflow - -## Combining Multiple Operations - - - ## Combining multiple GraphQL operations in a single request - When you want to perform more than one operation in a single request, you can place them within the same document. For example, to retrieve a Post and a Todo diff --git a/src/pages/[platform]/prev/build-a-backend/auth/admin_actions/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx similarity index 100% rename from src/pages/[platform]/prev/build-a-backend/auth/admin_actions/index.mdx rename to src/pages/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx From 58acad33f5254c54f8623e49bcf6421d4a7b4bf4 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 10 May 2024 11:52:00 -0700 Subject: [PATCH 52/88] chore: remove duplicate pages that don't exist on gen2 and edit paths --- src/directory/directory.mjs | 11 +- .../auth/admin-actions/index.mdx | 369 ----- .../auth/existing-resources-no-cli/index.mdx | 33 - .../auth/import-existing-resources/index.mdx | 111 -- .../auth/override-cognito/index.mdx | 185 --- .../auth/user-group-management/index.mdx | 117 -- .../existing-resources/cdk/index.mdx | 832 ----------- .../existing-resources/cli/index.mdx | 1010 ------------- .../existing-resources/index.mdx | 29 - .../functions/build-options/index.mdx | 141 -- .../functions/configure-options/index.mdx | 114 -- .../functions/environment-variables/index.mdx | 86 -- .../functions/graphql-from-lambda/index.mdx | 504 ------- .../prev/build-a-backend/functions/index.mdx | 36 - .../functions/layers/index.mdx | 229 --- .../functions/secrets/index.mdx | 105 -- .../functions/set-up-function/index.mdx | 161 --- .../batch-put-custom-resolver/index.mdx | 151 -- .../graphqlapi/best-practice/index.mdx | 36 - .../query-with-sorting/index.mdx | 132 -- .../warehouse-management/index.mdx | 552 ------- .../client-code-generation/index.mdx | 241 ---- .../index.mdx | 1014 ------------- .../index.mdx | 262 ---- .../custom-business-logic/index.mdx | 954 ------------- .../customize-authorization-modes/index.mdx | 49 - .../customize-authorization-rules/index.mdx | 933 ------------ .../graphqlapi/data-modeling/index.mdx | 1262 ---------------- .../index.mdx | 500 ------- .../graphqlapi/schema-evolution/index.mdx | 141 -- .../search-and-result-aggregations/index.mdx | 479 ------- .../graphqlapi/troubleshooting/index.mdx | 92 -- .../restapi/configure-rest-api/index.mdx | 286 ---- .../restapi/override-api-gateway/index.mdx | 318 ----- .../restapi/test-api/index.mdx | 178 --- .../storage/configure-storage/index.mdx | 186 --- .../build-a-backend/storage/import/index.mdx | 254 ---- .../index.mdx | 110 -- src/pages/gen2/start/mobile-support/index.mdx | 1267 ----------------- 39 files changed, 4 insertions(+), 13466 deletions(-) delete mode 100644 src/pages/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/existing-resources/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/functions/build-options/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/functions/configure-options/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/functions/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/functions/layers/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/functions/secrets/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx delete mode 100644 src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx delete mode 100644 src/pages/gen2/start/mobile-support/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index c212ae44ee2..f7874f4a12f 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -295,6 +295,9 @@ export const directory = { { path: 'src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx' }, + { + path: 'src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx' + }, { path: 'src/pages/[platform]/build-a-backend/storage/authorization/index.mdx' }, @@ -1686,7 +1689,7 @@ export const directory = { path: 'src/pages/gen1/[platform]/build-a-backend/troubleshooting/migrate-from-javascript-v5-to-v6/index.mdx' }, { - path: 'src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx' + path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx' }, { path: 'src/pages/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/index.mdx' @@ -1757,18 +1760,12 @@ export const directory = { { path: 'src/pages/gen1/[platform]/build-ui/uibuilder/slots/index.mdx' }, - { - path: 'src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx' - }, { path: 'src/pages/gen1/[platform]/build-ui/uibuilder/theming/index.mdx' }, { path: 'src/pages/gen1/[platform]/build-ui/uibuilder/responsive/index.mdx' }, - { - path: 'src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx' - }, { path: 'src/pages/gen1/[platform]/build-ui/uibuilder/override/index.mdx' }, diff --git a/src/pages/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx deleted file mode 100644 index f07d733d007..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx +++ /dev/null @@ -1,369 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Set up admin actions', - description: 'Learn how to expose administrative actions for your Cognito User Pool to your end user applications.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/auth/admin-actions/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -Admin Actions allow you to execute queries and operations against users and groups in your Cognito user pool. - -For example, the ability to list all users in a Cognito User Pool may provide useful for the administrative panel of an app if the logged-in user is a member of a specific Group called "Admins". - -> This is an advanced feature that is not recommended without an understanding of the underlying architecture. The associated infrastructure which is created is a base designed for you to customize for your specific business needs. We recommend removing any functionality which your app does not require. - -The Amplify CLI can setup a REST endpoint with secure access to a Lambda function running with limited permissions to the User Pool if you wish to have these capabilities in your application, and you can choose to expose the actions to all users with a valid account or restrict to a specific User Pool Group. - -## Enable Admin Queries - - - - -```bash -amplify add auth -``` - -Select the option to go through Manual configuration. - -```console - Do you want to use the default authentication and security configuration? (Use arrow keys) - Default configuration - Default configuration with Social Provider (Federation) -❯ Manual configuration - I want to learn more. -``` - -Go through the rest of the configuration steps until you reach the following prompts: - -```console -? Do you want to add User Pool Groups? Yes -? Provide a name for your user pool group: Admins -? Do you want to add another User Pool Group No -✔ Sort the user pool groups in order of preference · Admins -? Do you want to add an admin queries API? Yes -? Do you want to restrict access to the admin queries API to a specific Group? Yes -? Select the group to restrict access with: (Use arrow keys) -❯ Admins - Enter a custom group -``` - -Continue with the rest of the prompts to finish the configuration. - - - - - -```bash -amplify update auth -``` - -Select the option to Create or update Admin queries API. - -```console -What do you want to do? Create or update Admin queries API -? Do you want to restrict access to the admin queries API to a specific Group Yes -? Select the group to restrict access with: (Use arrow keys) -❯ Admins - Enter a custom group -``` - - - - - - - -If you don't have any User Pool Groups, you will need to select `Enter a custom group`. - - - -When ready, run `amplify push` to deploy the changes. - -This will configure an API Gateway endpoint with a Cognito Authorizer that accepts an Access Token, which is used by a Lambda function to perform actions against the User Pool. The function is example code which you can use to remove, add, or alter functionality based on your business case by editing it in the `amplify/backend/function/AdminQueriesXXX/src` directory and running an `amplify push` to deploy your changes. If you choose to restrict actions to a specific Group, custom middleware in the function will prevent any actions unless the user is a member of that Group. - -## Admin Queries API - -The default routes and their functions, HTTP methods, and expected parameters are below - -- `addUserToGroup`: Adds a user to a specific Group. Expects `username` and `groupname` in the POST body. -- `removeUserFromGroup`: Removes a user from a specific Group. Expects `username` and `groupname` in the POST body. -- `confirmUserSignUp`: Confirms a users signup. Expects `username` in the POST body. -- `disableUser`: Disables a user. Expects `username` in the POST body. -- `enableUser`: Enables a user. Expects `username` in the POST body. -- `getUser`: Gets specific user details. Expects `username` as a GET query string. -- `listUsers`: Lists all users in the current Cognito User Pool. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. -- `listGroups`: Lists all groups in the current Cognito User Pool. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. -- `listGroupsForUser`: Lists groups to which current user belongs to. Expects `username` as a GET query string. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. -- `listUsersInGroup`: Lists users that belong to a specific group. Expects `groupname` as a GET query string. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. -- `signUserOut`: Signs a user out from User Pools, but only if the call is originating from that user. Expects `username` in the POST body. - -## Example - -To leverage this functionality in your app you would call the appropriate route from `Amplify.API` after signing in. The following example adds the user "richard" to the Editors Group and then list all members of the Editors Group with a pagination limit of 10: - - - - - -```js -import React from 'react' -import { Amplify } from 'aws-amplify'; -import { fetchAuthSession } from 'aws-amplify/auth'; -import { post } from 'aws-amplify/api' -import { withAuthenticator } from '@aws-amplify/ui-react'; -import '@aws-amplify/ui-react/styles.css'; - -import amplifyconfig from './amplifyconfiguration.json'; -Amplify.configure(amplifyconfig); - -const client = generateClient() - -async function addToGroup() { - let apiName = 'AdminQueries'; - let path = '/addUserToGroup'; - let options = { - body: { - "username" : "richard", - "groupname": "Editors" - }, - headers: { - 'Content-Type' : 'application/json', - Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` - } - } - return post({apiName, path, options}); -} - -async function listEditors(limit){ - let apiName = 'AdminQueries'; - let path = '/listUsersInGroup'; - let options = { - queryStringParameters: { - "groupname": "Editors", - "limit": limit, - }, - headers: { - 'Content-Type' : 'application/json', - Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` - } - } - const response = await get({apiName, path, options}); - return response; -} - -function App() { - return ( -
- - -
- ); -} - -export default withAuthenticator(App); -``` - -
- - - -1. Initialize Amplify API. Refer to [Set up Amplify REST API](/[platform]/build-a-backend/restapi/set-up-rest-api/) for more details. - -You should have the initialization code including the imports: - -```swift -import Amplify -import AWSCognitoAuthPlugin -import AWSAPIPlugin -``` - -and code that adds `AWSCognitoAuthPlugin` and `AWSAPIPlugin` before configuring Amplify. - -```swift -try Amplify.add(plugin: AWSCognitoAuthPlugin()) -try Amplify.add(plugin: AWSAPIPlugin()) -try Amplify.configure() -``` - -2. Sign in using `Amplify.Auth`. See [Amplify.Auth](/[platform]/build-a-backend/auth/set-up-auth/) to learn more about signing up and signing in a user. - -3. Use the following in your app to add a user to the Group. - -```swift -func addToGroup(username: String, groupName: String) async { - let path = "/addUserToGroup" - let body = "{\"username\":\"\(username)\",\"groupname\":\"\(groupName)\"}".data(using: .utf8) - let request = RESTRequest(path: path, body: body) - do { - let data = try await Amplify.API.post(request: request) - print("Response Body: \(String(decoding: data, as: UTF8.self))") - } catch { - if case let .httpStatusError(statusCode, response) = error as? APIError, - let awsResponse = response as? AWSHTTPURLResponse, - let responseBody = awsResponse.body { - print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") - } - } -} - -await addToGroup(username: "richard", groupName: "Editors") -``` - -4. Use the following to list the users in the Group. - -```swift -func listEditors(groupName: String, limit: Int, nextToken: String? = nil) async { - let path = "/listUsersInGroup" - var query = [ - "groupname": groupName, - "limit": String(limit) - ] - if let nextToken = nextToken { - query["token"] = nextToken - } - - let request = RESTRequest(path: path, queryParameters: query, body: nil) - do { - let data = try await Amplify.API.get(request: request) - print("Response Body: \(String(decoding: data, as: UTF8.self))") - } catch { - if case let .httpStatusError(statusCode, response) = error as? APIError, - let awsResponse = response as? AWSHTTPURLResponse, - let responseBody = awsResponse.body { - print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") - } - } -} - -await listEditors(groupName: "Editors", limit: 10) -``` - -**Note: Cognito User Pool with HostedUI** - -The Admin Queries API configuration in **amplifyconfiguration.json** will have the endpoint's authorization type set to `AMAZON_COGNITO_USER_POOLS`. With this authorization type, `Amplify.API` will perform the request with the access token. However, when using HostedUI, the app may get unauthorized responses despite being signed in, and will require using the ID Token. Set the authorizationType to "NONE" and add a custom interceptor to return the ID Token. - -```json -{ - "awsAPIPlugin": { - "[YOUR-RESTENDPOINT-NAME]": { - "endpointType": "REST", - "endpoint": "[YOUR-REST-ENDPOINT]", - "region": "[REGION]", - "authorizationType": "NONE" - } - } -} -``` - - -If you perform additional updates to your resources using Amplify CLI, the authorizationType will be reverted back to `AMAZON_COGNITO_USER_POOLS`. Make sure to update this back to `NONE`. - - - -Add a custom interceptor to the API -```swift -try Amplify.configure() -try Amplify.API.add(interceptor: MyCustomInterceptor(), for: "[YOUR-RESTENDPOINT-NAME]") -``` - -Set up the custom interceptor to return the ID token for the request. - -```swift -import Amplify -import AWSPluginsCore - -class MyCustomInterceptor: URLRequestInterceptor { - func latestAuthToken() async throws -> String { - guard let session = try await Amplify.Auth.fetchAuthSession() as? AuthCognitoTokensProvider else { - throw AuthError.unknown("Could not retrieve Cognito token") - } - - let tokens = try session.getCognitoTokens().get() - return tokens.idToken - } - - func intercept(_ request: URLRequest) async throws -> URLRequest { - var request = request - do { - let token = try await latestAuthToken() - request.setValue(token, forHTTPHeaderField: "authorization") - } catch { - throw APIError.operationError("Failed to retrieve Cognito UserPool token.", "", error) - } - return request - } -} -``` - -
- -## Adding Admin Actions - -To add additional admin actions that are not included by default but are enabled by Amazon Cognito, you will need to update the Lambda function code that is generated for you. The change will include adding a route handler for the action and creating a route for it. You will then associate the route handler to the route within the [Express](https://expressjs.com/) app. - -Below is an example of how to add an admin action that will allow you to [update a user's attributes](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminUpdateUserAttributes.html). -```js -async function updateUserAttributes(username, attributes) { - - const params = { - Username: username, - UserAttributes: attributes, - UserPoolId: 'STRING_VALUE', - }; - - console.log(`Attempting to update ${username} attributes`); - - try { - await cognitoIdentityServiceProvider.adminUpdateUserAttributes(params).promise(); - console.log(`Success updating ${username} attributes`); - return { - message: `Success updating ${username} attributes`, - }; - } catch (err) { - console.log(err); - throw err; - } -} -``` -Once the route handler is defined, you will then add a route with the correct HTTP method to the Express app and associate the route handler to the route. Be sure to make the route unique. - -Below is an example of how you can add a `POST` route named `/updateUserAttributes` and associate the above route handler to it. -```js -app.post('/updateUserAttributes', async (req, res, next) => { - if (!req.body.username || !req.body.attributes) { - const err = new Error('username and attributes are required'); - err.statusCode = 400; - return next(err); - } - - try { - const response = await updateUserAttributes(req.body.username, req.body.attributes); - res.status(200).json(response); - } catch (err) { - next(err); - } -}); -``` diff --git a/src/pages/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx deleted file mode 100644 index c178748008e..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx +++ /dev/null @@ -1,33 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Use existing resources without the CLI', - description: - 'Configure the Amplify Libraries to use existing Amazon Cognito resources by referencing them in your configuration.', - platforms: ['flutter', 'swift', 'android'] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import android0 from '/src/fragments/lib/auth/existing-resources.mdx'; - - - -import ios1 from '/src/fragments/lib/auth/existing-resources.mdx'; - - - -import flutter2 from '/src/fragments/lib/auth/existing-resources.mdx'; - - diff --git a/src/pages/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx deleted file mode 100644 index ecf4d878240..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx +++ /dev/null @@ -1,111 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Use an existing Cognito User Pool and Identity Pool', - description: 'Configure the Amplify CLI to use existing Amazon Cognito User Pool and Identity Pool resources as an authentication and authorization mechanism for other Amplify categories (API, Storage, and more).', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/auth/import-existing-resources/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -Import existing Amazon Cognito resources into your Amplify project. Get started by running `amplify import auth` command to search for & import an existing Cognito User Pool & Identity Pool in your account. - -```bash -amplify import auth -``` - -The `amplify import auth` command will: - -- automatically populate your Amplify Library configuration files (aws-exports.js, amplifyconfiguration.json) with your chosen Amazon Cognito resource information -- provide your designated existing Cognito resource as the authentication & authorization mechanism for all auth-dependent categories (API, Storage and more) -- enable Lambda functions to access the chosen Cognito resource if you permit it - -Make sure to run `amplify push` to complete the import process and deploy this backend change to the cloud. - -This feature is particularly useful if you're trying to: - -- enable Amplify categories (such as API, Storage, and function) for your existing user base; -- incrementally adopt Amplify for your application stack; -- independently manage Cognito resources while working with Amplify. - -> Note: Amplify does not manage the lifecycle of an imported resource. - -## Import an existing Cognito User Pool - -Select the "Cognito User Pool only" option when you've run `amplify import auth`. In order to successfully import your User Pool, your User Pools require at least one app client with the following conditions: - -- *A "Web app client"*: an app client **without** a client secret - -Run `amplify push` to complete the import procedure. - -import attributesCallout from "/src/fragments/common/writable-vs-mutable-attributes.mdx"; - - - - - -Ensure that the hosted UI for an app client has a sign-out URL defined as omitting this may cause the Amplify CLI to not generate the OAuth `scopes`, `redirectSignIn`, `redirectSignOut` and `responseType` in the `aws-exports.js` file. - -If the Cognito user pool has native and web client defined ensure the clients have matching OAuth properties. - - - -## Import an existing Identity Pool - -Select the "Cognito User Pool and Identity Pool" option when you've run `amplify import auth`. In order to successfully import your Identity Pool, it must have both of the User Pool app clients fulfilling [these requirements](#import-an-existing-cognito-user-pool) associated as an authentication provider. - -Your Identity Pool needs: - -- an Authenticated Role with a trust relationship to your Identity Pool -- an Unauthenticated Role with a trust relationship to your Identity Pool - -These roles are usually automatically configured when you create a new Identity Pool enabling "Unauthenticated" access and have a Cognito User Pool as an authentication provider. - -Amplify CLI will update the policies attached to the roles to ensure Amplify categories function correctly. For example, enabling Storage for authenticated & guest users will add private, protected, public, read and upload permissions for the S3 bucket to the unauthenticated & authenticated role. - -Run `amplify push` to complete the import procedure. - -## Multi-environment support - -When you create a new environment through `amplify env add`, Amplify CLI will assume by default that you're managing your app's Cognito resources outside of an Amplify project. You'll be asked to either import a different Cognito resource or maintain the same Cognito resource for your app's auth category. - -If you want to have Amplify manage your auth resources in a new environment, run `amplify remove auth` to unlink the imported Cognito resource and `amplify add auth` to create new Amplify-managed auth resources in the new environment. - -## Unlink an existing Cognito User Pool or Identity Pool - -In order to unlink your existing Cognito resource run `amplify remove auth`. This will only unlink the Cognito resource referenced from the Amplify project. It will not delete the Cognito resource itself. - -Run `amplify push` to complete the unlink procedure. - -## Add Environmental Variables to Amplify Console Build - -In order to successfully build your application with Amplify Console add the following environmental variables to your build environment: - -|Environment Variable|Description| -|-|-| -|AMPLIFY_USERPOOL_ID|The ID for the Amazon Cognito user pool imported for auth| -|AMPLIFY_WEBCLIENT_ID|The ID for the app client to be used by web applications. The app client must be configured with access to the Amazon Cognito user pool specified by the AMPLIFY_USERPOOL_ID environment variable.| -|AMPLIFY_NATIVECLIENT_ID|The ID for the app client to be used by native applications. The app client must be configured with access to the Amazon Cognito user pool specified by the AMPLIFY_USERPOOL_ID environment variable.| -|AMPLIFY_IDENTITYPOOL_ID|The ID for the Amazon Cognito identity pool| diff --git a/src/pages/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx deleted file mode 100644 index 6b6303d0afd..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx +++ /dev/null @@ -1,185 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Override Amplify-generated Cognito resources', - description: "The 'amplify override auth' command generates a developer-configurable 'overrides' TypeScript file that provides Amplify-generated Cognito resources as CDK constructs. For example, developers can set auth settings that are not directly available in the Amplify CLI workflow, such as the number of valid days for a temporary password.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter' - ], - canonicalPath: '/javascript/build-a-backend/auth/override-cognito/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -```bash -amplify override auth -``` - -Run the command above to override Amplify-generated auth resources including Amazon Cognito user pool, identity pool, user pool groups, and more. - -The command creates a new `overrides.ts` file under `amplify/backend/auth//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). - -## Customize Amplify-generated Cognito auth resources - -Apply all the overrides in the `override(...)` function. For example, to update the temporary password validity days for your Cognito user pool: - -```ts -import { AmplifyAuthCognitoStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyAuthCognitoStackTemplate) { - resources.userPool.policies = { // Set the user pool policies - passwordPolicy: { - ...resources.userPool.policies["passwordPolicy"], // Carry over existing settings - temporaryPasswordValidityDays: 3 // Add new setting not provided Amplify's default - } - } -} -``` - -Or add a custom attribute to your Cognito user pool: - - - -Removing or adding an attribute on a Cognito userpool schema including default attributes (e.g. `email`) will cause errors such as -`Invalid AttributeDataType input, consider using the provided AttributeDataType enum` as CloudFormation interprets this as schema change. - - - - - -Custom attributes can not be renamed or deleted after you create them. - - - -```ts -import { AmplifyAuthCognitoStackTemplate } from '@aws-amplify/cli-extensibility-helper' - -export function override(resources: AmplifyAuthCognitoStackTemplate) { - const myCustomAttribute = { - attributeDataType: 'String', - developerOnlyAttribute: false, - mutable: true, - name: 'my_custom_attribute', - required: false, - } - resources.userPool.schema = [ - ...(resources.userPool.schema as any[]), // Carry over existing attributes (example: email) - myCustomAttribute, - ] -} -``` - -You can override the following auth resources that Amplify generates: - -|Amplify-generated resource|Description| -|-|-| -|[customMessageConfirmationBucket](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html)|S3 bucket used for custom message triggers| -|[snsRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|SNS role for sending authentication-related messages| -|[userPool](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html)|The Cognito user pool that enables user sign-up and sign-in| -|[userPoolClientWeb](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)|A Cognito user pool client for web apps| -|[userPoolClient](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)|A Cognito user pool client for mobile apps| -|[identityPool](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypool.html)|A Cognito identity pool to federate identities| -|[identityPoolRoleMap](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypoolroleattachment.html)|Role mapping for authenticated and unauthenticated user roles| -|[lambdaConfigPermissions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html)|Permissions for Lambda function to access Cognito user pool and identity pool | -|[lambdaTriggerPermissions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM policy attached to Cognito Lambda triggers| -|[userPoolClientLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to fetch app client secret from user pool client| -|[userPoolClientRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|IAM Role for Lambda function to fetch app client secret from user pool client| -|[userPoolClientLambdaPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to fetch app client secret from user pool client| -|[userPoolClientLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to fetch app client secret from user pool client| -|[userPoolClientInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to fetch app client secret from user pool client| -|[hostedUICustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable Cognito user pool Hosted UI login| -|[hostedUICustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to enable Cognito user pool Hosted UI login| -|[hostedUICustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to enable Cognito user pool Hosted UI login| -|[hostedUICustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable Cognito user pool Hosted UI login| -|[hostedUIProvidersCustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to configure Hosted UI with 3rd party identity providers| -|[hostedUIProvidersCustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to configure Hosted UI with 3rd party identity provider| -|[hostedUIProvidersCustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to configure Hosted UI with 3rd party identity provider| -|[hostedUIProvidersCustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to configure Hosted UI with 3rd party identity provider| -|[oAuthCustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable OAuth| -|[oAuthCustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for OAuth custom CloudFormation resource| -|[oAuthCustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for OAuth Lambda function| -|[oAuthCustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable OAuth| -|[mfaLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable multi-factor authentication function| -|[mfaLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for multi-factor authentication Lambda function| -|[mfaLambdaPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for multi-factor authentication Lambda function| -|[mfaLambdaInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable multi-factor authentication| -|[mfaLambdaRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|IAM Execution Role for multi-factor authentication Lambda function| -|[openIdLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable OpenID Connect| -|[openIdLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for OpenID Connect Lambda function| -|[openIdLambdaIAMPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable OpenID Connect Lambda function| -|[openIdLambdaInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable OpenID Connect| -|[openIdLambdaRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|Lambda Execution Role for OpenID Connect Lambda function| - -## Customize Amplify-generated Cognito user group resources - -Apply all the overrides in the `override(...)` function. For example to add a path to the lambda execution role that facilitates the user pool group to role mapping: -```ts -import { AmplifyUserPoolGroupStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyUserPoolGroupStackTemplate) { - resources.lambdaExecutionRole.path = "//" // Note: CFN does not allow you to modify the path after creation -} -``` - -You can override the following user pool group resources that Amplify generates: - -|Amplify-generated resource|Description| -|-|-| -|[userPoolGroup](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolgroup.html)|The map of user pool groups| -|[userPoolGroupRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|The map of user pool group roles| -|[roleMapCustomResource](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|A custom CloudFormation resource to map user pool groups to their roles| -|[lambdaExecutionRole](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html)|Lambda execution role for the "user pool group"-to-role mapping function| -|[roleMapLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|The Lambda function that facilitates the user pool group to role mapping| - - -## Customize Amplify-generated Cognito auth resources with social providers - -Apply all the overrides in the `override(...)` function. For example to add social providers to your Cognito user pool: - -```ts -import { AmplifyAuthCognitoStackTemplate } from "@aws-amplify/cli-extensibility-helper"; - -export function override(resources: AmplifyAuthCognitoStackTemplate) { - resources.addCfnResource( - { - type: "AWS::Cognito::UserPoolIdentityProvider", - properties: { - AttributeMapping: { - preferred_username: "email", - email: "email" - }, - ProviderDetails: { - client_id: "test", - client_secret: "test", - authorize_scopes: "test", - }, - ProviderName: "LoginWithAmazon", - ProviderType: "LoginWithAmazon", - UserPoolId: { - Ref: "UserPool", - }, - }, - }, - "amazon-social-provider" - ); -} -``` diff --git a/src/pages/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx b/src/pages/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx deleted file mode 100644 index a8cfbbb90d4..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx +++ /dev/null @@ -1,117 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Set up user group management', - description: 'Create logical groups in Cognito User Pools and assign permissions to access resources in Amplify categories with the Amplify CLI.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/auth/user-group-management/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -You can create logical groups in Cognito User Pools and assign permissions to access resources in Amplify categories with the CLI, as well as define the relative precedence of one group to another. This can be useful for defining which users should be part of "Admins" vs "Editors", and if the users in a Group should be able to just write or write & read to a resource (AppSync, API Gateway, S3 bucket, etc). [You can also use these with `@auth` Static Groups in the GraphQL Transformer](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules#user-group-based-data-access). Precedence helps remove any ambiguity on permissions if a user is in multiple Groups. - -## Create user groups - -```bash -amplify add auth -``` - -```console -❯ Manual configuration - -Do you want to add User Pool Groups? (Use arrow keys) -❯ Yes - -? Provide a name for your user pool group: Admins -? Do you want to add another User Pool Group Yes -? Provide a name for your user pool group: Editors -? Do you want to add another User Pool Group No -? Sort the user pool groups in order of preference … (Use + to change the order) - Admins - Editors -``` - -When asked as in the example above, you can press `Shift` on your keyboard along with the **LEFT** and **RIGHT** arrows to move a Group higher or lower in precedence. Once complete you can open `amplify/backend/auth/userPoolGroups/user-pool-group-precedence.json` to manually set the precedence. - -## Group access controls - -For certain Amplify categories you can restrict access with CRUD (Create, Read, Update, and Delete) permissions, setting different access controls for authenticated users vs Guests (e.g. Authenticated users can read & write to S3 buckets while Guests can only read). You can further restrict this to apply different permissions conditionally depending on if a logged-in user is part of a specific User Pool Group. - -```bash -amplify add storage # Select content -``` - -```console -? Restrict access by? (Use arrow keys) - Auth/Guest Users - Individual Groups -❯ Both - Learn more - -Who should have access? -❯ Auth and guest users - -What kind of access do you want for Authenticated users? -❯ create/update, read - -What kind of access do you want for Guest users? -❯ read - -Select groups: -❯ Admins - -What kind of access do you want for Admins users? -❯ create/update, read, delete -``` - -The above example uses a combination of permissions where users in the "Admins" Group have full access, "Guest" users can only read, and "Authenticated" users who are not a part of any group have create, update, and read access. Amplify will configure the corresponding IAM policy on your behalf. Advanced users can additionally set permissions by adding a `customPolicies` key to `amplify/backend/auth/userPoolGroups/user-pool-group-precedence.json` with custom IAM policy for a Group. This will attach an inline policy on the IAM role associated to this Group during deployment. **Note** this is an advanced feature and only suitable if you have an understanding of AWS resources. For instance perhaps you wanted users in the "Admins" group to have the ability to Create an S3 bucket: - -```json -[ - { - "groupName": "Admins", - "precedence": 1, - "customPolicies": [ - { - "PolicyName": "admin-group-policy", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "statement1", - "Effect": "Allow", - "Action": ["s3:CreateBucket"], - "Resource": ["arn:aws:s3:::*"] - } - ] - } - } - ] - }, - { - "groupName": "Editors", - "precedence": 2 - } -] -``` diff --git a/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx deleted file mode 100644 index d6b8761a673..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx +++ /dev/null @@ -1,832 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Connect to existing AWS resources built with the CDK', - description: "Connect a new app to AWS resources built with the CDK.", - platforms: [ - 'flutter', - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -This guide shows you how to connect a new app to AWS resources you've already created using the [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/). The AWS CDK is an open-source software development framework for defining cloud infrastructure as code with modern programming languages. This infrastructure is then deployed through [AWS CloudFormation](https://aws.amazon.com/cloudformation/). - - - -In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then create and connect a React web app to the GraphQL API. - - - - - -In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then build and integrate a Flutter app with the GraphQL API. - - - -Before you begin, you will need: - - - -* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. -* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. - - - - - -* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. -* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. -* Flutter and its command-line tools [installed](https://docs.flutter.dev/get-started/install) and configured. - - - -## Build a GraphQL API using the Amplify Data CDK construct - -The CDK provides a simple way to define cloud infrastructure in code. In this section, we will use the CDK to build out the backend resources for our application. - -**Step 1:** Create a folder for the CDK app by running the following command in your terminal. - -```bash title="Terminal" showLineNumbers={false} -mkdir cdk-backend -``` - -**Step 2:** Navigate to the `cdk-backend` folder and create a new CDK project by running the `cdk init` command and specifying your preferred language. - -```bash title="Terminal" -cd cdk-backend -cdk init --language typescript -``` - -**Step 3:** Open the newly created CDK project using VS Code, or your preferred IDE. - -**Step 4:** In your terminal, navigate to the `cdk_backend` root folder, and install the AWS Amplify Data package by running the following command. - -```bash title="Terminal" showLineNumbers={false} -npm install @aws-amplify/data-construct -``` - -**Step 5:** Update the `cdk_backend/lib/cdk-backend-stack.ts` file as shown in the following code to use the `AmplifyData` construct to create an AWS AppSync API. - -```ts title="lib/cdk-backend-stack.ts" -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { - AmplifyData, - AmplifyDataDefinition -} from '@aws-amplify/data-construct'; - -export class CdkBackendStack extends cdk.Stack { - constructor(scope: Construct, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - // highlight-start - new AmplifyData(this, 'AmplifyCdkData', { - definition: AmplifyDataDefinition.fromString(/* GraphQL */ ` - type Todo @model @auth(rules: [{ allow: public }]) { - id: ID! - name: String! - description: String - complete: Boolean - } - `), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } - }); - // highlight-end - } -} -``` - -**Step 6:** Deploy the CDK stacks by running the following command. - -```bash showLineNumbers={false} -cdk deploy -``` - -**Step 7:** The CDK will prepare the resources for deployment and will display the following prompt. Enter **Y** and press **Enter**. - -The CDK preparing for deployment. - -The CDK will deploy the stacks and display the following confirmation. Note the details of the deployed API; we’re going to use them in the next section. - -CDK deploying the stacks. - -Now that you have built the backend API with the CDK, you can connect a frontend. - - - -## Build a React app and connect to the GraphQL API - -In this section, we will connect a React web app to our existing GraphQL API. First, we will create a new React project and install the necessary Amplify packages. Next, we will use the Amplify CLI to generate GraphQL code matching our API structure. Then, we will add React components to perform queries and mutations to manage to-do items in our API. After that, we will configure the Amplify library with details of our backend API. Finally, we will run the application to demonstrate full CRUD functionality with our existing API. - -**Step 1:** Create a React app by running the following command in your terminal. - -```bash title="Terminal" showLineNumbers={false} -npx create-react-app react-amplify-connect -``` - -**Step 2:** Open the newly created React app using VS Code, or your preferred IDE. - -**Step 3:** Install the `aws-amplify`, `@aws-amplify/ui-react`, and `@aws-amplify/cli` packages by running the following commands. - -```bash title="Terminal" showLineNumbers={false} -npm install aws-amplify @aws-amplify/ui-react @aws-amplify/cli -``` - -**Step 4:** Use the awsAppsyncApiId and awsAppsyncRegion values of the CDK stack you created previously to generate the GraphQL client helper code by running the following command. - -awsAppsyncApiId and awsAppsyncRegion values highlighted within the outputs of the CDK stack. - -```react showLineNumbers={false} -npx @aws-amplify/cli codegen add --apiId --region -``` - -**Step 5:** Accept the default values for the prompts. - -```console title="Terminal" showLineNumbers={false} -? Choose the type of app that you're building javascript -? What javascript framework are you using react -✔ Getting API details -? Choose the code generation language target javascript -? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js -? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes -? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 -✔ Downloaded the schema -✔ Generated GraphQL operations successfully and saved at src/graphql -``` - -The Amplify CLI will create the GraphQL client helper code inside the `src/graphql` folder. - -mutations.js, queries.js, and subscriptions.js within the graphql folder. - -**Step 6:** Update the `App.js` file with the following code to create a form with a button to create to-dos, as well as a way to fetch and render the to-do list. - -```jsx title="src/App.js" -import { Amplify} from 'aws-amplify' -import '@aws-amplify/ui-react/styles.css'; -import { useEffect, useState } from 'react'; -import { generateClient } from 'aws-amplify/api'; -import { createTodo } from './graphql/mutations'; -import { listTodos } from './graphql/queries'; - -Amplify.configure({ - API: { - GraphQL: { - // highlight-start - endpoint: '', - region: '', - defaultAuthMode: 'apiKey', - apiKey: '' - // highlight-end - } - } -}); - -const initialState = { name: '', description: '' }; -const client = generateClient(); - -const App = () => { - const [formState, setFormState] = useState(initialState); - const [todos, setTodos] = useState([]); - - useEffect(() => { - fetchTodos(); - }, []); - - function setInput(key, value) { - setFormState({ ...formState, [key]: value }); - } - - async function fetchTodos() { - try { - const todoData = await client.graphql({ - query: listTodos - }); - const todos = todoData.data.listTodos.items; - setTodos(todos); - } catch (err) { - console.log('error fetching todos'); - } - } - - async function addTodo() { - try { - if (!formState.name || !formState.description) return; - const todo = { ...formState }; - setTodos([...todos, todo]); - setFormState(initialState); - await client.graphql({ - query: createTodo, - variables: { - input: todo - } - }); - } catch (err) { - console.log('error creating todo:', err); - } - } - - return ( -
-

Amplify Todos

- setInput('name', event.target.value)} - style={styles.input} - value={formState.name} - placeholder="Name" - /> - setInput('description', event.target.value)} - style={styles.input} - value={formState.description} - placeholder="Description" - /> - - {todos.map((todo, index) => ( -
-

{todo.name}

-

{todo.description}

-
- ))} -
- ); -}; - -const styles = { - container: { - width: 400, - margin: '0 auto', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - padding: 20 - }, - todo: { marginBottom: 15 }, - input: { - border: 'none', - backgroundColor: '#ddd', - marginBottom: 10, - padding: 8, - fontSize: 18 - }, - todoName: { fontSize: 20, fontWeight: 'bold' }, - todoDescription: { marginBottom: 0 }, - button: { - backgroundColor: 'black', - color: 'white', - outline: 'none', - fontSize: 18, - padding: '12px 0px' - } -}; - -export default App; -``` - - -**Step 7:** Run the app using the following command. - -```bash title="Terminal" showLineNumbers={false} -npm start -``` - -**Step 8:** Use the form to create a few to-do items. - - - -In this section, we generated GraphQL code, created React components, configured Amplify, and connected the app to the API. This enabled full CRUD functionality with our backend through queries and mutations. - -
- - - -## Build a Flutter app and connect to the GraphQL API - -In this section, we will connect a Flutter mobile app to the GraphQL API we created with the CDK. First, we will initialize a Flutter project, define models matching our schema, and use Amplify to integrate CRUD operations. Then we will add UI pages to manage to-do items with queries and mutations. Finally, we will configure Amplify with our backend details and run the app to demonstrate full functionality with our existing API. - -**Step 1:** Create a Flutter app by running the following command in your terminal. - -```bash title="Terminal" showLineNumbers={false} -flutter create flutter_todo_app --platforms=web -``` - -**Step 2:** Open the newly created Flutter app by running the following commands in your terminal. - -```bash title="Terminal" -cd flutter_todo_app -code . -r -``` - -**Step 3**: Update the `pubspec.yaml` file in the app’s root directory to add the required dependencies, as shown in the following code. - -{/* cSpell:disable */} -```yaml title="pubspec.yaml" -name: flutter_todo_app -description: "A new Flutter project." -publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.0+1 - -// highlight-start -dependencies: - flutter: - sdk: flutter - amplify_flutter: ^1.0.0 - amplify_api: ^1.0.0 - go_router: ^6.5.5 - cupertino_icons: ^1.0.2 -// highlight-end - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^2.0.0 - -flutter: - uses-material-design: true -``` -{/* cSpell:enable */} - -**Step 4:** Run the following command in your terminal to install the dependencies you added to the `pubspec.yaml` file. - -```bash title="Terminal" showLineNumbers={false} -flutter pub get -``` - -**Step 5:** Install the @aws-amplify/cli packages by running the following command. - -```bash title="Terminal" showLineNumbers={false} -npm install @aws-amplify/cli -``` - -**Step 6:** Create a new folder and name it `graphql`. Inside it, create the file `schema.graphql`. - -The schema.graphql file inside the graphql folder. - -**Step 7:** Update the file `schema.graphql`, as shown in the following example, to define the Todo data model, similar to what you used for the CDK app. - -```graphql title="schema.graphql" -type Todo @model @auth(rules: [{ allow: public }]) { - id: ID! - name: String! - description: String - complete: Boolean -} -``` - -**Step 8:** Run the following command to generate the GraphQL client helper models inside the `lib/models` folder. - -```bash showLineNumbers={false} -npx @aws-amplify/cli codegen models --model-schema ./graphql --target flutter --output-dir ./lib/models -``` - -The ModelProvider.dart and Todo.dart files within the models folder. - -**Step 9:** Create the file `todo_item_page.dart` inside the `lib` folder and update it with the following code to present a form to the user for creating a to-do item. Once submitted, the form will initiate a GraphQL mutation to add or modify the item in the database. - -```dart title="todo_item_page.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import '../models/ModelProvider.dart'; - -class ToDoItemPage extends StatefulWidget { - const ToDoItemPage({ - required this.todoItem, - super.key, - }); - - final Todo? todoItem; - - @override - State createState() => _ToDoItemPageState(); -} - -class _ToDoItemPageState extends State { - final _formKey = GlobalKey(); - final TextEditingController _nameController = TextEditingController(); - final TextEditingController _descriptionController = TextEditingController(); - - late final String _nameText; - late bool _isDone; - - bool get _isCreate => _todoItem == null; - Todo? get _todoItem => widget.todoItem; - - @override - void initState() { - super.initState(); - - final todoItem = _todoItem; - if (todoItem != null) { - _nameController.text = todoItem.name; - _descriptionController.text = todoItem.description ?? ''; - - _nameText = 'Update to-do Item'; - _isDone = todoItem.complete ?? false; - } else { - _nameText = 'Create to-do Item'; - _isDone = false; - } - } - - @override - void dispose() { - _nameController.dispose(); - _descriptionController.dispose(); - - super.dispose(); - } - - Future submitForm() async { - if (!_formKey.currentState!.validate()) { - return; - } - - // If the form is valid, submit the data - final name = _nameController.text; - final description = _descriptionController.text; - final complete = _isDone; - - // highlight-start - if (_isCreate) { - // Create a new todo item - final newEntry = Todo( - name: name, - description: description.isNotEmpty ? description : null, - complete: complete, - ); - final request = ModelMutations.create(newEntry); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Create result: $response'); - } else { - // Update todoItem instead - final updateToDoItem = _todoItem!.copyWith( - name: name, - description: description.isNotEmpty ? description : null, - complete: complete, - ); - final request = ModelMutations.update(updateToDoItem); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Update result: $response'); - } - // highlight-end - - // Navigate back to homepage after create/update executes - if (mounted) { - context.pop(); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(_nameText), - ), - body: Align( - alignment: Alignment.topCenter, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 800), - child: Padding( - padding: const EdgeInsets.all(16), - child: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _nameController, - decoration: const InputDecoration( - labelText: 'Name (required)', - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a name'; - } - return null; - }, - ), - TextFormField( - controller: _descriptionController, - decoration: const InputDecoration( - labelText: 'Description', - ), - ), - SwitchListTile( - title: const Text('Done'), - value: _isDone, - onChanged: (bool value) { - setState(() { - _isDone = value; - }); - }, - secondary: const Icon(Icons.done_all_outlined), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: submitForm, - child: Text(_nameText), - ), - ], - ), - ), - ), - ), - ), - ), - ); - } -} -``` - -**Step 10:** Create the file `home_page.dart.dart` inside the `lib` folder and update it with the following code. This page will use a GraphQL query to retrieve the list of to-do items and display them in a ListView widget. The page will also allow the user to delete a to-do item by using a GraphQL mutation. - -```dart title="home_page.dart.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import '../models/ModelProvider.dart'; - -class HomePage extends StatefulWidget { - const HomePage({super.key}); - - @override - State createState() => _HomePageState(); -} - -class _HomePageState extends State { - var _todoItems = []; - - @override - void initState() { - super.initState(); - _refreshTodoItems(); - } - - // highlight-start - Future _refreshTodoItems() async { - try { - final request = ModelQueries.list(Todo.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items; - if (response.hasErrors) { - safePrint('errors: ${response.errors}'); - return; - } - setState(() { - _todoItems = todos!.whereType().toList(); - }); - } on ApiException catch (e) { - safePrint('Query failed: $e'); - } - } - - Future _deleteToDoItem(Todo todoItem) async { - final request = ModelMutations.delete(todoItem); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Delete response: $response'); - await _refreshTodoItems(); - } - // highlight-end - - Future _openToDoItem({Todo? todoItem}) async { - await context.pushNamed('manage', extra: todoItem); - // Refresh the entries when returning from the - // todo item screen. - await _refreshTodoItems(); - } - - Widget _buildRow({ - required String name, - required String description, - required bool isDone, - TextStyle? style, - }) { - return Row( - children: [ - Expanded( - child: Text( - name, - textAlign: TextAlign.center, - style: style, - ), - ), - Expanded( - child: Text( - description, - textAlign: TextAlign.center, - style: style, - ), - ), - Expanded( - child: isDone ? const Icon(Icons.done) : const SizedBox(), - ), - ], - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton( - // Navigate to the page to create new todo item - onPressed: _openToDoItem, - child: const Icon(Icons.add), - ), - appBar: AppBar( - title: const Text('To-Do List'), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.only(top: 25), - child: RefreshIndicator( - onRefresh: _refreshTodoItems, - child: Column( - children: [ - if (_todoItems.isEmpty) - const Text('Use the \u002b sign to add new to-do items') - else - const SizedBox(height: 30), - _buildRow( - name: 'Name', - description: 'Description', - isDone: false, - style: Theme.of(context).textTheme.titleMedium, - ), - const Divider(), - Expanded( - child: ListView.builder( - itemCount: _todoItems.length, - itemBuilder: (context, index) { - final todoItem = _todoItems[index]; - return Dismissible( - key: ValueKey(todoItem), - background: const ColoredBox( - color: Colors.red, - child: Padding( - padding: EdgeInsets.only(right: 10), - child: Align( - alignment: Alignment.centerRight, - child: Icon(Icons.delete, color: Colors.white), - ), - ), - ), - onDismissed: (_) => _deleteToDoItem(todoItem), - child: ListTile( - onTap: () => _openToDoItem( - todoItem: todoItem, - ), - title: _buildRow( - name: todoItem.name, - description: todoItem.description ?? '', - isDone: todoItem.complete ?? false, - ), - ), - ); - }, - ), - ), - ], - ), - ), - ), - ), - ); - } -} -``` - -**Step 11:** Update `main.dart` to configure Amplify using the details of the GraphQL API you created using the CDK app in the previous section. - -```dart title="main.dart" -import 'package:amplify_api/amplify_api.dart'; - -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import 'models/ModelProvider.dart'; -import 'home_page.dart'; -import 'todo_item_page.dart'; - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await _configureAmplify(); - runApp(const MyApp()); -} - -Future _configureAmplify() async { - try { - final api = AmplifyAPI(modelProvider: ModelProvider.instance); - - await Amplify.addPlugins([api]); - const amplifyconfig = '''{ - "api": { - "plugins": { - "awsAPIPlugin": { - "flutter_todo_app": { - "endpointType": "GraphQL", - // highlight-start - "endpoint": "", - "region": "", - "authorizationType": "API_KEY", - "apiKey": "" - // highlight-end - } - } - } - } -}'''; - - await Amplify.configure(amplifyconfig); - - safePrint('Successfully configured'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // GoRouter configuration - static final _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, state) => const HomePage(), - ), - GoRoute( - path: '/manage-todo-item', - name: 'manage', - builder: (context, state) => ToDoItemPage( - todoItem: state.extra as Todo?, - ), - ), - ], - ); - - @override - Widget build(BuildContext context) { - return MaterialApp.router( - routerConfig: _router, - debugShowCheckedModeBanner: false, - builder: (context, child) { - return child!; - }, - ); - } -} -``` - -**Step 12:** Run the app in the Chrome browser using the following command. - -```bash title="Terminal" showLineNumbers={false} -flutter run -d chrome -``` - - - - - -## Conclusion - -Congratulations! You used the AWS Amplify Data CDK construct to create a GraphQL API backend using AWS AppSync. You then connected your app to that API using the Amplify libraries. If you have any feedback, leave a [GitHub issue](https://github.com/aws-amplify/docs/issues) or join our [Discord Community](https://discord.gg/amplify)! - -## Clean up resources - -Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the CDK app created above. - -```bash title="Terminal" showLineNumbers={false} - cdk destroy -``` diff --git a/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx b/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx deleted file mode 100644 index c3a7cac57c9..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx +++ /dev/null @@ -1,1010 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Connect to existing AWS resources with Amplify CLI', - description: "Use the Amplify CLI to connect existing AWS resources to a new app.", - platforms: [ - 'flutter', - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -The AWS Amplify CLI (Command Line Interface) CLI provides a simple workflow for provisioning cloud resources like authentication, databases, and storage for apps through the command line. - - -In this guide, you will learn how to connect a new Flutter mobile app to backend resources you've already created using the Amplify CLI. - - - -Connecting a web app? We also offer a version of this guide for integrating existing backends with React using the Amplify CLI. Check out the [React guide](/react/build-a-backend/existing-resources/cli/). - - - - - -In this guide, you will learn how to connect a new React web app to backend resources you've already created using the Amplify CLI. - - - -Connecting a mobile app? We also offer a version of this guide for integrating existing backends with Flutter using the Amplify CLI. Check out the [Flutter guide](/flutter/build-a-backend/existing-resources/cli/). - - - - - -## Connect mobile app to existing AWS resources - -This guide will walk you through connecting a new Flutter app to AWS resources created with Amplify for an existing Flutter app. If you don't already have an existing app, you can follow this [Flutter tutorial](https://docs.amplify.aws/start/getting-started/setup/q/integration/flutter/) to create a budget tracker app that uses Amplify Auth and API resources. - -Before you begin, you will need: - -* An existing Flutter app -* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. -* Flutter [installed](https://docs.flutter.dev/get-started/install) and configured. -* A text editor combined with Flutter’s command-line tools. For this guide, we will use VS Code, but you can use your preferred IDE. - -### Find the AWS backend details -Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app. - -**Step 1:** In your existing app, open the file `/amplify/team-provider-info.json`. - -The team-provider-info.json file within the file directory of the Amplify app. - -**Step 2:** In the `team-provider-info.json` file, note the following: - -1. The environment you want to use -2. The `AmplifyAppId` for the required environment - -{/* cSpell:disable */} -The environment and AmplifyAppId in team-provider-info.json file. -{/* cSpell:enable */} - -### Create the Flutter app -Now that we have gathered the necessary backend details, we can start building out the new Flutter app. - -**Step 1:** Create a Flutter app by running the following command in your terminal. - -```bash showLineNumbers={false} - -flutter create amplify_connect_resources - -``` - - - -**Step 2:** Open the newly created Flutter app using VS Code by running the following commands in your terminal. - -``` -cd amplify_connect_resources -code . -r -``` - -![Open the created app using VS Code.](/images/existing-resources/app-vscode-mobile-cli.png) - - -**Step 3:** Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal. - -```bash showLineNumbers={false} -amplify pull --appId --envName -``` - - -**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. - -Accept the default values for the prompts about the default editor, type of app, and storage location of the configuration file. Then answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. - -{/* cSpell:disable */} -``` -? Select the authentication method you want to use: AWS profile - -For more information on AWS Profiles, see: -https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html - -? Please choose the profile you want to use AwsWest1 -Amplify AppID found: dorapq24trw9r. Amplify App name is:amplifyBudget -Backend environment dev found in Amplify Console app: amplifyBudget -? Choose your default editor: Visual Studio Code -✔ Choose the type of app that you're building · flutter -Please tell us about your project -? Where do you want to store your configuration file? ./lib/ -? Do you plan on modifying this backend? Yes -⠦ Fetching updates to backend environment: dev from the cloud.⠋ Building resource api/amplifyBudget -⠹ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully. - -Edit your schema at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema -✔ Successfully pulled backend environment dev from the cloud. -✅ - -✅ Successfully pulled backend environment dev from the cloud. -Run 'amplify pull' to sync future upstream changes. -``` -{/* cSpell:enable */} - -The Amplify CLI will add a new folder named `amplify` to the app's root folder, which contains the Amplify project and backend details. It will also add a new Dart file, `amplifyconfiguration.dart`, to the `lib/` folder. The app will use this file at runtime to locate and connect to the backend resources you have provisioned. - -The Amplify folder and amplifyconfiguration.dart file within the file directory of the Amplify app. - -**Step 5:** Update the file `pubspec.yaml` in the app root directory to add the required packages. In this example, we will use the same packages as the app created in this guide. To do this, update the `pubspec.yaml` as shown in the following. - -{/* cSpell:disable */} -```yaml title="pubspec.yaml" -name: budget_tracker -description: A new Flutter project. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.0+1 - -dependencies: -// highlight-start - amplify_api: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_authenticator: ^1.0.0 - amplify_flutter: ^1.0.0 -// highlight-end - flutter: - sdk: flutter -// highlight-next-line - go_router: ^6.5.5 - cupertino_icons: ^1.0.2 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^2.0.0 - -flutter: - uses-material-design: true - -``` -{/* cSpell:enable */} - - -**Step 6**: To enable type-safe interaction with the GraphQL schema, use this command to generate the required Dart files. - -```bash showLineNumbers={false} - -amplify codegen models - -``` -The Amplify CLI will generate the Dart files in the `lib/models` folder. - -The models folder within the file directory of the Amplify app. - -**Step 7:** Update the `main.dart` file with the following code to introduce the Amplify Authenticator and integrate Amplify API with your app to create, update, query, and delete `BudgetEntry` items. Typically, you would break this file up into smaller modules but we've kept it as a single file for this guide. - -```bash title="main.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:amplify_authenticator/amplify_authenticator.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'amplifyconfiguration.dart'; -import 'models/ModelProvider.dart'; - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await _configureAmplify(); - runApp(const MyApp()); -} - -// highlight-start -Future _configureAmplify() async { - - try { - - final api = AmplifyAPI(modelProvider: ModelProvider.instance); - final auth = AmplifyAuthCognito(); - await Amplify.addPlugins([api, auth]); - await Amplify.configure(amplifyconfig); - - safePrint('Successfully configured'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } -} -// highlight-end - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // GoRouter configuration - static final _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, state) => const HomeScreen(), - ), - GoRoute( - path: '/manage-budget-entry', - name: 'manage', - builder: (context, state) => ManageBudgetEntryScreen( - budgetEntry: state.extra as BudgetEntry?, - ), - ), - ], - ); - - @override - Widget build(BuildContext context) { - return Authenticator( - child: MaterialApp.router( - routerConfig: _router, - debugShowCheckedModeBanner: false, - // highlight-next-line - builder: Authenticator.builder(), - ), - ); - } -} - -class LoadingScreen extends StatelessWidget { - const LoadingScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ); - } -} - -class HomeScreen extends StatefulWidget { - const HomeScreen({super.key}); - - @override - State createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { - var _budgetEntries = []; - - @override - void initState() { - super.initState(); - _refreshBudgetEntries(); - } - - Future _refreshBudgetEntries() async { - try { - final request = ModelQueries.list(BudgetEntry.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items; - if (response.hasErrors) { - safePrint('errors: ${response.errors}'); - return; - } - setState(() { - _budgetEntries = todos!.whereType().toList(); - }); - } on ApiException catch (e) { - safePrint('Query failed: $e'); - } - } - - Future _deleteBudgetEntry(BudgetEntry budgetEntry) async { - final request = ModelMutations.delete(budgetEntry); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Delete response: $response'); - await _refreshBudgetEntries(); - } - - Future _navigateToBudgetEntry({BudgetEntry? budgetEntry}) async { - await context.pushNamed('manage', extra: budgetEntry); - // Refresh the entries when returning from the - // budget entry screen. - await _refreshBudgetEntries(); - } - - double _calculateTotalBudget(List items) { - var totalAmount = 0.0; - for (final item in items) { - totalAmount += item?.amount ?? 0; - } - return totalAmount; - } - - Widget _buildRow({ - required String title, - required String description, - required String amount, - TextStyle? style, - }) { - return Row( - children: [ - Expanded( - child: Text( - title, - textAlign: TextAlign.center, - style: style, - ), - ), - Expanded( - child: Text( - description, - textAlign: TextAlign.center, - style: style, - ), - ), - Expanded( - child: Text( - amount, - textAlign: TextAlign.center, - style: style, - ), - ), - ], - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton( - // Navigate to the page to create new budget entries - onPressed: _navigateToBudgetEntry, - child: const Icon(Icons.add), - ), - appBar: AppBar( - title: const Text('Budget Tracker'), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.only(top: 25), - child: RefreshIndicator( - onRefresh: _refreshBudgetEntries, - child: Column( - children: [ - if (_budgetEntries.isEmpty) - const Text('Use the \u002b sign to add new budget entries') - else - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Show total budget from the list of all BudgetEntries - Text( - 'Total Budget: \$ ${_calculateTotalBudget(_budgetEntries).toStringAsFixed(2)}', - style: const TextStyle(fontSize: 24), - ) - ], - ), - const SizedBox(height: 30), - _buildRow( - title: 'Title', - description: 'Description', - amount: 'Amount', - style: Theme.of(context).textTheme.titleMedium, - ), - const Divider(), - Expanded( - child: ListView.builder( - itemCount: _budgetEntries.length, - itemBuilder: (context, index) { - final budgetEntry = _budgetEntries[index]; - return Dismissible( - key: ValueKey(budgetEntry), - background: const ColoredBox( - color: Colors.red, - child: Padding( - padding: EdgeInsets.only(right: 10), - child: Align( - alignment: Alignment.centerRight, - child: Icon(Icons.delete, color: Colors.white), - ), - ), - ), - onDismissed: (_) => _deleteBudgetEntry(budgetEntry), - child: ListTile( - onTap: () => _navigateToBudgetEntry( - budgetEntry: budgetEntry, - ), - title: _buildRow( - title: budgetEntry.title, - description: budgetEntry.description ?? '', - amount: - '\$ ${budgetEntry.amount.toStringAsFixed(2)}', - ), - ), - ); - }, - ), - ), - ], - ), - ), - ), - ), - ); - } -} - -class ManageBudgetEntryScreen extends StatefulWidget { - const ManageBudgetEntryScreen({ - required this.budgetEntry, - super.key, - }); - - final BudgetEntry? budgetEntry; - - @override - State createState() => - _ManageBudgetEntryScreenState(); -} - -class _ManageBudgetEntryScreenState extends State { - final _formKey = GlobalKey(); - final TextEditingController _titleController = TextEditingController(); - final TextEditingController _descriptionController = TextEditingController(); - final TextEditingController _amountController = TextEditingController(); - - late final String _titleText; - - bool get _isCreate => _budgetEntry == null; - BudgetEntry? get _budgetEntry => widget.budgetEntry; - - @override - void initState() { - super.initState(); - - final budgetEntry = _budgetEntry; - if (budgetEntry != null) { - _titleController.text = budgetEntry.title; - _descriptionController.text = budgetEntry.description ?? ''; - _amountController.text = budgetEntry.amount.toStringAsFixed(2); - _titleText = 'Update budget entry'; - } else { - _titleText = 'Create budget entry'; - } - } - - @override - void dispose() { - _titleController.dispose(); - _descriptionController.dispose(); - _amountController.dispose(); - super.dispose(); - } - - Future submitForm() async { - if (!_formKey.currentState!.validate()) { - return; - } - - // If the form is valid, submit the data - final title = _titleController.text; - final description = _descriptionController.text; - final amount = double.parse(_amountController.text); - - if (_isCreate) { - // Create a new budget entry - final newEntry = BudgetEntry( - title: title, - description: description.isNotEmpty ? description : null, - amount: amount, - ); - final request = ModelMutations.create(newEntry); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Create result: $response'); - } else { - // Update budgetEntry instead - final updateBudgetEntry = _budgetEntry!.copyWith( - title: title, - description: description.isNotEmpty ? description : null, - amount: amount, - ); - final request = ModelMutations.update(updateBudgetEntry); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Update result: $response'); - } - - // Navigate back to homepage after create/update executes - if (mounted) { - context.pop(); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(_titleText), - ), - body: Align( - alignment: Alignment.topCenter, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 800), - child: Padding( - padding: const EdgeInsets.all(16), - child: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _titleController, - decoration: const InputDecoration( - labelText: 'Title (required)', - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a title'; - } - return null; - }, - ), - TextFormField( - controller: _descriptionController, - decoration: const InputDecoration( - labelText: 'Description', - ), - ), - TextFormField( - controller: _amountController, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - decoration: const InputDecoration( - labelText: 'Amount (required)', - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter an amount'; - } - final amount = double.tryParse(value); - if (amount == null || amount <= 0) { - return 'Please enter a valid amount'; - } - return null; - }, - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: submitForm, - child: Text(_titleText), - ), - ], - ), - ), - ), - ), - ), - ), - ); - } -} - -``` - - -**Step 8:** Run the app using the following command, and use the authentication flow to create a user. Then create a few budget items. - - -```bash showLineNumbers={false} - -flutter run - - -``` - - - - - -### Conclusion - -Congratulations! Your new Flutter app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management and a scalable GraphQL API backed by Amazon DynamoDB. - - -### Clean up resources - -Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app. - -```bash showLineNumbers={false} -amplify delete - -``` - -If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the [Build & connect backend section](/[platform]/build-a-backend/) for guides on how to add and connect other backend resources. - - - -## Connect web app to existing AWS resources - -This guide will walk you through connecting a new React web app to the AWS resources created with Amplify for an existing React app. If you don't already have an existing app, you can follow this [React tutorial](https://docs.amplify.aws/react/start/getting-started/introduction/) to create a to-do app that uses Amplify Auth, API, and Hosting resources. - -Before you begin, you will need: - -* An existing React app -* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](https://docs.amplify.aws/cli/start/install/) and configured. -* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. - -### Find the AWS backend details -Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app. - -**Step 1:** In your existing app, open the file `/amplify/team-provider-info.json` . - -The team-provider-info.json file within the file directory of the Amplify app. - -**Step 2:** In the `team-provider-info.json` file, note the following: - -1. The environment you want to use -2. The `AmplifyAppId` for the required environment - -{/* cSpell:disable */} -The environment and AmplifyAppId in team-provider-info.json file. -{/* cSpell:enable */} - -### Create the React app -Now that we have gathered the necessary backend details, we can start building out the new React app. - -**Step 1:** Create a React app by running the following command in your terminal. - -```bash showLineNumbers={false} -npx create-react-app react-amplify-connect -``` - -**Step 2:** Open the newly created React app using VS Code by running the following commands in your terminal. - -``` -cd react-amplify-connect -code . -r -``` - -![Open the created app using VS Code.](/images/existing-resources/app-vscode-web-cli.png) - -**Step 3:** Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal. - -```bash showLineNumbers={false} -amplify pull --appId --envName -``` - -**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. - -Accept the default values for the prompts and make sure to answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. - -{/* cSpell:disable */} -``` -? Select the authentication method you want to use: AWS profile - -For more information on AWS Profiles, see: -https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html - -? Please choose the profile you want to use AwsWest1 -Amplify AppID found: dfn3u8j1nvzjc. Amplify App name is: reactamplified -Backend environment dev found in Amplify Console app: reactamplified -? Choose your default editor: Visual Studio Code -✔ Choose the type of app that you're building · javascript -Please tell us about your project -? What javascript framework are you using react -? Source Directory Path: src -? Distribution Directory Path: build -? Build Command: npm run-script build -? Start Command: npm run-script start -? Do you plan on modifying this backend? Yes -⠋ Fetching updates to backend environment: dev from the cloud. -⚠️ WARNING: your GraphQL API currently allows public create, read, update, and delete access to all models via an API Key. To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql/authorization-rules - -⠇ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully. - -Edit your schema at /Users/malakamm/development/react-amplify-connect/amplify/backend/api/reactamplified/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/react-amplify-connect/amplify/backend/api/reactamplified/schema -✔ Successfully pulled backend environment dev from the cloud. -Browserslist: caniuse-lite is outdated. Please run: - npx update-browserslist-db@latest - Why you should do it regularly: https://github.com/browserslist/update-db#readme -✅ - -✅ Successfully pulled backend environment dev from the cloud. -Run 'amplify pull' to sync future upstream changes. -``` -{/* cSpell:enable */} - -The Amplify CLI will add a new folder named `amplify` to the app's root folder, which contains the Amplify project and backend details. - -The amplify folder within the file directory of the Amplify app. - -**Step 5**: Use the following command to generate the GraphQL statements. - -```bash showLineNumbers={false} -amplify codegen add -``` - -**Step 6:** Accept the default values of the prompts. - -``` - -? Choose the code generation language target javascript -? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js -? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes -? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 -✔ Generated GraphQL operations successfully and saved at src/graphql - -``` - -The CLI will add a new folder named `graphql` to the app's root folder, which contains the GraphQL statements. - -The graphql folder within the file directory of the Amplify app. - -**Step 7:** Install the aws-amplify and @aws-amplify/ui-react packages by running the following commands. - -```bash showLineNumbers={false} -npm install aws-amplify @aws-amplify/ui-react -``` - -**Step 8:** Update the `App.js` file with the following code to create a login flow using Amplify UI and a form with a button to create to-dos, as well as a way to fetch and render the list of to-dos. - -```bash title="src/App.js" -import React, { useEffect, useState } from 'react' -import { Amplify, API, graphqlOperation } from 'aws-amplify' -import { createTodo } from './graphql/mutations' -import { listTodos } from './graphql/queries' -import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react'; -import '@aws-amplify/ui-react/styles.css'; - -// highlight-start -import awsExports from "./aws-exports"; -Amplify.configure(awsExports); -// highlight-end - -const initialState = { name: '', description: '' } - -const App = ({ signOut, user }) => { - const [formState, setFormState] = useState(initialState) - const [todos, setTodos] = useState([]) - - useEffect(() => { - fetchTodos() - }, []) - - function setInput(key, value) { - setFormState({ ...formState, [key]: value }) - } - - // highlight-start - async function fetchTodos() { - try { - const todoData = await API.graphql(graphqlOperation(listTodos)) - const todos = todoData.data.listTodos.items - setTodos(todos) - } catch (err) { console.log('error fetching todos') } - } - - async function addTodo() { - try { - if (!formState.name || !formState.description) return - const todo = { ...formState } - setTodos([...todos, todo]) - setFormState(initialState) - await API.graphql(graphqlOperation(createTodo, {input: todo})) - } catch (err) { - console.log('error creating todo:', err) - } - } - // highlight-end - - return ( -
- Hello {user.username} - -

Amplify Todos

- setInput('name', event.target.value)} - style={styles.input} - value={formState.name} - placeholder="Name" - /> - setInput('description', event.target.value)} - style={styles.input} - value={formState.description} - placeholder="Description" - /> - - { - todos.map((todo, index) => ( -
-

{todo.name}

-

{todo.description}

-
- )) - } -
- ) -} - -const styles = { - container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 }, - todo: { marginBottom: 15 }, - input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 }, - todoName: { fontSize: 20, fontWeight: 'bold' }, - todoDescription: { marginBottom: 0 }, - button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' } -} - -export default withAuthenticator(App); - - -``` - - -**Step 9:** Run the app using the following command. - -```bash showLineNumbers={false} -npm start -``` - -**Step 10:** Change the UI of the app as shown in the following to update the placeholder text of the to-do form and to use the user’s email for the hello message. - -```bash title="src/App.js" -... - return ( -
- // highlight-next-line - Hello {*user*.attributes.email} - -

Amplify Todos

- setInput('name', event.target.value)} - style={styles.input} - value={formState.name} - // highlight-next-line - placeholder="ToDo Name" - /> - setInput('description', event.target.value)} - style={styles.input} - value={formState.description} - // highlight-next-line - placeholder="ToDo Description" - /> - - { - todos.map((todo, index) => ( -
-

{todo.name}

-

{todo.description}

-
- )) - } -
- ) -.... -``` - -The `App.js` file should now look like the following code snippet. - -```bash title="src/App.js" -import React, { useEffect, useState } from 'react' -import { Amplify, API, graphqlOperation } from 'aws-amplify' -import { createTodo } from './graphql/mutations' -import { listTodos } from './graphql/queries' -import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react'; -import '@aws-amplify/ui-react/styles.css'; - -import awsExports from "./aws-exports"; -Amplify.configure(awsExports); - -const initialState = { name: '', description: '' } - -const App = ({ signOut, user }) => { - const [formState, setFormState] = useState(initialState) - const [todos, setTodos] = useState([]) - - useEffect(() => { - fetchTodos() - }, []) - - function setInput(key, value) { - setFormState({ ...formState, [key]: value }) - } - - async function fetchTodos() { - try { - const todoData = await API.graphql(graphqlOperation(listTodos)) - const todos = todoData.data.listTodos.items - setTodos(todos) - } catch (err) { console.log('error fetching todos') } - } - - async function addTodo() { - try { - if (!formState.name || !formState.description) return - const todo = { ...formState } - setTodos([...todos, todo]) - setFormState(initialState) - await API.graphql(graphqlOperation(createTodo, {input: todo})) - } catch (err) { - console.log('error creating todo:', err) - } - } - - return ( -
- Hello {user.attributes.email} - -

Amplify Todos

- setInput('name', event.target.value)} - style={styles.input} - value={formState.name} - placeholder="ToDo Name" - /> - setInput('description', event.target.value)} - style={styles.input} - value={formState.description} - placeholder="ToDo Description" - /> - - { - todos.map((todo, index) => ( -
-

{todo.name}

-

{todo.description}

-
- )) - } -
- ) -} - -const styles = { - container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 }, - todo: { marginBottom: 15 }, - input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 }, - todoName: { fontSize: 20, fontWeight: 'bold' }, - todoDescription: { marginBottom: 0 }, - button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' } -} - -export default withAuthenticator(App); - -``` - -**Step 11:** Use the following command to publish your changes. Amplify CLI will publish the changes and display the app URL. - -```bash showLineNumbers={false} -amplify publish -``` - -Amplify CLI publish the app and display a URL. - -**Step 12:** Use the URL to run the app in the browser. - - - -### Conclusion - -Congratulations! Your new React app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management, a scalable GraphQL API backed by Amazon DynamoDB, and a hosting service for publishing your app to the cloud. - -### Clean up resources - -Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app. - -```bash showLineNumbers={false} -amplify delete -``` - -If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the [Build & connect backend section](/[platform]/build-a-backend/) for guides on how to add and connect other backend resources. -
diff --git a/src/pages/[platform]/prev/build-a-backend/existing-resources/index.mdx b/src/pages/[platform]/prev/build-a-backend/existing-resources/index.mdx deleted file mode 100644 index 1026dbb75ec..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/existing-resources/index.mdx +++ /dev/null @@ -1,29 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; -import { getChildPageNodes } from '@/utils/getChildPageNodes'; - -export const meta = { - title: 'Existing AWS resources', - description: - 'Use the Amplify CLI or AWS CDK to connect to existing AWS resources.', - platforms: [ - 'flutter', - ], - route: '/[platform]/build-a-backend/existing-resources' -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - const childPageNodes = getChildPageNodes(meta.route); - return { - props: { - platform: context.params.platform, - meta, - childPageNodes - } - }; -} - - diff --git a/src/pages/[platform]/prev/build-a-backend/functions/build-options/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/build-options/index.mdx deleted file mode 100644 index 0b4019f0afd..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/functions/build-options/index.mdx +++ /dev/null @@ -1,141 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Build options', - description: "Use build options for the function category in Amplify to execute a script before a function is deployed, for example, to transpile Typescript or ES6 with Babel into a format that is supported by the AWS Lambda's node runtime.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/build-options/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - - - - -In some cases, it might be necessary to execute a script before a function is deployed, e.g. to transpile Typescript or ES6 with Babel or with `tsc` into a format that is supported by the AWS Lambda's node runtime. `amplify push` will look for a `script` definition in the project root's `package.json` with the name `amplify:` and run it right after `npm install` is called in the function resource's `src` directory. - -**Example: Transpiling Typescript code with TSC** - -Make sure you have the `tsc` command installed globally by running `npm install -g typescript` or locally by running `npm install --save-dev typescript` - -Let's say, a function resource has been created with `amplify function add` and it is called `generateReport`. The ES6 source code for this function is located in `amplify/backend/function/generateReport/lib` and the resource's `src` directory only contains the auto-generated `package.json` for this function. In order to compile TypeScript, you have to add the following script definition to your project root's `package.json`: - -```json -{ - "scripts": { - "amplify:generateReport": "cd amplify/backend/function/generateReport && tsc -p ./tsconfig.json && cd -" - }, -} -``` - -Navigate into `amplify/backend/function/generateReport` and create `tsconfig.json` then add the following to it: - -```json -{ - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "lib": ["dom", "esnext"], - "module": "commonjs", - "moduleResolution": "node", - "skipLibCheck": true, - "resolveJsonModule": true, - "outDir": "./src", - "baseUrl": "./", - "rootDir": "./lib", - "paths": { - "src": ["./lib"] - } - } -} -``` - -**Note:** It is important to note that if you are using `aws-sdk` in your TypeScript file, you will get a timeout if you attempt to import it with the following: - -```js -import AWS from 'aws-sdk'; -``` - -Change to this: - -```js -import * as AWS from 'aws-sdk'; -``` - -Once you run `amplify push`, the `amplify:generateReport` script will be executed, either by `yarn` or by `npm` depending on the existence of a `yarn.lock` file in the project root directory. - -**Example: Transpiling ES6 code with Babel** - -Let's say, a function resource has been created with `amplify function add` and it is called `generateReport`. The ES6 source code for this function is located in `amplify/backend/function/generateReport/lib` and the resource's `src` directory only contains the auto-generated `package.json` for this function. In order to run Babel, you have to add the following script definition and dev dependencies to your project root's `package.json`: - -```json -{ - "scripts": { - "amplify:generateReport": "cd amplify/backend/function/generateReport && babel lib -d src && cd -" - }, - "devDependencies": { - "@babel/cli": "^7.5.5", - "@babel/preset-env": "^7.5.5", - } -} -``` - -Babel needs to be configured properly so that the transpiled code can be run on AWS Lambda. This can be done by adding a `.babelrc` file to the resource folder (`amplify/backend/function/generateReport/.babelrc` in this case): - -```json -{ - "presets": [ - [ - "env", - { - "exclude": ["transform-regenerator"], - "targets": { - "node": "10.18" - } - } - ] - ], - "plugins": [ - "transform-async-to-generator", - "transform-exponentiation-operator", - "transform-object-rest-spread" - ] -} -``` - -Once you run `amplify push`, the `amplify:generateReport` script will be executed, either by `yarn` or by `npm` depending on the existence of a `yarn.lock` file in the project root directory. - - - - - -There are no existing build options for Python functions. The process of building and packaging Python functions is in line with Amazon's [existing documentation](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-venv) for manually creating a Lambda deployment package which depends on a virtual environment. - -Amplify will run `pipenv install` in your function's source directory during builds using either Pipenv's default virtual environment, or whichever virtual environment happens to be active. Then, during the packaging stage, the contents of the `site-packages` directory for that virtual environment will be zipped up along with the function-specific files. - -The contents of the Python build can include local development dependencies (e.g. for testing) in addition to those necessary for your function to run. Packages installed as "editable" (using the `-e` flag) will not be packaged, as they are represented as an `.egg-link` file pointing to the local, editable code of the dependency. - - - - diff --git a/src/pages/[platform]/prev/build-a-backend/functions/configure-options/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/configure-options/index.mdx deleted file mode 100644 index 8d6b61eab10..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/functions/configure-options/index.mdx +++ /dev/null @@ -1,114 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure Lambda function settings', - description: 'Learn how to configure custom settings for your Lambda function', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/configure-options/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -You may want to override the Amplify CLI default configurations for your Lambda function or configure changes not available within the `amplify add function` workflow. - -*Example*: When creating a `Node.js` function, the CLI will automatically configure a runtime version, a default memory size, and more. There are a few things you may want to override or configure: - -1. Runtime -2. Memory size -3. Environment variables - -Let's look at how to update all of these things. - -## Updating the Runtime - -You may want to tweak the runtime version to be either a newer or older version than the Amplify-generated default. - -Let's say we've deployed a Lambda function using a Node.js runtime and you want to modify the version of the runtime to be `14.x`. - -To do so, open __amplify/backend/function/function-name/function-name-cloudformation-template.json__ and set the `Runtime` property in the `LambdaFunction` resource to: - -```json -"Resources": { - "LambdaFunction": { - ... - "Properties": { - "Runtime": "nodejs14.x", // Runtime now set to 14.x - "Layers": [], - } - ... - } - }, -} -``` - -Next, deploy the updates using the Amplify CLI: - -```sh -amplify push -``` - -## Updating the default memory size - -When you deploy a function with Amplify, the default memory size will be set to a low setting (128MB). Often you will want to increase the default memory size in order to improve performance. A popular memory setting in Lambda is 1024MB as it speeds the function noticeably while usually keeping the cost the same or close to it. - -To update the memory size, open __amplify/backend/function/function-name/function-name-cloudformation-template.json__ and set the `MemorySize` property in the `LambdaFunction` resource: - -```json -"Resources": { - "LambdaFunction": { - ... - "Properties": { - "Runtime": "nodejs14.x", - "MemorySize": 1024, // Memory size now set to 1024 MB - "Layers": [], - } - ... - } - }, -} -``` - -Next, deploy the updates using the Amplify CLI: - -```sh -amplify push -``` - -_To learn more about optimizing resources allocation for Lambda functions, check out [this](https://dev.to/aws/deep-dive-finding-the-optimal-resources-allocation-for-your-lambda-functions-35a6) blog post._ - -## Setting an environment variable - -A very common scenario is the need to set and use an environment variable in your Lambda function. - -There are generally two types of environment variables: -- [Secret values (example: access keys, API keys etc.)](/[platform]/build-a-backend/functions/secrets/) -- [Non-secret values (example: endpoint information, locale information etc.)](/[platform]/build-a-backend/functions/environment-variables/) - - - -To view all configuration options available in AWS Lambda, check out the documentation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-environment.html) - -To learn more about extending the Amplify CLI with custom resources, check out the documentation [here](/[platform]/tools/cli/custom/cdk/) - - diff --git a/src/pages/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx deleted file mode 100644 index dea2617a370..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx +++ /dev/null @@ -1,86 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Environment variables', - description: 'Configure environment variables for AWS Lambda functions', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/environment-variables/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -Amplify CLI allows you to configure environment variables for your Lambda functions. Each Amplify environment can have a different environment variable value. This enables use cases such as switching between dev and prod URLs depending on the environment. - -> Environment variables should NOT be used for storing sensitive configuration values such as database passwords, API keys, or access tokens. Use [function secrets configuration](/[platform]/build-a-backend/functions/secrets/) instead! - -## Configuring environment variables -To configure a new function with environment variables, run `amplify add function`, select `yes` to the advanced settings prompt and select `yes` to the environment variables configuration prompt. From there, you will be able to specify a key and value for the environment variable. - -```console -$ amplify add function -... -? Do you want to configure advanced settings? Yes -... -? Do you want to configure environment variables for this function? Yes -? Enter the environment variable name: API_URL -? Enter the environment variable value: https://example.com/test -? Select what you want to do with environment variables: (Use arrow keys) - Add new environment variable - Update existing environment variables - Remove existing environment variables -> I'm done -``` - -To configure environment variables for an existing function, run `amplify update function`, and select `Environment variables configuration`. You can then add, update, or remove environment variables. - -```console -$ amplify update function -... -? Which setting do you want to update? - Resource access permissions - Scheduled recurring invocation - Lambda layers configuration -> Environment variables configuration - Secret values configuration -? Select what you want to do with environment variables: -> Add new environment variable - Update existing environment variables - Remove existing environment variables - I'm done -``` - -## Multi-environment flows -When creating a new Amplify environment using `amplify env add`, Amplify CLI asks if you want to apply all environment variable values to the new environment or modify them. If you choose to apply the existing values, you can still make edits anytime by running `amplify update function`. - -When creating a new Amplify environment using `amplify env add --yes`, Amplify CLI will apply all environment variable values from the current environment to the new environment. - -In multi-environment workflows, you may have added a new environment variable in one Amplify environment and then checked out a different Amplify environment. In this case, on the next `amplify push`, Amplify CLI will detect that there is a new environment variable that does not have a value specified in the current environment and prompt for one. -Running `amplify push --yes` in this case will fail with a message explaining the missing environment variable values. - -In [**git-based** multi-environment workflows](/[platform]/tools/cli/teams/), you may run into errors during deployment. For example, this happens when you add an environment variable in `envA` (corresponding to a git branch `branchA`), then `amplify checkout envB` and `git checkout branchB` and `git merge` branchA into branchB. Upon pushing `envB`, Amplify CLI detects that a new environment variable has been added but can't infer a value for it. To resolve this issue, run the following commands in the terminal: - -1. `amplify env checkout ` -2. `amplify push` - when prompted, enter a new value for the environment variable(s) -3. `git commit` -4. `git push` diff --git a/src/pages/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx deleted file mode 100644 index 51cbb4bed84..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx +++ /dev/null @@ -1,504 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Call a GraphQL API from a Lambda function', - description: 'Interact with a GraphQL API from a Lambda function.', - platforms: [ - 'flutter', - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -You can call an AppSync GraphQL API from a Node.js app or a Lambda function. Take a basic `Todo` app as an example: - -```graphql -type Todo @model @auth(rules: [{ allow: public }]) { - name: String - description: String -} -``` - -This API will have operations available for `Query`, `Mutation`, and `Subscription`. Let's take a look at how to perform both a **query** as well as a **mutation** from a Lambda function using Node.js. - -## Utilizing Lambda function template (IAM authorization) - -First, create a Lambda function with `amplify add function` and choose the `AppSync - GraphQL API request (with IAM)` to get started. Be sure to grant access to your GraphQL API when prompted by the CLI to grant access to other resources in the project. Alternatively, you can create the [function from scratch](#create-from-scratch). - -```console -amplify add function -? Select which capability you want to add: Lambda function (serverless function) -? Provide an AWS Lambda function name: myfunction -? Choose the runtime that you want to use: NodeJS -? Choose the function template that you want to use: AppSync - GraphQL API request (with IAM) - -Available advanced settings: -- Resource access permissions -- Scheduled recurring invocation -- Lambda layers configuration -- Environment variables configuration -- Secret values configuration - -? Do you want to configure advanced settings? Yes -? Do you want to access other resources in this project from your Lambda function? Yes -? Select the categories you want this function to have access to. api -? Select the operations you want to permit on Query, Mutation, Subscription - -You can access the following resource attributes as environment variables from your Lambda function - API__GRAPHQLAPIENDPOINTOUTPUT - API__GRAPHQLAPIIDOUTPUT - API__GRAPHQLAPIKEYOUTPUT - ENV - REGION -``` - - - -The function can only be added when the GraphQL API with IAM authorization exists. - - - -### Create from scratch - -```console -amplify add function -? Select which capability you want to add: Lambda function (serverless function) -? Provide an AWS Lambda function name: myfunction -? Choose the runtime that you want to use: NodeJS -? Choose the function template that you want to use: Hello World - -Available advanced settings: -- Resource access permissions -- Scheduled recurring invocation -- Lambda layers configuration -- Environment variables configuration -- Secret values configuration - -? Do you want to configure advanced settings? Yes -? Do you want to access other resources in this project from your Lambda function? Yes -? Select the categories you want this function to have access to. api -? Select the operations you want to permit on Query, Mutation, Subscription - -You can access the following resource attributes as environment variables from your Lambda function - API__GRAPHQLAPIENDPOINTOUTPUT - API__GRAPHQLAPIIDOUTPUT - API__GRAPHQLAPIKEYOUTPUT - ENV - REGION -``` - -The examples on this page use [`node-fetch`](https://www.npmjs.com/package/node-fetch) to make a HTTP request to your GraphQL API. When the Node.js v18 runtime is released for Lambda this dependency can be removed in favor of [native `fetch`](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#fetch) To get started, add the `node-fetch` module as a dependency: - -**CommonJS**: - -For functions written using CommonJS, you will need to install version 2 of `node-fetch` - -```diff -{ - "name": "myfunction", - "version": "2.0.0", - "description": "Lambda function generated by Amplify", - "main": "index.js", - "license": "Apache-2.0", -+ "dependencies": { -+ "node-fetch": "2" -+ }, - "devDependencies": { - "@types/aws-lambda": "^8.10.92" - } -} -``` - -**ESM**: - -```diff -{ - "name": "myfunction", -+ "type": "module", - "version": "2.0.0", - "description": "Lambda function generated by Amplify", - "main": "index.js", - "license": "Apache-2.0", -+ "dependencies": { -+ "node-fetch": "^3.2.3" -+ }, - "devDependencies": { - "@types/aws-lambda": "^8.10.92" - } -} -``` - -## Query - -Using an API Key for authenticating your requests, you can query the GraphQL API to get a list of all `Todo`s. To paginate over the list queries, you need to pass in a `limit` and `nextToken` on the `listTodos` query. See more at [GraphQL pagination ](/[platform]/build-a-backend/graphqlapi/query-data/). - -{/* prettier-ignore */} -```js -import { default as fetch, Request } from 'node-fetch'; - -const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; -const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; - -const query = /* GraphQL */ ` - query LIST_TODOS { - listTodos { - items { - id - name - description - } - } - } -`; - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -export const handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - - /** @type {import('node-fetch').RequestInit} */ - const options = { - method: 'POST', - headers: { - 'x-api-key': GRAPHQL_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ query }) - }; - - const request = new Request(GRAPHQL_ENDPOINT, options); - - let statusCode = 200; - let body; - let response; - - try { - response = await fetch(request); - body = await response.json(); - if (body.errors) statusCode = 400; - } catch (error) { - statusCode = 400; - body = { - errors: [ - { - status: response.status, - message: error.message, - stack: error.stack - } - ] - }; - } - - return { - statusCode, - body: JSON.stringify(body) - }; -}; -``` - -## Mutation - -In this example you will create a mutation showing how to pass in variables as arguments to create a `Todo` record. - -{/* prettier-ignore */} -```js -import { default as fetch, Request } from 'node-fetch'; - -const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; -const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; - -const query = /* GraphQL */ ` - mutation CREATE_TODO($input: CreateTodoInput!) { - createTodo(input: $input) { - id - name - createdAt - } - } -`; - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -export const handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - - const variables = { - input: { - name: 'Hello, Todo!' - } - }; - - /** @type {import('node-fetch').RequestInit} */ - const options = { - method: 'POST', - headers: { - 'x-api-key': GRAPHQL_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ query, variables }) - }; - - const request = new Request(GRAPHQL_ENDPOINT, options); - - let statusCode = 200; - let body; - let response; - - try { - response = await fetch(request); - body = await response.json(); - if (body.errors) statusCode = 400; - } catch (error) { - statusCode = 400; - body = { - errors: [ - { - status: response.status, - message: error.message, - stack: error.stack - } - ] - }; - } - - return { - statusCode, - body: JSON.stringify(body) - }; -}; -``` - -## IAM Authorization - -Let's take a look at another example schema that uses `iam` authorization. - -```graphql -type Todo @model @auth(rules: [{ allow: private, provider: iam }]) { - name: String - description: String -} -``` - -The CLI will automatically configure the Lambda execution IAM role to call the GraphQL API. Before writing your Lambda function you will first need to install the appropriate AWS SDK v3 dependencies: - -```diff -{ - "name": "myfunction", -+ "type": "module", - "version": "2.0.0", - "description": "Lambda function generated by Amplify", - "main": "index.js", - "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-crypto/sha256-js": "^2.0.1", -+ "@aws-sdk/credential-provider-node": "^3.76.0", -+ "@aws-sdk/protocol-http": "^3.58.0", -+ "@aws-sdk/signature-v4": "^3.58.0", -+ "node-fetch": "^3.2.3" -+ }, - "devDependencies": { - "@types/aws-lambda": "^8.10.92" - } -} -``` - -Then, the following example will sign the request to call the GraphQL API using IAM authorization. - -{/* due to placeholder text */} - -{/* prettier-ignore */} -```js -import crypto from '@aws-crypto/sha256-js'; -import { defaultProvider } from '@aws-sdk/credential-provider-node'; -import { SignatureV4 } from '@aws-sdk/signature-v4'; -import { HttpRequest } from '@aws-sdk/protocol-http'; -import { default as fetch, Request } from 'node-fetch'; - -const { Sha256 } = crypto; -const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; -const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; - -const query = /* GraphQL */ ` - query LIST_TODOS { - listTodos { - items { - id - name - description - } - } - } -`; - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -export const handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - - const endpoint = new URL(GRAPHQL_ENDPOINT); - - const signer = new SignatureV4({ - credentials: defaultProvider(), - region: AWS_REGION, - service: 'appsync', - sha256: Sha256 - }); - - const requestToBeSigned = new HttpRequest({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - host: endpoint.host - }, - hostname: endpoint.host, - body: JSON.stringify({ query }), - path: endpoint.pathname - }); - - const signed = await signer.sign(requestToBeSigned); - const request = new Request(GRAPHQL_ENDPOINT, signed); - - let statusCode = 200; - let body; - let response; - - try { - response = await fetch(request); - body = await response.json(); - if (body.errors) statusCode = 400; - } catch (error) { - statusCode = 500; - body = { - errors: [ - { - message: error.message - } - ] - }; - } - - return { - statusCode, - body: JSON.stringify(body) - }; -}; -``` - -### CommonJS - -When writing functions with CommonJS, you will need to install version 2 of `node-fetch`: - -```diff -{ - "name": "myfunction", - "version": "2.0.0", - "description": "Lambda function generated by Amplify", - "main": "index.js", - "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-crypto/sha256-js": "^2.0.1", -+ "@aws-sdk/credential-provider-node": "^3.76.0", -+ "@aws-sdk/protocol-http": "^3.58.0", -+ "@aws-sdk/signature-v4": "^3.58.0", -+ "node-fetch": "2" -+ }, - "devDependencies": { - "@types/aws-lambda": "^8.10.92" - } -} -``` - -Similar to the example above you can now write your handler. The difference here is the use of `require()` rather than `import ... from` - -```js -const { Sha256 } = require('@aws-crypto/sha256-js'); -const { defaultProvider } = require('@aws-sdk/credential-provider-node'); -const { SignatureV4 } = require('@aws-sdk/signature-v4'); -const { HttpRequest } = require('@aws-sdk/protocol-http'); -const { default: fetch, Request } = require('node-fetch'); - -const GRAPHQL_ENDPOINT = - process.env.API_ < YOUR_API_NAME > _GRAPHQLAPIENDPOINTOUTPUT; -const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; - -const query = /* GraphQL */ ` - query LIST_TODOS { - listTodos { - items { - id - name - description - } - } - } -`; - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -exports.handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - - const endpoint = new URL(GRAPHQL_ENDPOINT); - - const signer = new SignatureV4({ - credentials: defaultProvider(), - region: AWS_REGION, - service: 'appsync', - sha256: Sha256 - }); - - const requestToBeSigned = new HttpRequest({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - host: endpoint.host - }, - hostname: endpoint.host, - body: JSON.stringify({ query }), - path: endpoint.pathname - }); - - const signed = await signer.sign(requestToBeSigned); - const request = new Request(GRAPHQL_ENDPOINT, signed); - - let statusCode = 200; - let body; - let response; - - try { - response = await fetch(request); - body = await response.json(); - if (body.errors) statusCode = 400; - } catch (error) { - statusCode = 500; - body = { - errors: [ - { - message: error.message - } - ] - }; - } - - return { - statusCode, - body: JSON.stringify(body) - }; -}; -``` diff --git a/src/pages/[platform]/prev/build-a-backend/functions/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/index.mdx deleted file mode 100644 index be5e840404b..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/functions/index.mdx +++ /dev/null @@ -1,36 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; -import { getChildPageNodes } from '@/utils/getChildPageNodes'; - -export const meta = { - title: 'Functions', - description: 'Build and deploy serverless functions to perform various tasks, such as handling API requests, executing backend logic, or integrating with other AWS services.', - platforms: [ - 'flutter', - ], - route: '/[platform]/build-a-backend/functions', - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - const childPageNodes = getChildPageNodes(meta.route); - return { - props: { - platform: context.params.platform, - meta, - childPageNodes - } - }; -} - - diff --git a/src/pages/[platform]/prev/build-a-backend/functions/layers/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/layers/index.mdx deleted file mode 100644 index 6ebdbfaf42b..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/functions/layers/index.mdx +++ /dev/null @@ -1,229 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Reuse code and assets using layers', - description: "Use Amplify CLI's Lambda layer capability to reuse code & assets across functions.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/layers/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -Lambda layers allow you to pull common code & assets for your Lambda function into a centralized location. With Lambda layers you can: - -1. _*Re-use your code & assets*_: Your Lambda functions can leverage these layers to reuse shared code & assets across functions -2. _*Speed up function deployments*_: Iterating on your Lambda function will be significantly faster because it can be independently updated from the layer which usually contains the bulk of large static content - -> **Known limitation**: Functions using a layer can't be mocked locally using `amplify mock`. We recommend you to create a dev environment and test the functions inside the AWS Lambda console. - -![Lambda layer architecture diagram](/images/layers-architecture.gif) - -The general workflow breaks down into the following steps: - -1. Create a Lambda layer -2. Add shared code & assets to the layer -3. Add the Lambda layer to a function -4. Deploy the layer & function - -## Create a Lambda Layer - -To create a layer, run the following command within your Amplify project: - -```bash -amplify add function -``` - -Select `Lambda layer` as the capability to add - -```console -? Select which capability you want to add: -> Lambda layer (shared code & resource used across functions) -``` - -```console -? Provide a name for your Lambda layer: (layer-name) -? Choose the runtime that you want to use: (Use arrow keys) -❯ NodeJS - Python -``` - -Next, you'll be guided through a workflow to provide a **layer name**, and select a **supported runtime**. Currently Amplify CLI provides NodeJS or Python runtime support for layers. - -```console -? The current AWS account will always have access to this layer. - Optionally, configure who else can access this layer. (Hit to skip) -◯ Specific AWS accounts -◯ Specific AWS organization -◯ Public (everyone on AWS can use this layer) -``` - -After that, you'll be asked to configure your **layer's permission**. - -The **current AWS account will always have access to this layer**. -In addition, the customer can configure access for: - -- **Specific AWS accounts**: provide a comma-separated list of AWS Account IDs to provide access to them. -- **Specific AWS organizations**: provide a comma-separated list of AWS Organization IDs to provide access to them. *AWS Organization IDs start with `o-`.* -- **Public**: make this layer available for everyone AWS. Anyone in AWS can reference this layer using its ARN. - -```console -Next steps: -Move your libraries to the following folder: -[NodeJS]: amplify/backend/function//lib/nodejs - -Include any files you want to share across runtimes in this folder: -amplify/backend/function//opt - -"amplify function update " - configure a function with this Lambda layer -"amplify push" - builds all of your local backend resources and provisions them in the cloud -``` - -Your Lambda layer is ready to use after the permissions are set up. - -## Add shared code & assets - -Now that your layer is set up, you'll see a new folder with the `layer-name` added to `amplify/backend/function/`. The respective runtime's folder structure is autogenerated. - -### Add shared code - - - - - -A `nodejs` folder is auto-generated for you. In there you'll find an empty `package.json` file and a `node_modules` folder. If you want to offload other node_modules you can either: - -1. `cd` into the `nodejs` folder and add the dependencies into the `package.json` file, or -2. move all your existing function's `node_modules` content into the layer's `node_modules` folder - -Any dependency listed within the layer's `package.json` file will be installed and packaged during `amplify push`. - -Any node module that is in the layer's `node_modules` folder can be accessed from the function as if the node module is in the function's `node_modules` folder. - -*In order to take advantage of Lambda layer's for your NodeJS function, you don't even need to update your function's code!* - - - - - -A `python` folder is auto-generated for you. In there you'll find an empty `Pipfile` file. Any packages listed within the layer's `Pipfile` file will be installed and packaged during `amplify push`. You can `import` these packages from within your Python function just like any other package within your Python function. - - - - - -### Add shared assets - -Any assets like large images or other files that you want to share across various functions can be placed in the `amplify/backend/function//opt/` folder. Your function's code can import any assets by looking for files in the `/opt/` path. - -### Lambda layer versions - -Every time `amplify push` or `amplify update function` is run, Amplify CLI checks if a layer's content has changed and automatically creates a new *layer version*. Layer versions are immutable and functions always use a specific layer version. - -In order to speed up deployments when vast amount of node_modules exist, Amplify CLI scans only for changes within each module's `package.json` file. If you don't see Amplify CLI detect your latest changes, verify that at least of your node module's `package.json` content has changed. - -## Add a layer to a function - -You can either create a new function and add Lambda layers by running `amplify add function` or add layers to an existing function using `amplify update function`. Select `Lambda function` when prompted and you'll be presented the following question during the guided flow: - -```console -... -? Do you want to enable Lambda layers for this function? Yes -? Provide existing layers or select layers in this project to access from this function (pick up to 5): - ◯ Provide existing Lambda layer ARNs -❯◉ myamplifylayer1 - ◯ myamplifylayer2 -``` - -You can either add an existing layer in AWS by referencing its ARN or select a layer from your Amplify project that's listed below. - -```console -? Select a version for myamplifylayer1: -❯ Always choose latest version - 2: Updated layer version 2021-06-08T05:33:42.651Z - 1: Updated layer version 2021-06-08T05:30:43.101Z -``` - -When adding a layer from your Amplify project, you'll also be able to select a specific layer version or always choose the latest layer version. The largest layer version number represents the most recent changes. - -```console -? Modify the layer order: -(Layers with conflicting files will overwrite contents of layers earlier in the list): -- layer2 -- layer3 -- layer6 -- -- -``` - -Given that layers can have overlapping contents, the order of the layer matters. You can adjust the layer's order if needed in the next step. - -Now, you've successfully added a layer to your function. - -## Deploy Lambda layers & functions with Lambda layers - -Once you're ready with your changes in your layer and functions, you can deploy them by running `amplify push`. - -If a layer’s content has been updated and it has permissions associated, Amplify CLI will prompt you whether you want to carry the permissions forward to a newer version. - -```console -Content changes in Lambda layers detected. -Suggested configuration for new layer versions: - -myamplifylayer1 - - Description: Updated layer version 2021-06-08T05:33:42.651Z - -? Accept the suggested layer version configurations? (Y/n) -``` - -During `amplify push`, you get to modify the layer version description. By default, Amplify CLI will populate the description as `Updated layer version `. - -## Update layer content - -Any file changes within a layer's folder are automatically tracked by Amplify CLI. If there are changes available, the Amplify CLI will create a new layer version with the changes. - -## Update layer settings - -You can update layer's permissions by running `amplify update function` and selecting `Lambda layer`. - -Next, you'll be prompted to select the layer for which you want to update the settings for. - -#### Note: Update Layer Permissions from Public to Specific - - To update a lambda layer from Public access to Specific (Account/Organization) access, please remember to remove Public access by **un-selecting** the option in the 'amplify update' CLI flow before selecting a specific AWS account/organization. - - If you have already selected 'Public' access, just adding additional 'specific' AWS accounts/organizations will not have any effect on the Lambda Layer configuration. It will not automatically remove Public access. - -## Remove a layer - -To remove a Lambda layer, run the `amplify function remove` command and select `Lambda layers`. Next, you'll be prompted to select which layer to remove. You can delete specific layer versions or all of them. - -> Warning: When you delete a layer, you can no longer configure functions to use it. However, any function that already uses the layer continues to have access to it. - -```console -? Choose the resource you would want to remove (layer) -When you delete a layer version, you can no longer configure functions to use it. -However, any function that already uses the layer version continues to have access to it. -? Choose the Layer versions you want to remove. -❯◯ 1: Updated layer version 2021-06-08T05:30:43.101Z - ◯ 2: Updated layer version 2021-06-08T05:33:42.651Z -? Are you sure you want to delete the resource? This action deletes all files related to this resource from the backend directory. (Y/n) -``` diff --git a/src/pages/[platform]/prev/build-a-backend/functions/secrets/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/secrets/index.mdx deleted file mode 100644 index fcf0f3862cc..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/functions/secrets/index.mdx +++ /dev/null @@ -1,105 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Access secret values', - description: 'Configure Lambda functions to securely access secret values', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/secrets/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -Amplify CLI allows you to configure secret values that can be securely accessed from a Lambda function. Each Amplify environment can have a different secret value. This enables use cases such as different API keys for a dev and prod environment. Secrets should be used for values such as database passwords, API keys, and access tokens. - -> To access non-sensitive configuration values in a Lambda function, use [environment variables](/[platform]/build-a-backend/functions/environment-variables/). - -## Configuring secret values -To configure a new function with secret values, run `amplify add function`, select `yes` to the advanced settings prompt and select `yes` to the secrets configuration prompt. From there, you can specify the name and value of the secret. - -```console -$ amplify add function -... -? Do you want to configure advanced settings? Yes -... -? Do you want to configure secret values this function can access? Yes -? Enter a secret name (this is the key used to look up the secret value): API_KEY -? Enter the value for API_KEY: [hidden] -? What do you want to do? (Use arrow keys) - Add a secret - Update a secret - Remove secrets -> I'm done -``` - -To configure secrets for an existing function, run `amplify update function`, and select `Secret values configuration`. You can then add, update and remove secret values. - -```console -$ amplify update function -... -? Which setting do you want to update? - Resource access permissions - Scheduled recurring invocation - Lambda layers configuration - Environment variables configuration -> Secret values configuration -? What do you want to do? -> Add a secret - Update a secret - Remove secrets - I'm done -``` - -> Note: Amplify CLI never stores secrets locally. All secret values are immediately stored in [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) using the SecureString parameter type. - -## Accessing the values in your function -To access the secret values in your Lambda function, use the [AWS SSM GetParameter API](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html). Amplify CLI will automatically supply the SSM parameter name of the secret as an environment variable to the function. This value can be passed into the API call as the "Name" to retrieve the value. Ensure that the API call has "WithDecryption" specified as `true`. - -If your Lambda function is using the Node.js runtime, a comment block will be placed at the top of your `index.js` file with example code to retrieve the secret values. - -```js -const aws = require('aws-sdk'); - -const { Parameters } = await (new aws.SSM()) - .getParameters({ - Names: ["EXAMPLE_SECRET_1", "EXAMPLE_SECRET_2"].map(secretName => process.env[secretName]), - WithDecryption: true, - }) - .promise(); - -// Parameters will be of the form { Name: 'secretName', Value: 'secretValue', ... }[] -``` - -## Multi-environment flows -When creating a new Amplify environment using `amplify env add`, Amplify CLI asks if you want to apply all secret values to the new environment or modify them. If you choose to apply the existing values, you can still make edits anytime using `amplify update function`. - -When creating a new Amplify environment using `amplify env add --envName --yes`, Amplify CLI will apply all secret values from the current environment to the new environment. - -In multi-environment workflows, you may have added a new secret in one Amplify environment and then checked out a different Amplify environment. In this case, on the next `amplify push`. Amplify CLI will detect that there is a new secret that does not have a value specified in the current environment and prompt for one. Running `amplify push --yes` in this case will fail with a message explaining the missing secret values. - -In [**git-based** multi-environment workflows](/[platform]/tools/cli/teams/), you may run into errors during deployment. For example, this happens when you add a secret in `envA` (corresponding to a git branch `branchA`), then `amplify env checkout envB` and `git checkout branchB` and `git merge` branchA into branchB. Upon pushing `envB`, Amplify CLI detects that a new secret has been added but can't infer a value for it. To resolve this issue, run the following commands in the terminal: - -1. `amplify env checkout ` -2. `amplify push` - when prompted, enter a new value for the secret(s) -3. `git commit` -4. `git push` diff --git a/src/pages/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx b/src/pages/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx deleted file mode 100644 index 98144a46542..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx +++ /dev/null @@ -1,161 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Set up a function', - description: 'Use Amplify CLI to add powerful Lambda functions to your cloud-based mobile and web app with a simple guided workflow.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter' - ], - canonicalPath: '/javascript/build-a-backend/functions/set-up-function/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -## Set up a function - -You can add a Lambda function to your project which you can use alongside a REST API or as a datasource in your GraphQL API using the [`@function` directive](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#lambda-function-resolver). - -```bash -amplify add function -``` - -```console -? Select which capability you want to add: Lambda function (serverless function) -? Provide a friendly name for your resource to be used as a label for this category in the project: lambdafunction -? Provide the AWS Lambda function name: lambdafunction -? Choose the runtime that you want to use: NodeJS -? Choose the function template that you want to use: (Use arrow keys) -> Hello world function - CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB) - Serverless express function (Integration with Amazon API Gateway) -``` - -## Function templates - -- The `Hello World function` will create a basic hello world Lambda function -- The `CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB)` function will add a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) Lambda function template for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command) -- The `Serverless express function (Integration with Amazon API Gateway)` will add a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) Lambda function template with routing enabled for your REST API paths. - -You can update the Lambda execution role policies for your function to access other resources generated and maintained by the CLI using the CLI. - -```console -$ amplify update function -Please select the Lambda Function you would want to update: lambdafunction -? Which setting do you want to update? Resource access permissions -? Select the category (Press to select,
to toggle all, to invert selection) -> api - function - storage - auth -? Select the operations you want to permit on (Press to select, to toggle all, to invert selection) -> Query - Mutation - Subscription - -You can access the following resource attributes as environment variables from your Lambda function - API__GRAPHQLAPIENDPOINTOUTPUT - API__GRAPHQLAPIIDOUTPUT - API__GRAPHQLAPIKEYOUTPUT -``` - -Behind the scenes, the CLI automates populating of the resource identifiers for the selected resources as Lambda environment variables which you will see in your function code as well. This process additionally configures CRUD level IAM policies on the Lambda execution role to access these resources from the Lambda function. For instance, you might grant permissions to your Lambda function to read/write to a DynamoDB table in the Amplify project by using the above flow and the appropriate IAM policy would be set on that Lambda function's execution policy which is scoped to that table only. - -## Supported Lambda runtimes - -Amplify CLI enables you to create, test and deploy Lambda functions with the following runtimes: - -| Runtime | Default Version | Requirements | -| --- | --- | --- | -| NodeJS | 14.x | - Install [NodeJS](https://nodejs.org/en/) | -| Java | 11 | - Install [Java 11 JDK](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) and [Gradle 5+](https://docs.gradle.org/current/userguide/installation.html) | -| Go | 1.x | - Install [Go](https://golang.org/doc/install) | -| .NET Core | 3.1 | - Install [.NET Core SDK](https://docs.microsoft.com/en-us/dotnet/core/install/sdk) | -| Python | 3.8.x | - Install [python3](https://www.python.org/downloads/) and [pipenv](https://pypi.org/project/pipenv/)
- Ensure `python3` and `pipenv` commands are available in your `PATH` | - -In order to create and test Lambda functions locally, you need to have the runtime's requirements (table above) fulfilled. You'll be asked to `Choose the runtime you would like to use:` when running `amplify add function`. - -Once a runtime is selected, you can select a function template for the runtime to help bootstrap your Lambda function. - -## Access existing AWS resource from Lambda Function - -You can grant your Lambda function access to your existing resources. After running `amplify add function`, the CLI generates a `custom-policies.json` file under the folder `amplify/backend/function//`. The file is where you can specify the [resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html) and [actions](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html) that grant Lambda Function access to the specified AWS resources. - -### File Structure - -```json -[ - { - "Action": ["s3:CreateBucket"], - "Resource": ["arn:aws:s3:::*"] - } -] -``` - -**Action:** Specify the actions that are required to be granted to your AWS resource. Wild characters ‘\*’ is accepted. - -**Resource**: Specify resources that the AWS resource needs access. The resource accepts multiple Arns for a service and wild card character ‘\*’ is accepted. - -> Note: Specifying resource or action as ‘\*’ is not recommended as best practice. This gives the Amplify function resource Administrative privileges which should be avoided. - -If your Amplify resource requires access to multiple AWS services and resources, create another block to grant access to the additional services and resources. - -```json -[ - { - "Action": ["s3:CreateBucket"], - "Resource": ["arn:aws:s3:::*"] - }, - { - "Action": ["iam:GetPolicy"], - "Resource": ["arn:aws:iam:::policy/*"] - } -] -``` - -Optionally, the `Effect` field can be specified to use ‘Allow’ or ‘Deny’. If not specified the field defaults to ‘Allow’ - -```json -{ - "Action": ["s3:CreateBucket"], - "Resource": ["arn:aws:s3:::*"], - "Effect": "Allow" -} -``` - -### Multi-Environment Workflow - -To specify AWS ARN resources across environments, an optional `\${env}` parameter can be used for resources. The `\${env}` parameter in the AWS ARN resource will get populated with the current Amplify environment name at deployment. - -```json -"Resource": ["arn:aws:s3:::${env}my-bucket"] -``` - -### Next Step - -On running `amplify push` commands, the IAM policies specified in the `custom-policies.json` file will be appended to the existing IAM policy list tied to the Lambda Function's execution role. - -## Schedule recurring Lambda functions - -Amplify CLI allows you to schedule Lambda functions to be executed periodically (e.g every minute, hourly, daily, weekly, monthly or yearly). You can also formulate more complex schedules using AWS Cron Expressions such as: _“10:15 AM on the last Friday of every month”_. Review the [Schedule Expression for Rules documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions) for more details. - -To schedule your Lambda function, answer **Yes** to `Do you want to invoke this function on a recurring schedule?` in the `amplify add function` flow. Once you deploy a function, it'll create a CloudWatch Rule to periodically execute the selected Lambda function. - -For more information on files generated in the function resource folder, see [Function Category Files](/[platform]/tools/cli/reference/files/#function-category-files). diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx deleted file mode 100644 index 07a85a3f18d..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx +++ /dev/null @@ -1,151 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Batch put custom resolver', - description: 'Leverage GraphQL mutations to efficiently create multiple objects in one request rather than making sequential requests to create each object individually.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -Sometimes you need to create objects in bulk, rather than creating individual objects sequentially and waiting for all the requests to complete. - -1. Define your schema with a custom mutation. The custom mutation should not be deployed to AppSync beforehand if following these steps, the CLI will attach its own resolver preventing you from attaching a custom resource this way. -```graphql -type Todo @model { - id: ID! - name: String! - description: String -} - -type Mutation { - batchCreateTodo(todos: [BatchCreateTodo]): [Todo] -} - -input BatchCreateTodo { - id: ID - name: String! - description: String -} -``` - -2. [Create a custom resource for your resolver](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) and use the following code snippets as a guide to get started - -2. Follow the steps for creating a custom resolver: -```bash -amplify add custom -``` -```console -? How do you want to define this custom resource? -❯ AWS CDK -? Provide a name for your custom resource -❯ MyCustomResolvers -``` - -Next, install the AppSync dependencies for your custom resource: -```bash -cd amplify/backend/custom/MyCustomResolvers -npm i @aws-cdk/aws-appsync@~1.124.0 -``` - -Use the following template as a starting point for your custom CDK stack, the resolvers must be templated with environment references - -```ts -import * as cdk from '@aws-cdk/core'; -import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; -import * as appsync from '@aws-cdk/aws-appsync'; -import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; - -export class cdkStack extends cdk.Stack { - constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) { - super(scope, id, props); - /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ - new cdk.CfnParameter(this, 'env', { - type: 'String', - description: 'Current Amplify CLI env name', - }); - - // Access other Amplify Resources - const retVal:AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this, - amplifyResourceProps.category, - amplifyResourceProps.resourceName, - [{ - category: "api", - resourceName: "" - }] - ); - - const requestVTL = ` - ## [Start] Initialization default values. ** - $util.qr($ctx.stash.put("defaultValues", $util.defaultIfNull($ctx.stash.defaultValues, {}))) - #set( $createdAt = $util.time.nowISO8601() ) - #set($todosArray = []) - #foreach($item in \${ctx.args.todos}) - $util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId()))) - $util.qr($item.put("createdAt", $util.defaultIfNull($item.createdAt, $createdAt))) - $util.qr($item.put("updatedAt", $util.defaultIfNull($item.updatedAt, $createdAt))) - $util.qr($item.put("__typename", "Todo")) - $util.qr($todosArray.add($util.dynamodb.toMapValues($item))) - #end - ## [End] Initialization default values. ** - $util.toJson( { - "version": "2018-05-29", - "operation": "BatchPutItem", - "tables": { - "-${apiIdRef}-${envRef}": $todosArray - } - } ) - ` - const responseVTL = ` - ## [Start] ResponseTemplate. ** - #if( $ctx.error ) - $util.error($ctx.error.message, $ctx.error.type) - #else - $util.toJson($ctx.result.data.-${apiIdRef}-${envRef}) - #end - ## [End] ResponseTemplate. ** - `; - - - const resolver = new appsync.CfnResolver(this, "custom-resolver", { - // apiId: retVal.api.new.GraphQLAPIIdOutput, - // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 - // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. - // Previously the ApiId is the variable Name which is wrong , it should be variable value as below - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - fieldName: "querySomething", - typeName: "Query", // Query | Mutation | Subscription - requestMappingTemplate: requestVTL, - responseMappingTemplate: responseVTL, - dataSourceName: "TodoTable" // DataSource name - }) - } -} -``` - -By using CloudFormation parameters, you contextualize your custom resolvers to the environment you're working with. - -3. Run `amplify push` and deploy your API - -The full documentation for custom resolvers [is available here](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx deleted file mode 100644 index b4a94f5cf67..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx +++ /dev/null @@ -1,36 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; -import { getChildPageNodes } from '@/utils/getChildPageNodes'; - -export const meta = { - title: 'Best practice', - description: 'Best practices and examples for working with GraphQL.', - platforms: [ - 'flutter', - ], - route: '/[platform]/build-a-backend/graphqlapi/best-practice', - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - const childPageNodes = getChildPageNodes(meta.route); - return { - props: { - platform: context.params.platform, - meta, - childPageNodes - } - }; -} - - diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx deleted file mode 100644 index d65d4eee7fb..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx +++ /dev/null @@ -1,132 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'GraphQL query with sorting by date', - description: 'How to implement sorting in a GraphQL query', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/query-with-sorting/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -In this guide you will learn how to implement sorting in a GraphQL API. In our example, you will implement sorting results by date in either an ascending or descending order by implementing an additional data access pattern leveraging a DynamoDB Global Secondary Index using the `@index` GraphQL Transformer directive. - -### Overview - -To get started, let's start with a basic GraphQL schema for a Todo app: - -```graphql -type Todo @model { - id: ID! - title: String! -} -``` - -When the API is created with an `@model` directive, the following queries will automatically be created for you: - -```graphql -type Query { - getTodo(id: ID!): Todo - listTodos( - filter: ModelTodoFilterInput - limit: Int - nextToken: String - ): ModelTodoConnection -} -``` - -Next, take a look at the `ModelTodoConnection` type to get an idea of the data that will be returned when the `listTodos` query is run: - -```graphql -type ModelTodoConnection { - items: [Todo] - nextToken: String -} -``` - -By default, the `listTodos` query will return the `items` array **unordered**. Many times you will need these items to be ordered by title, by creation date, or in some other way. - -To enable this, you can use the [@index](/[platform]/build-a-backend/graphqlapi/data-modeling/) directive. This directive will allow you to set a custom `sortKey` on any field in your API. - -### Implementation - -In this example, you will enable sorting by the `createdAt` field. By default, Amplify will populate this `createdAt` field with a timestamp if none is passed in. - -To enable this, update your schema with the following: - -```graphql -type Todo @model { - id: ID! - title: String! - type: String! - @index( - name: "todosByDate" - queryField: "todosByDate" - sortKeyFields: ["createdAt"] - ) - createdAt: String! -} -``` - - - -When created a Todo, you must now populate the `type` field for this to work properly. - - - -Next, create a few todos being sure to populate the `type` field: - -```graphql -mutation createTodo { - createTodo(input: { title: "Todo 1", type: "Todo" }) { - id - title - } -} -``` - -Now, you can query for todos by date in an ascending or descending order using the new `todosByDate` query: - -```graphql -query todosByDate { - todosByDate(type: "Todo", sortDirection: ASC) { - items { - id - title - createdAt - } - } -} - -query todosByDateDescending { - todosByDate(type: "Todo", sortDirection: DESC) { - items { - id - title - createdAt - } - } -} -``` - -To learn more about the `@index` directive, check out the documentation [here](/[platform]/build-a-backend/graphqlapi/data-modeling/) diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx deleted file mode 100644 index ee4b595b00c..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx +++ /dev/null @@ -1,552 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Warehouse Management System', - description: 'Configure common access patters for your app following a warehouse management system example.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/warehouse-management/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -In this "Warehouse management system" example, you will learn how to configure common access patterns for your app. This example has the following types: - -- Warehouse -- Product -- Inventory -- Employee -- AccountRepresentative -- Customer - -These types have the following common access patterns: - -1. [Look up employee details by employee ID](#1-look-up-employee-details-by-employee-id) -2. [Query employee details by employee name](#2-query-employee-details-by-employee-name) -3. [Find an employee's phone number(s)](#3-find-an-employees-phone-number) -4. [Find a customer's phone number(s)](#4-find-a-customers-phone-number) -5. [Get orders for a given customer within a given date range](#5-get-orders-for-a-given-customer-within-a-given-date-range) -6. [Show all open orders within a given date range across all customers](#6-show-all-open-orders-within-a-given-date-range-across-all-customers) -7. [See all employees recently hired](#7-see-all-employees-hired-recently) -8. [Find all employees working in a given warehouse](#8-find-all-employees-working-in-a-given-warehouse) -9. [Get all items on order for a given product](#9-get-all-items-on-order-for-a-given-product) -10. [Get current inventories for a given product at all warehouses](#10-get-current-inventories-for-a-product-at-all-warehouses) -11. [Get customers by account representative](#11-get-customers-by-account-representative) -12. [Get orders by account representative and date](#12-get-orders-by-account-representative-and-date) -13. [Get all items on order for a given product](#13-get-all-items-on-order-for-a-given-product) -14. [Get all employees with a given job title](#14-get-all-employees-with-a-given-job-title) -15. [Get inventory by product and warehouse](#15-get-inventory-by-product-by-warehouse) -16. [Get total product inventory](#16-get-total-product-inventory) -17. [Get account representatives ranked by order total and sales period](#17-get-sales-representatives-ranked-by-order-total-and-sales-period) - -The following schema introduces the required indexes and relationships so that you can support these access patterns: - -```graphql -# This "input" configures a global authorization rule to enable public access to -# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/auth -input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY! - -type Order @model { - id: ID! - customerID: ID! @index(name: "byCustomerByStatusByDate", sortKeyFields: ["status", "date"]) @index(name: "byCustomerByDate", sortKeyFields: ["date"]) - accountRepresentativeID: ID! @index(name: "byRepresentativebyDate", sortKeyFields: ["date"]) - productID: ID! @index(name: "byProduct", sortKeyFields: ["id"]) - status: String! - amount: Int! - date: String! -} - -type Customer @model { - id: ID! - name: String! - phoneNumber: String - accountRepresentativeID: ID! @index(name: "byRepresentative", sortKeyFields: ["id"]) - ordersByDate: [Order] @hasMany(indexName: "byCustomerByDate", fields: ["id"]) - ordersByStatusDate: [Order] @hasMany(indexName: "byCustomerByStatusByDate", fields: ["id"]) -} - -type Employee @model { - id: ID! - name: String! @index(name: "byName", queryField: "employeeByName", sortKeyFields: ["id"]) - startDate: String! - phoneNumber: String! - warehouseID: ID! @index(name: "byWarehouse", sortKeyFields: ["id"]) - jobTitle: String! @index(name: "byTitle", queryField: "employeesByJobTitle", sortKeyFields: ["id"]) - newHire: String! @index(name: "newHire", queryField: "employeesNewHire", sortKeyFields: ["id"]) @index(name: "newHireByStartDate", queryField: "employeesNewHireByStartDate", sortKeyFields: ["startDate"]) -} - -type Warehouse @model { - id: ID! - employees: [Employee] @hasMany(indexName: "byWarehouse", fields: ["id"]) -} - -type AccountRepresentative @model { - id: ID! - customers: [Customer] @hasMany(indexName: "byRepresentative", fields: ["id"]) - orders: [Order] @hasMany(indexName: "byRepresentativebyDate", fields: ["id"]) - orderTotal: Int - salesPeriod: String @index(name: "bySalesPeriodByOrderTotal", queryField: "repsByPeriodAndTotal", sortKeyFields: ["orderTotal"]) -} - -type Inventory @model { - productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) - warehouseID: ID! @index(name: "byWarehouseID", queryField: "itemsByWarehouseID") - inventoryAmount: Int! -} - -type Product @model { - id: ID! - name: String! - orders: [Order] @hasMany(indexName: "byProduct", fields: ["id"]) - inventories: [Inventory] @hasMany(fields: ["id"]) -} -``` - -Now that you have the schema created, let's create the items in the database that you will be operating against: - -```graphql -# first -mutation createWarehouse { - createWarehouse(input: {id: "1"}) { - id - } -} - -# second -mutation createEmployee { - createEmployee(input: { - id: "amanda" - name: "Amanda", - startDate: "2018-05-22", - phoneNumber: "6015555555", - warehouseID: "1", - jobTitle: "Manager", - newHire: "true"} - ) { - id - jobTitle - name - newHire - phoneNumber - startDate - warehouseID - } -} - -# third -mutation createAccountRepresentative { - createAccountRepresentative(input: { - id: "dabit" - orderTotal: 400000 - salesPeriod: "January 2019" - }) { - id - orderTotal - salesPeriod - } -} - -# fourth -mutation createCustomer { - createCustomer(input: { - id: "jennifer_thomas" - accountRepresentativeID: "dabit" - name: "Jennifer Thomas" - phoneNumber: "+16015555555" - }) { - id - name - accountRepresentativeID - phoneNumber - } -} - -# fifth -mutation createProduct { - createProduct(input: { - id: "yeezyboost" - name: "Yeezy Boost" - }) { - id - name - } -} - -# sixth -mutation createInventory { - createInventory(input: { - productID: "yeezyboost" - warehouseID: "1" - inventoryAmount: 300 - }) { - productID - inventoryAmount - warehouseID - } -} - -# seventh -mutation createOrder { - createOrder(input: { - amount: 300 - date: "2018-07-12" - status: "pending" - accountRepresentativeID: "dabit" - customerID: "jennifer_thomas" - productID: "yeezyboost" - }) { - id - customerID - accountRepresentativeID - amount - date - customerID - productID - } -} -``` - -### 1. Look up employee details by employee ID - -This can simply be done by querying the employee model with an employee ID, no `@primaryKey` or `@index` need to be explicitly specified to make this work. - -```graphql -query getEmployee($id: ID!) { - getEmployee(id: $id) { - id - name - phoneNumber - startDate - jobTitle - } -} -``` - -### 2. Query employee details by employee name - -The `@index` `byName` on the `Employee` type makes this access-pattern feasible because under the hood an index is created and a query is used to match against the name field. You can use this query: - -```graphql -query employeeByName($name: String!) { - employeeByName(name: $name) { - items { - id - name - phoneNumber - startDate - jobTitle - } - } -} -``` - -### 3. Find an Employee’s phone number - -Either one of the previous queries would work to find an employee’s phone number as long as one has their ID or name. - -### 4. Find a customer’s phone number - -A similar query to those given above but on the Customer model would give you a customer’s phone number. - -```graphql -query getCustomer($customerID: ID!) { - getCustomer(id: $customerID) { - phoneNumber - } -} -``` - -### 5. Get orders for a given customer within a given date range - -There is a one-to-many relation that lets all the orders of a customer be queried. - -This relationship is created by having the `@index` name `byCustomerByDate` on the Order model that is queried by the `@hasMany` relationship on the orders field of the Customer model. - -A sort key with the date is used. What this means is that the GraphQL resolver can use predicates like `Between` to efficiently search the date range rather than scanning all records in the database and then filtering them out. - -The query one would need to get the orders to a customer within a date range would be: - -```graphql -query getCustomerWithOrdersByDate($customerID: ID!) { - getCustomer(id: $customerID) { - ordersByDate(date: { - between: [ "2018-01-22", "2020-10-11" ] - }) { - items { - id - amount - productID - } - } - } -} -``` - -### 6. Show all open orders within a given date range across all customers - -The `@index` `byCustomerByStatusByDate` enables you to run a query that would work for this access pattern. - -In this example, a composite sort key (combination of two or more keys) with the `status` and `date` is used. What this means is that the unique identifier of a record in the database is created by concatenating these two fields (status and date) together, and then the GraphQL resolver can use predicates like `between` or `contains` to efficiently search the unique identifier for matches rather than scanning all records in the database and then filtering them out. - -```graphql -query listCustomersWithOrdersByStatusDate { - listCustomers { - items { - ordersByStatusDate(statusDate: { - between: [ - { status: "pending", date: "2018-01-22" }, - { status: "pending", date: "2020-10-11" } - ]}) { - items { - id - amount - date - } - } - } - } -} -``` - -### 7. See all employees hired recently - -Having `@index(name: "newHire", fields: ["newHire", "id"])` on the `Employee` model allows one to query by whether an employee has been hired recently. - -```graphql -query employeesNewHire { - employeesNewHire(newHire: "true") { - items { - id - name - phoneNumber - startDate - jobTitle - } - } -} -``` - -You can also query and have the results returned by start date by using the `employeesNewHireByStartDate` query: - -```graphql -query employeesNewHireByDate { - employeesNewHireByStartDate(newHire: "true") { - items { - id - name - phoneNumber - startDate - jobTitle - } - } -} -``` - -### 8. Find all employees working in a given warehouse - -This needs a one to many relationship from warehouses to employees. As can be seen from the `@hasMany` relationship in the `Warehouse` model, this relationship uses the `byWarehouse` index on the `Employee` model. The relevant query would look like this: - -```graphql -query getWarehouse($warehouseID: ID!) { - getWarehouse(id: $warehouseID) { - id - employees{ - items { - id - name - startDate - phoneNumber - jobTitle - } - } - } -} -``` - -### 9. Get all items on order for a given product - -This access-pattern would use a one-to-many relation from products to orders. With this query you can get all orders of a given product: - -```graphql -query getProductOrders($productID: ID!) { - getProduct(id: $productID) { - id - orders { - items { - id - status - amount - date - } - } - } -} -``` - -### 10. Get current inventories for a product at all warehouses - -The query needed to get the inventories of a product in all warehouses would be: - -```graphql -query getProductInventoryInfo($productID: ID!) { - getProduct(id: $productID) { - id - inventories { - items { - warehouseID - inventoryAmount - } - } - } -} -``` - -### 11. Get customers by account representative - -This uses a has-many relationship between account representatives and customers: - -The query needed would look like this: - -```graphql -query getCustomersForAccountRepresentative($representativeId: ID!) { - getAccountRepresentative(id: $representativeId) { - customers { - items { - id - name - phoneNumber - } - } - } -} -``` - -### 12. Get orders by account representative and date - -As can be seen in the AccountRepresentative model this relationship uses the `byRepresentativebyDate` field on the `Order` model to create the connection needed. The query needed would look like this: - -```graphql -query getOrdersForAccountRepresentative($representativeId: ID!) { - getAccountRepresentative(id: $representativeId) { - id - orders(date: { - between: [ - "2010-01-22", "2020-10-11" - ] - }) { - items { - id - status - amount - date - } - } - } -} -``` - -### 13. Get all items on order for a given product - -This is the same as number 9. - -### 14. Get all employees with a given job title - -Using the `byTitle` `@index` makes this access pattern quite easy. - -```graphql -query employeesByJobTitle { - employeesByJobTitle(jobTitle: "Manager") { - items { - id - name - phoneNumber - jobTitle - } - } -} -``` - -### 15. Get inventory by product by warehouse - -Here having the inventories be held in a separate model is particularly useful since this model can have its own partition key and sort key such that the inventories themselves can be queried as is needed for this access-pattern. - -A query on this model would look like this: - -```graphql -query inventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { - getInventory(productID: $productID, warehouseID: $warehouseID) { - productID - warehouseID - inventoryAmount - } -} - -``` - -You can also get all inventory from an individual warehouse by using the `itemsByWarehouseID` query created by the `byWarehouseID` key: - -```graphql -query byWarehouseId($warehouseID: ID!) { - itemsByWarehouseID(warehouseID: $warehouseID) { - items { - inventoryAmount - productID - } - } -} -``` - -### 16. Get total product inventory - -How this would be done depends on the use case. If one just wants a list of all inventories in all warehouses, one could just run a list inventories on the Inventory model: - -```graphql -query listInventorys { - listInventorys { - items { - productID - warehouseID - inventoryAmount - } - } -} -``` - -### 17. Get sales representatives ranked by order total and sales period - -The sales period is either a date range or maybe even a month or week. Therefore you can set the sales period as a string and query using the combination of `salesPeriod` and `orderTotal`. You can also set the `sortDirection` in order to get the return values from largest to smallest: - -```graphql -query repsByPeriodAndTotal { - repsByPeriodAndTotal( - sortDirection: DESC, - salesPeriod: "January 2019", - orderTotal: { - ge: 1000 - }) { - items { - id - orderTotal - } - } -} -``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx deleted file mode 100644 index f50ed4399b4..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx +++ /dev/null @@ -1,241 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'JavaScript, Android, Swift, and Flutter client code generation', - description: "Amplify's codegen capabilities generate native code for iOS and Android, as well as types for Flow and TypeScript. Codegen can also generate GraphQL statements (queries, mutations, and subscriptions).", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/client-code-generation/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -"Codegen" generates native code for Swift (iOS), Java (Android), and JavaScript that represent your GraphQL API's data models. It can also generate GraphQL statements (queries, mutations, and subscriptions) so that you don't have to hand code them. - -The design of codegen functionality provides mechanisms to run at different points in your app development lifecycle, including when you create or update an API as well as independently when you want to just update the data fetching requirements of your app but leave your API alone. It additionally allows you to work in a team where the schema is updated or managed by another person. Finally, you can also include the codegen in your build process so that it runs automatically (such as from in Xcode). - -## Generate GraphQL client helper code for GraphQL APIs deployed with Amplify GraphQL CDK construct - -The necessary GraphQL client helper code differ from platform to platform. For JavaScript GraphQL client code, you need to reference the API ID that you receive after you deploy your application. For Android, iOS, and Flutter you can reference the local GraphQL schema to generate models for your API client. - -### JavaScript / TypeScript GraphQL API client helper code - -Go to your frontend app's root directory and run the following command in the Terminal: - -```bash -npx @aws-amplify/cli codegen add --apiId <...> --region <...> -``` - -This will download your API's schema and by default generate client helper code into the `src/graphql` folder. After every API deployment, you can rerun the following command to generate updated GraphQL statement and types: - -``` -npx @aws-amplify/cli codegen -``` - -### Generate "models" for Android, Swift, Flutter, and JavaScript DataStore - -The Android, Swift, Flutter, and DataStore on JavaScript use the "modelgen" pattern to interact with the client library. To generate models, run the following command from your frontend application's root directory: - -```bash -npx @aws-amplify/cli codegen models \ - --model-schema \ - --target [android|ios|flutter|javascript|typescript] \ - --output-dir ./ -``` - -## Generate GraphQL Client code with Amplify CLI-deployed GraphQL API - -### Create API then automatically generate code - -```bash -amplify init -amplify add api (select GraphQL) -amplify push -``` - -You’ll see questions as before, but now it will also automatically ask you if you want to generate GraphQL statements and do codegen. It will also respect the `./app/src/main` directory for Android projects. After the AppSync deployment finishes the Swift file will be automatically generated (Android you’ll need to kick off a [Gradle Build step](#androiduse)) and you can begin using in your app immediately. - -When you deploy your GraphQL API to the cloud, you are prompted to configure codegen. When a project is configured to generate code with codegen, it stores all the configuration `.graphqlconfig.yml` file in the root folder of your project. To make changes to the configuration, use `amplify configure codegen`. - -### Modify GraphQL schema, push, then automatically generate code - -During development, you might wish to update your GraphQL schema and generated code as part of an iterative dev/test cycle. Modify & save your schema in `amplify/backend/api//schema.graphql` then run: - -```bash -amplify push -``` - -Each time you will be prompted to update the code in your API and also ask you if you want to run codegen again as well, including regeneration of the GraphQL statements from the new schema. - -### No API changes, just update GraphQL statements & generate code - -One of the benefits of GraphQL is the client can define it's data fetching requirements independently of the API. Amplify codegen supports this by allowing you to modify the selection set (e.g. add/remove fields inside the curly braces) for the GraphQL statements and running type generation again. This gives you fine-grained control over the network requests that your application is making. Modify your GraphQL statements (default in the `./graphql` folder unless you changed it) then save the files and run: - -```bash -amplify codegen types -``` - -A new updated Swift file will be created (or run Gradle Build on Android for the same). You can then use the updates in your application code. - -## Shared schema, modified elsewhere (e.g. console or team workflows) - -Suppose you are working in a team and the schema is updated either from the AWS AppSync console or on another system. Your types are now out of date because your GraphQL statement was generated off an outdated schema. The easiest way to resolve this is to regenerate your GraphQL statements, update them if necessary, and then generate your types again. Modify the schema in the console or on a separate system, then run: - -```bash -amplify codegen statements -amplify codegen types -``` - -You should have newly generated GraphQL statements and Swift code that matches the schema updates. If you ran the second command your types will be updated as well. Alternatively, if you run `amplify codegen` alone it will perform both of these actions. - -## Introspection Schema outside of an initialized project - -If you would like to generate statements and types without initializing an amplify project, you can do so by providing your introspection schema named `schema.json` in your project directory and adding codegen from the same directory. To download your introspection schema from an AppSync api, in the AppSync console go to the schema editor and under "Export schema" choose `schema.json`. - -```bash -amplify add codegen -``` - -Once codegen has been added you can update your introspection schema, then generate statements and types again without re-entering your project information. - -```bash -amplify codegen -``` - -You can update your project and codegen configuration if required. - -```bash -amplify configure codegen -amplify codegen -``` - -When generating types, codegen uses GraphQL statements as input. It will generate only the types that are being used in the GraphQL statements. - -## Codegen commands - -### amplify add codegen - -```bash -amplify add codegen -``` - -The `amplify add codegen` allows you to add AppSync API created using the AWS console. If you have your API is in a different region then that of your current region, the command asks you to choose the region. If you are adding codegen outside of an initialized amplify project, provide your introspection schema named `schema.json` in the same directory that you make the add codegen call from. **Note**: If you use the --apiId flag to add an externally created AppSync API, such as one created in the AWS console, you will not be able to manage this API from the Amplify CLI with commands such as amplify api update when performing schema updates. You cannot add an external AppSync API when outside of an initialized project. - -### amplify configure codegen - -```bash -amplify configure codegen -``` - -The `amplify configure codegen` command allows you to update the codegen configuration after it is added to your project. When outside of an initialized project, you can use this to update your project configuration as well as the codegen configuration. - -### amplify codegen statements - -```bash -amplify codegen statements [--nodownload] [--maxDepth ] -``` - -The `amplify codegen statements` command generates GraphQL statements(queries, mutation and subscription) based on your GraphQL schema. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. - -### amplify codegen types - -```bash -amplify codegen types -``` - -The `amplify codegen types [--nodownload]` command generates GraphQL `types` for Flow and typescript and Swift class in an iOS project. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. - -### amplify codegen - -```bash -amplify codegen [--maxDepth ] -``` - -The `amplify codegen [--nodownload]` generates GraphQL `statements` and `types`. This command downloads introspection schema every time it is run but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. If you are running codegen outside of an initialized amplify project, the introspection schema named `schema.json` must be in the same directory that you run amplify codegen from. This command will not download the introspection schema when outside of an amplify project - it will only use the introspection schema provided. - -## Statement depth - -In the below schema there are connections between `Comment` -> `Post` -> `Blog` -> `Post` -> `Comments`. When generating statements codegen has a default limit of 2 for depth traversal. But if you need to go deeper than 2 levels you can change the `maxDepth` parameter either when setting up your codegen or by passing `--maxDepth` parameter to `codegen` - -```graphql -type Blog @model { - id: ID! - name: String! - posts: [Post] @hasMany -} -type Post @model { - id: ID! - title: String! - blog: Blog @belongsTo - comments: [Comment] @hasMany -} -type Comment @model { - id: ID! - content: String - post: Post @belongsTo -} -``` - -```graphql -query GetComment($id: ID!) { - getComment(id: $id) { - # depth level 1 - id - content - post { - # depth level 2 - id - title - blog { - # depth level 3 - id - name - posts { - # depth level 4 - items { - # depth level 5 - id - title - } - nextToken - } - } - comments { - # depth level 3 - items { - # depth level 4 - id - content - post { - # depth level 5 - id - title - } - } - nextToken - } - } - } -} -``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx deleted file mode 100644 index a193e7eb2d3..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx +++ /dev/null @@ -1,1014 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Connect API to existing MySQL or PostgreSQL database', - description: 'Learn how to connect your API to an existing MySQL or PostgreSQL database.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/connect-api-to-existing-database/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -The following content requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). - - - - -In this section, you'll learn how to: - -- Connect Amplify GraphQL API to an existing MySQL or PostgreSQL database -- Execute SQL statements with custom GraphQL queries and mutations using the new `@sql` directive -- Generate create, read, update, and delete API operations based on your SQL database schema - -## Connect your API with an existing MySQL or PostgreSQL database - - - - -Pre-requisites: - -- Have an existing [MySQL database](https://aws.amazon.com/getting-started/hands-on/create-mysql-db/) or [PostgreSQL database](https://aws.amazon.com/getting-started/hands-on/create-connect-postgresql-db/) deployed -- The [AWS CDK CLI is installed](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install) -- Have an [AWS CDK application initialized](https://docs.aws.amazon.com/cdk/v2/guide/hello_world.html) - - - -This feature is not yet available in the Asia Pacific (Hong Kong, ap-east-1) or Europe (Milan, eu-south-1) regions. - - - - - -First, place your database connection information (hostname, username, password, port, and database name) into Systems Manager, each as a `SecureString`. - -Go to the Systems Manager console, navigate to Parameter Store, and click "Create Parameter". Create five different SecureStrings: one each for the hostname of your database server, the username and password to connect, the database port, and the database name. - -Your Systems Manager configuration should look something like this: - -![A screenshot of an AWS Systems Manager console page titled "Parameter Store". The page shows a list of parameters with names like "/amplify-cdk-app/username", "/amplify-cdk-app/password", and "/amplify-cdk-app/hostname" indicating database connection details. Each parameter is of Tier "Standard" and typed as "SecureString". The last modified date is displayed for each parameter.](/images/storing-db-creds-in-ssm.png) - - -First, place your database connection information (hostname, username, password, port, and database name) into Secrets Manager. - -Go to the Secrets Manager console, navigate to Secrets, and click "Store a new secret". You may create the secret in any manner as long as there are `username` and `password` keys defined. - -![A screenshot of a page in the Secrets Manager console titled "Secret value info". The screenshot shows an example of a secret's keys and values in a table including "username", "password", "engine", "host", "port", and "dbClusterIdentifier".](/images/storing-db-creds-in-secrets-manager.png) - -Optionally, you can decide whether to encrypt the secret using the KMS key that Secrets Manager creates or a customer managed KMS key that you create. - -You can also configure a rotation schedule and create a Lambda function or choose an existing Lambda function from your account to rotate the database credentials automatically. - - - -Install the following package to add the Amplify GraphQL API construct to your dependencies: - -```sh -npm install @aws-amplify/graphql-api-construct -``` - -Create a new `schema.sql.graphql` file within your CDK app’s `lib/` folder that includes the APIs you want to expose. Define your GraphQL object types, queries, and mutations to match the APIs you wish to expose. For example, define object types for database tables, queries to fetch data from those tables, and mutations to modify those tables. - -```graphql -type Post { - id: Int! - title: String! - content: String! - published: Boolean - publishedDate: AWSDate @refersTo(name: "published_date") -} - -type Query { - searchPosts(contains: String!): [Post] - @sql( - statement: "SELECT * FROM posts WHERE title LIKE CONCAT('%', :contains, '%');" - ) - @auth(rules: [{ allow: public }]) -} - -type Mutation { - createPost(title: String! content: String!): AWSJSON - @sql(statement: "INSERT INTO posts (title, content) VALUES (:title, :content);") - @auth(rules: [{ allow: public }]) -} -``` - -You can use the `:variable` notation to reference input variables from the query request. - - -Amplify’s GraphQL API operates on a deny-by-default basis. The `{ allow: public }` auth rule in the example schema above designates that anyone using an API Key is authorized to execute the query. - -Review [Authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) to limit access to these queries and mutations based on API Key, Amazon Cognito User Pool, OpenID Connect, AWS Identity and Access Management (IAM), or a custom Lambda function. - - - -Next, open the main stack file in your CDK project (usually located in `lib/-stack.ts`). Import the necessary constructs at the top of the file: - -```ts -import { - AmplifyGraphqlApi, - AmplifyGraphqlDefinition -} from '@aws-amplify/graphql-api-construct'; - -import path from 'path'; -``` - -In the main stack class, add the following code to define a new GraphQL API. Replace `stack` with the name of your stack instance (often referenced via `this`): - - - -```ts -new AmplifyGraphqlApi(stack, 'SqlBoundApi', { - apiName: 'MySqlBoundApi', - definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( - [path.join(__dirname, 'schema.sql.graphql')], - { - name: 'MySQLSchemaDefinition', - dbType: 'MYSQL', - vpcConfiguration: { - vpcId: 'vpc-123456', - securityGroupIds: ['sg-123', 'sg-456'], - subnetAvailabilityZoneConfig: [ - { subnetId: 'sn-123456', availabilityZone: 'us-east-1a' }, - { subnetId: 'sn-987654', availabilityZone: 'us-east-1b' } - ] - }, - dbConnectionConfig: { - hostnameSsmPath: - '/path/to/ssm/SecureString/containing/value/of/hostname', - portSsmPath: '/path/to/ssm/SecureString/containing/value/of/port', - usernameSsmPath: - '/path/to/ssm/SecureString/containing/value/of/username', - passwordSsmPath: - '/path/to/ssm/SecureString/containing/value/of/password', - databaseNameSsmPath: - '/path/to/ssm/SecureString/containing/value/of/databaseName' - } - } - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); -``` - - - - -```ts -new AmplifyGraphqlApi(this, 'SqlBoundApi', { - apiName: 'MySqlBoundApi', - definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( - [path.join(__dirname, 'schema.sql.graphql')], - { - name: 'MySQLSchemaDefinition', - dbType: 'MYSQL', - vpcConfiguration: { - vpcId: 'vpc-123456', - securityGroupIds: ['sg-123', 'sg-456'], - subnetAvailabilityZoneConfig: [ - { subnetId: 'sn-123456', availabilityZone: 'us-east-1a' }, - { subnetId: 'sn-987654', availabilityZone: 'us-east-1b' }, - ], - }, - dbConnectionConfig: { - databaseName: 'database', - port: 3306, - hostname: 'database-1-instance-1.id.region.rds.amazonaws.com', - secretArn: - 'arn:aws:secretsmanager:Region1:123456789012:secret:MySecret-a1b2c3', - }, - } - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30), - }, - }, -}); -``` - - - - -The API will have an API key enabled for authorization. - -Before deploying, make sure to: - -- Set a value for `name`. This will be used to name the AppSync DataSource itself, plus any associated resources like resolver Lambdas. This name must be unique across all schema definitions in a GraphQL API. - -- Change the `dbType` to match your database engine. This is the type of the SQL database used to process model operations for this definition. Supported engines are `"MYSQL"` or `"POSTGRES"`. - -- Update the SSM parameter paths within `dbConnectionConfig` to point to those existing in your AWS account. These are the parameters the SQL Lambda will use to connect to the database. - -- If your database instance exists within a VPC, update the `vpcConfiguration` properties - `vpcId`, `securityGroupIds`, and `subnetAvailabilityZoneConfig` with your vpc details. This is the configuration of the VPC into which to install the SQL Lambda. - - - -If your database exists within a VPC, the RDS instance must be configured to be `Publicly accessible`. This does not mean the instance needs to accessible from the internet. - -The target security group(s) must have two inbound rules set up: - -- A rule allowing traffic on port 443 from the security group. - -- An inbound rule allowing traffic on the database port from the security group. (Default: 3306 for MySQL. 5432 for PostgreSQL.) - -In addition, the target security group(s) must have two outbound rules set up: - -- An outbound rule allowing traffic on port 443 to the security group. - -- An outbound rule allowing traffic on the database port to the security group. (Default: 3306 for MySQL. 5432 for PostgreSQL.) - - - **NOTE:** Make sure to limit the type of inbound traffic your security group - allows according to your security needs and/or use cases. For information on - security group rules, please refer to the [Amazon EC2 Security Group Rules reference](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html?icmpid=docs_ec2_console). - - - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - - - -Consider adding an RDS Proxy in front of the cluster to manage database connections. - -When using Amplify GraphQL API with a relational database like Amazon RDS, each query from your application needs to open a separate connection to the database. - -If there are a large number of queries occurring concurrently, it can exceed the connection limit on the database and result in errors like "Too many connections". To avoid this, Amplify can use an RDS Proxy when connecting your GraphQL API to a database. - -The RDS Proxy acts as an intermediary sitting in front of your database. Instead of each application query opening a direct connection to the database, they will connect through the Proxy. The Proxy helps manage and pool these connections to avoid overwhelming your database cluster. This improves the availability of your API, allowing more queries to execute concurrently without hitting connection limits. - -However, there is a tradeoff of increased latency - queries may take slightly longer as they wait for an available connection from the Proxy pool. There are also additional costs associated with using RDS Proxy. Please refer to the [pricing page for RDS Proxy](https://aws.amazon.com/rds/proxy/pricing/) to learn more. - - - -## Create custom queries and mutations - -Amplify GraphQL API for SQL databases introduces the `@sql` directive, which allows you to define SQL statements in custom GraphQL queries and mutations. This provides more flexibility when the default, auto-generated GraphQL queries and mutations are not sufficient. - -There are two ways to specify the SQL statement - inline or by referencing a `.sql` file. - -### Inline SQL Statement - -For getting started, you can embed the SQL statement directly in the schema using the `statement` argument. - -The SQL statement can use parameters in the format `:variable`, which will be bound to the input variables passed when executing a custom GraphQL query or mutation. - -In the example below, a SQL statement is defined, accepting a `searchTerm` input variable. - -```graphql -type Query { - searchPosts(searchTerm: String): [Post] - @sql(statement: "SELECT * FROM posts WHERE title LIKE :searchTerm;") - @auth(rules: [{ allow: public }]) -} -``` - -{/* TODO: Add a NOTE: about proxy/connection pinning here. */} - -### SQL File Reference - -For longer, more complex SQL queries, you can specify the statement in separate `.sql` files rather than inline. Referencing a file keeps your schema clean and allows reuse of SQL statements across fields. - - - - -First, update your GraphQL schema file to reference a SQL file name without the `.sql` extension: - -```graphql -type Query { - getPublishedPosts(start: AWSDate, end: AWSDate): [Post] - @sql(reference: "getPublishedPostsByDateRange") - @auth(rules: [{ allow: public }]) -} -``` - -Next, create a new `lib/sql-statements` folder and add any custom queries or mutations as SQL files. For example, you could create different `.sql` files for different queries: - -```sql --- lib/sql-statements/getPublishedPostsByDateRange.sql -SELECT p.id, p.title, p.content, p.published_date -FROM posts p -WHERE p.published = 1 - AND p.published_date > :startDate - AND p.published_date < :endDate -ORDER BY p.published_date DESC -LIMIT 10 -``` - -```sql --- lib/sql-statements/getPostById.sql -SELECT * FROM posts WHERE id = :id; -``` - -Then, you can import the `SQLLambdaModelDataSourceStrategyFactory` which helps define the datasource strategy from the custom `.sql` files you've created. - -```js -import { SQLLambdaModelDataSourceStrategyFactory } from '@aws-amplify/graphql-api-construct'; -import path from 'path'; -import fs from 'fs'; -``` - -In your `lib/-stack.ts` file, read from the `sql-statements/` folder and add them as custom SQL statements to your Amplify GraphQL API: - -```js - -// Define custom SQL statements folder path -const sqlStatementsPath = path.join(__dirname, 'sql-statements'); - -// Use the Factory to define the SQL data source strategy -const sqlStrategy = SQLLambdaModelDataSourceStrategyFactory.fromCustomSqlFiles( - // File paths to all SQL statements - fs - .readdirSync(sqlStatementsPath) - .map((file) => path.join(sqlStatementsPath, file)), - // Move your connection information and VPC config into here - { - dbType: 'MYSQL', - name: 'MySQLSchemaDefinition', - dbConnectionConfig: { - //... - }, - vpcConfiguration: { - //... - } - } -); - - -const amplifyApi = new AmplifyGraphqlApi(this, 'SqlBoundApi', { - definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( - [path.join(__dirname, 'schema.sql.graphql')], - sqlStrategy - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); -``` - -The SQL statements defined in the `.sql` files will be executed as if they were defined inline in the schema. The same rules apply in terms of using parameters, ensuring valid SQL syntax, and matching the return type to row data. - - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - - -### Custom Query - -For reference, you define a GraphQL query by adding a new field under a `type Query` object: - -```graphql -type Query { - searchPostsByTitle(title: String): [Post] - @sql( - statement: "SELECT * FROM posts WHERE title LIKE CONCAT('%', :title, '%');" - ) - @auth(rules: [{ allow: public }]) -} -``` - -### Custom Mutation - -For reference, you define a GraphQL mutation by adding a new field under a `type Mutation` object: - -```graphql -type Mutation { - publishPostById(id: ID!): AWSJSON - @sql(statement: "UPDATE posts SET published = :published WHERE id = :id;") - @auth(rules: [{ allow: public }]) -} -``` - -### Returning row data from custom mutations - -SQL statements such as `INSERT`, `UPDATE` and `DELETE` return the number of rows affected. - -If you want to return the result of the SQL statement, you can use `AWSJSON` as the return type. - -```graphql -type Mutation { - publishPosts: AWSJSON @sql(statement: "UPDATE posts SET published = 1;") - @auth(rules: [{ allow: public }]) -} -``` - -This will return a JSON response similar to this: - -```json -{ - "data": { - "publishPosts": "{\"fieldCount\":0,\"affectedRows\":7,\"insertId\":0,\"info\":\"Rows matched: 7 Changed: 7 Warnings: 0\",\"serverStatus\":34,\"warningStatus\":0,\"changedRows\":7}" - } -} -``` - -However, you might want to return the actual row data instead. - - - - -In MySQL, you can create and call a stored procedure that performs both an UPDATE statement and SELECT query to return a single post. - -Create a stored procedure by running the following SQL statement in your MySQL database: - -```sql -CREATE PROCEDURE publish_post (IN postId VARCHAR(255)) - -BEGIN -UPDATE posts SET published = 1 WHERE id = postId; - -SELECT * FROM posts WHERE id = postId LIMIT 1; -END -``` - -Call the stored procedure from the custom mutation: - -```graphql -type Mutation { - publishPostById(id: String!): [Post] - @sql(statement: "CALL publish_post(:id);") - @auth(rules: [{ allow: public }]) -} -``` - - - - -In PostgreSQL, you can add a `RETURNING` clause to an `INSERT`, `UPDATE`, or `DELETE` statement and get the actual modified row data. - -Example: - -```graphql -type Mutation { - publishPostById(id: String!): [Post] - @sql(statement: "UPDATE posts SET price = :id RETURNING *;") - @auth(rules: [{ allow: public }]) -} -``` - - - - - - The return type for custom queries and mutations expecting row data must - be an array of the corresponding model. - - - -## Apply authorization rules - -### Model level authorization rules - -The `@auth` directive can be used to restrict access to data and operations by specifying authorization rules. It allows granular access control over the GraphQL API based on the user's identity and attributes. You can for example, limit a query or mutation to only logged-in users via an `@auth(rules: [{ allow: private }])` rule or limit access to only users of the "Admin" group via an `@auth(rules: [{ allow: groups, groups: ["Admin"] }])` rule. - -All model-level authorization rules are supported for Amplify GraphQL schemas generated from MySQL and PostgreSQL databases. - -In the example below, public users authorized via API Key are granted unrestricted access to all posts. - -Add the following auth rule to the `Post` model within the `schema.sql.graphql` file: - -```graphql -type Post @model @refersTo(name: "posts") @auth(rules: [{ allow: public }]) { - id: String! @primaryKey - title: String! - content: String! -} -``` - -For more information on each rule please refer to our documentation on [Authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules). - -### Field-level authorization rules - -Field level auth rules are also supported for Amplify GraphQL schemas generated from MySQL and PostgreSQL databases. - -In the example below, unauthenticated users can read post data but only the owner of the post can perform operations on the `published` field. - -```graphql -type Post - @model - @refersTo(name: "posts") - @auth(rules: [ - { allow: public, operations: [read] }, - { allow: owner } - ]) { - id: String! @primaryKey - title: String! - content: String! - published: Boolean - // highlight-start - @auth(rules: [{ allow: owner }]) - // highlight-end -} -``` - -For more information on field-level auth rules please refer to our documentation on [Field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules). - -## Deploy your API - - - -To deploy the API, you can use the `cdk deploy` command: - -```sh -cdk deploy -``` - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - -Now the API has been deployed and you can start using it! - -You can start querying from the AWS AppSync console or integrate it into your application using the AWS Amplify libraries! - -## Auto-generate CRUDL operations for existing tables - -You can generate common CRUDL operations for your database tables based on your database schema. This saves time from hand-authoring the GraphQL types, queries, and mutations and SQL statements for common CRUDL use cases. After you generate the operations, you can annotate the `@model` types with authorization rules. - -Create a `Ingredients` table in your database: - -```sql -CREATE TABLE Ingredients ( - id varchar(255) NOT NULL PRIMARY KEY, - name varchar(255) NOT NULL, - unit_of_measurement varchar(255) NOT NULL, - price decimal(10, 2) NOT NULL, - supplier_id int, -); -``` - -### Step 1 - Export database schema as CSV - -Execute the following SQL statement on your database using a MySQL, PostgreSQL Client, or CLI tool similar to `psql` and export the output to a CSV file: - - - You must include column headers when exporting the database schema output to a CSV file. - - -Replace `` with the name of your database/schema. - - - -```sql -SELECT DISTINCT - INFORMATION_SCHEMA.COLUMNS.TABLE_NAME, - INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME, - INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT, - INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION, - INFORMATION_SCHEMA.COLUMNS.DATA_TYPE, - INFORMATION_SCHEMA.COLUMNS.COLUMN_TYPE, - INFORMATION_SCHEMA.COLUMNS.IS_NULLABLE, - INFORMATION_SCHEMA.COLUMNS.CHARACTER_MAXIMUM_LENGTH, - INFORMATION_SCHEMA.STATISTICS.INDEX_NAME, - INFORMATION_SCHEMA.STATISTICS.NON_UNIQUE, - INFORMATION_SCHEMA.STATISTICS.SEQ_IN_INDEX, - INFORMATION_SCHEMA.STATISTICS.NULLABLE -FROM INFORMATION_SCHEMA.COLUMNS -LEFT JOIN INFORMATION_SCHEMA.STATISTICS ON INFORMATION_SCHEMA.COLUMNS.TABLE_NAME=INFORMATION_SCHEMA.STATISTICS.TABLE_NAME AND INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME=INFORMATION_SCHEMA.STATISTICS.COLUMN_NAME -WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA = ''; --- Replace database name here ^^^^^^^^^^^^^^^ -``` - -Your exported SQL schema should look something like this: - -```csv -TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,ORDINAL_POSITION,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,CHARACTER_MAXIMUM_LENGTH,INDEX_NAME,NON_UNIQUE,SEQ_IN_INDEX,NULLABLE -Ingredients,id,,1,int,int,NO,,PRIMARY,0,1,"" -Ingredients,name,,2,varchar,varchar(100),NO,100,,,, -Ingredients,unit_of_measurement,,3,varchar,varchar(50),NO,50,,,, -Ingredients,price,,4,decimal,"decimal(10,2)",NO,,,,, -Ingredients,supplier_id,,6,int,int,YES,,,,, -Meals,id,,1,int,int,NO,,PRIMARY,0,1,"" -``` - - - -```sql -SELECT DISTINCT - INFORMATION_SCHEMA.COLUMNS.table_name, - enum_name,enum_values,column_name,column_default,ordinal_position,data_type,udt_name,is_nullable,character_maximum_length,indexname,constraint_type, - REPLACE(SUBSTRING(indexdef from '\((.*)\)'), '"', '') as index_columns -FROM INFORMATION_SCHEMA.COLUMNS -LEFT JOIN pg_indexes -ON - INFORMATION_SCHEMA.COLUMNS.table_name = pg_indexes.tablename - AND INFORMATION_SCHEMA.COLUMNS.column_name = ANY(STRING_TO_ARRAY(REPLACE(SUBSTRING(indexdef from '\((.*)\)'), '"', ''), ', ')) - LEFT JOIN ( - SELECT - t.typname AS enum_name, - ARRAY_AGG(e.enumlabel) as enum_values - FROM pg_type t JOIN - pg_enum e ON t.oid = e.enumtypid JOIN - pg_catalog.pg_namespace n ON n.oid = t.typnamespace - WHERE n.nspname = 'public' - GROUP BY enum_name - ) enums - ON enums.enum_name = INFORMATION_SCHEMA.COLUMNS.udt_name - LEFT JOIN information_schema.table_constraints - ON INFORMATION_SCHEMA.table_constraints.constraint_name = indexname - AND INFORMATION_SCHEMA.COLUMNS.table_name = INFORMATION_SCHEMA.table_constraints.table_name -WHERE INFORMATION_SCHEMA.COLUMNS.table_schema = 'public' - AND INFORMATION_SCHEMA.COLUMNS.TABLE_CATALOG = ''; --- Replace database name here ^^^^^^^^^^^^^^^ -``` - -Your exported SQL schema should look something like this: - -```csv -"table_name","enum_name","enum_values","column_name","column_default","ordinal_position","data_type","udt_name","is_nullable","character_maximum_length","indexname","constraint_type","index_columns" -"Ingredients","","","id","","1","bigint","int8","NO","","Ingredients_pkey","PRIMARY KEY","id" -"Ingredients","","","name","","2","text","text","NO","","","","" -"Ingredients","","","unit_of_measurement","","3","text","text","NO","","","","" -"Ingredients","","","price","","4","text","text","NO","","","","" -"Ingredients","","","supplier_id","","5","bigint","int8","NO","","","","" -``` - - - - -### Step 2 - Generate GraphQL schema from database schema - -Next, generate an Amplify GraphQL API schema by running the following command, replacing the `--engine-type` value with your database engine of `mysql` or `postgres`, and the `--sql-schema` value with the path to the CSV file created in the previous step: - -```bash -npx @aws-amplify/cli api generate-schema --engine-type mysql --sql-schema schema.csv --out schema.sql.graphql -``` - - -Next, update the first argument of `AmplifyGraphqlDefinition.fromFilesAndStrategy` to include the `schema.sql.graphql` file generated in the previous step: - -```ts -new AmplifyGraphqlApi(stack, 'SqlBoundApi', { - apiName: 'MySqlBoundApi', - definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( - [path.join(__dirname, 'schema.sql.graphql')], // file path - { - // ...strategy options - } - ) -}); -``` - -### Step 3 - Apply authorization rules for your generated GraphQL API - -Open your **schema.sql.graphql** file, you should see something like this. The auto-generated schema automatically changes the casing to better match common GraphQL conventions. Amplify's GraphQL API's operate on a **deny-by-default principle**, this means you must explicitly add `@auth` authorization rules in order to make this API accessible to your users. Currently only model-level authorization is supported. - -```graphql -input AMPLIFY { - engine: String = "mysql" -} - - -type Ingredient @refersTo(name: "Ingredients") @model { - id: Int! @refersTo(name: "ingredient_id") @primaryKey - name: String! - unitOfMeasurement: String! @refersTo(name: "unit_of_measurement") - price: Float! - supplierId: Int @refersTo(name: "supplier_id") -} -``` - -In our example, we'll add a public authorization rule, meaning anyone with an API key can create, read, update, and delete records from the database. Review [Customize authorization rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) to see the full scope of model-level authorization capabilities. - -```diff -input AMPLIFY { - engine: String = "mysql" -} - - -- type Ingredient @refersTo(name: "Ingredients") @model { -+ type Ingredient -+ @refersTo(name: "Ingredients") -+ @model -+ @auth(rules: [{ allow: public }]) { - id: Int! @refersTo(name: "ingredient_id") @primaryKey - name: String! - unitOfMeasurement: String! @refersTo(name: "unit_of_measurement") - price: Float! - supplierId: Int @refersTo(name: "supplier_id") -} -``` - -Finally, remember to deploy your API to the cloud: - - - -To deploy the API, you can use the `cdk deploy` command: - -```sh -cdk deploy -``` - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - -Now the API has been deployed and you can start using it! - -### Rename & map models to tables - -To rename models and fields, you can use the `@refersTo` directive to map the models in the GraphQL schema to the corresponding table or field by name. - -By default, the Amplify CLI singularizes each model name using PascalCase and field names that are either snake_case or kebab-case will be converted to camelCase. - -In the example below, the Post model in the GraphQL schema is now mapped to the posts table in the database schema. Also, the `isPublished` is now mapped to the `published` column on the posts table. - -```graphql -type Post @refersTo(name: "posts") @model { - id: String! @primaryKey - title: String! - content: String! - isPublished: Boolean @refersTo(name: "published") - publishedDate: AWSDate @refersTo(name: "published_date") -} -``` - -### Create relationships between models - -You can use the `@hasOne`, `@hasMany`, and `@belongsTo` relational directives to create relationships between models. The field named in the `references` parameter of the relational directives must exist on the child model. - - - -Relationships that query across DynamoDB and SQL data sources are currently not supported. However, you can create relationships across SQL data sources. - - - -Assume that you have `users`, `blogs`, and `posts` tables in your database schema. The following examples demonstrate how you might create different types of relationships between them. Use them as references for creating relationships between the models in your own schema. - -#### Has One relationship - -Create a one-directional one-to-one relationship between two models using the `@hasOne` directive. - -In the example below, a User has a single Blog. - -```graphql -type User - @refersTo(name: "users") - @model - @auth(rules: [{ allow: owner }, { allow: groups, groups: ["Admin"] }]) { - id: String! @primaryKey - name: String! - owner: String - blog: Blog @hasOne(references: ["userId"]) -} -``` - -#### Has Many relationship - -Create a one-directional one-to-many relationship between two models using the `@hasMany` directive. - -In the example below, a Blog has many Posts. - -```graphql -type Blog @model { - id: String! @primaryKey - title: String! - posts: [Post] @hasMany(references: ["blogId"]) -} - -type Post @model { - id: String! @primaryKey - title: String! - content: String! - blogId: String! @refersTo(name: "blog_id") -} -``` - -#### Belongs To relationship - -Make a "has one" or "has many" relationship bi-directional with the `@belongsTo` directive. - -In the example below, a Post belongs to a Blog. - -```graphql -type Post @model { - id: String! @primaryKey - title: String! - content: String! - blogId: String! @refersTo(name: "blog_id") - blog: Blog @belongsTo(references: ["blogId"]) -} -``` - -### Apply iterative changes from the database definition - - - - 1. Make any adjustments to your SQL statements such as: - -```sql -CREATE TABLE posts ( - id varchar(255) NOT NULL PRIMARY KEY, - title varchar(255) NOT NULL, - content varchar(255) NOT NULL, - published tinyint(1) DEFAULT 0 NOT NULL - published_date date NULL -); -``` - -2. Regenerate the database schema as a CSV file by following the instructions in [Generate GraphQL schema from database schema](#step-2---generate-graphql-schema-from-database-schema). - -3. Generate an updated schema by running the following command, replacing the `--engine-type` value with your database engine of `mysql` or `postgres`, and the `--sql-schema` value with the path to the CSV file created in the previous step: - -```sh -npx @aws-amplify/cli api generate-schema --engine-type mysql --sql-schema schema.csv --out schema.sql.graphql -``` - -4. Deploy your changes to the cloud: - -```sh -cdk deploy -``` - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/[platform]/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - -## How does it work? - -The Amplify uses AWS Lambda functions to enable features like querying data from your database. To work properly, these Lambda functions need access to common logic and dependencies. - -Amplify provides this shared code in the form of Lambda Layers. You can think of Lambda Layers as a package of reusable runtime code that Lambda functions can reference. - -When you deploy an Amplify API, it will create two Lambda functions: - -### SQL Lambda - -This allows you to query and write data to your database from your API. - - - **NOTE:** If the database is in a VPC, this Lambda function will be deployed - in the same VPC as the database. The usage of Amazon Virtual Private Cloud - (VPC) or VPC peering, with AWS Lambda functions will incur additional charges - as explained, this comes with an additional cost as explained on the [Amazon - Elastic Compute Cloud (EC2) on-demand pricing - page](https://aws.amazon.com/ec2/pricing/on-demand/). - - -### Updater Lambda - -This automatically keeps the SQL Lambda up-to-date by managing its Lambda Layers. - -A Lambda layer that includes all the core SQL connection logic lives within the AWS Amplify service account but is executed within your AWS account, when invoked by the SQL Lambda. This allows the Amplify service team to own the ongoing maintenance and security enhancements of the SQL connection logic. - -This allows the Amplify team to maintain and enhance the SQL Layer without needing direct access to your Lambdas. If updates to the Layer are needed, the Updater Lambda will receive a signal from Amplify and automatically update the SQL Lambda with the latest Layer. - -### Mapping of SQL data types to GraphQL types when auto-generating GraphQL schema - - - -**Note:** MySQL does not support time zone offsets in date time or timestamp fields. Instead, we will convert these values to `datetime`, without the offset. - -Unlike MySQL, PostgreSQL does support date time or timestamp values with an offset. - - - -| SQL | GraphQL | -|--------------------|--------------| -| **String** | | -| char | String | -| varchar | String | -| tinytext | String | -| text | String | -| mediumtext | String | -| longtext | String | -| **Geometry** | | -| geometry | String | -| point | String | -| linestring | String | -| geometryCollection | String | -| **Numeric** | | -| smallint | Int | -| mediumint | Int | -| int | Int | -| integer | Int | -| bigint | Int | -| tinyint | Int | -| float | Float | -| double | Float | -| decimal | Float | -| dec | Float | -| numeric | Float | -| **Date and Time** | | -| date | AWSDate | -| datetime | AWSDateTime | -| timestamp | AWSDateTime | -| time | AWSTime | -| year | Int | -| **Binary** | | -| binary | String | -| varbinary | String | -| tinyblob | String | -| blob | String | -| mediumblob | String | -| longblob | String | -| **Others** | | -| bool | Boolean | -| boolean | Boolean | -| bit | Int | -| json | AWSJSON | -| enum | ENUM | - -### Supported Amplify directives for auto-generated GraphQL schema - -| Name | Supported | Model Level | Field Level | Description | -|--------------|:---------:|:-----------:|:-----------:|-------------| -| `@model` | ✅ | ✅ | ❌ | Creates a datasource and resolver for a table. | -| `@auth` | ✅ | ✅ | ✅ | Allows access to data based on a set of authorization methods and operations. | -| `@primaryKey`| ✅ | ❌ | ✅ | Sets a field to be the primary key. | -| `@index` | ✅ | ❌ | ✅ | Defines an index on a table. | -| `@default` | ✅ | ❌ | ✅ | Sets the default value for a column. | -| `@hasOne` | ✅ | ❌ | ✅ | Defines a one-way 1:1 relationship from a parent to child model. | -| `@hasMany` | ✅ | ❌ | ✅ | Defines a one-way 1:M relationship between two models, the reference being on the child. | -| `@belongsTo` | ✅ | ❌ | ✅ | Defines bi-directional relationship with the parent model. | -| `@manyToMany`| ❌ | ❌ | ❌ | Defines a M:N relationship between two models. | -| `@refersTo` | ✅ | ✅ | ✅ | Maps a model to a table, or a field to a column, by name. | -| `@mapsTo` | ❌ | ❌ | ❌ | Maps a model to a DynamoDB table. | -| `@sql` | ✅ | ❌ | ✅ | Accepts an inline SQL statement or reference to a .sql file to be executed to resolve a Custom Query or Mutation. | - - -## Troubleshooting - -### Debug Mode - -To return the actual SQL error instead of a generic error from GraphQL responses, an environment variable `DEBUG_MODE` can be set to `true` on the Amplify-generated SQL Lambda function. You can find this Lambda function in the AWS Lambda console with the naming convention of: `--SQLLambdaFunction`. - -## Next steps - -Our recommended next steps include using the GraphQL API to mutate and query data on app clients or how to customize the authorization rules for your custom queries and mutations. Some resources that will help with this work include: - -- [Create, update, and delete application data](/[platform]/build-a-backend/graphqlapi/mutate-data/) -- [Read application data](/[platform]/build-a-backend/graphqlapi/query-data/) -- [Customize Authorization Rules](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx deleted file mode 100644 index b17dcd5204e..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx +++ /dev/null @@ -1,262 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Incorporate machine learning', - description: 'Add AI/ML capabilities such as text recognition, image labeling, text-to-speech, and translation to your GraphQL API.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/connect-machine-learning-services/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -Amplify allows you to identify text on an image, identify labels on an image, translate text, and synthesize speech from text with the `@predictions` directive. - -> Note: The `@predictions` directive requires a S3 storage bucket configured via `amplify add storage` or set the `predictionsBucket` property when using CDK. - -## Identify text on an image - -To configure text recognition on an image use the `identifyText` action in the `@predictions` directive. - -```graphql -type Query { - recognizeTextFromImage: String @predictions(actions: [identifyText]) -} -``` - -In your GraphQL query, can pass in a S3 `key` for the image. At the moment, this directive works only with objects located within the `public/` folder of your S3 bucket. The `public/` prefix is automatically added to the `key` input. For instance, in the example below, `public/myimage.jpg` will be used as the input. - -```graphql -query RecognizeTextFromImage($input: RecognizeTextFromImageInput!) { - recognizeTextFromImage(input: { identifyText: { key: "myimage.jpg" } }) -} -``` - -## Identify labels on an image - -To configure label recognition on an image use the `identifyLabels` action in the `@predictions` directive. - -```graphql -type Query { - recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) -} -``` - -In your GraphQL query, you can pass in a S3 `key` for the image. At the moment, this directive works only with objects located within `public/` folder in your S3 bucket. The `public/` prefix is automatically added to the `key` input. For instance, in the example below, `public/myimage.jpg` will be used as the input. - -The query below will return a list of identified labels. Review [Detecting Labels](https://docs.aws.amazon.com/rekognition/latest/dg/labels.html) in the Amazon Rekognition documentation for the full list of supported labels. - -```graphql -query RecognizeLabelsFromImage($input: RecognizeLabelsFromImageInput!) { - recognizeLabelsFromImage(input: { identifyLabels: { key: "myimage.jpg" } }) -} -``` - -## Translate text - -To configure text translation use the `identifyLabels` action in the `@predictions` directive. - -```graphql -type Query { - translate: String @predictions(actions: [translateText]) -} -``` - -The query below will return the translated string. Populate the `sourceLanguage` and `targetLanguage` parameters with one of the [Supported Language Codes](https://docs.aws.amazon.com/translate/latest/dg/what-is.html#what-is-languages). Pass in the text to translate via the `text` parameter. - -```graphql -query TranslateText($input: TranslateTextInput!) { - translate( - input: { - translateText: { - sourceLanguage: "en" - targetLanguage: "de" - text: "Translate me" - } - } - ) -} -``` - -## Synthesize speech from text - -To configure Text-to-Speech synthesis use the `convertTextToSpeech` action in the `@predictions` directive. - -```graphql -type Query { - textToSpeech: String @predictions(actions: [convertTextToSpeech]) -} -``` - -The query below will return a presigned URL with the synthesized speech. Populate the `voiceID` parameter with one of the [Supported Voice IDs](https://docs.aws.amazon.com/polly/latest/dg/voicelist.htm). Pass in the text to synthesize via the `text` parameter. - -```graphql -query ConvertTextToSpeech($input: ConvertTextToSpeechInput!) { - textToSpeech( - input: { - convertTextToSpeech: { - voiceID: "Nicole" - text: "Hello from AWS Amplify!" - } - } - ) -} -``` - -## Combining Predictions actions - -You can also combine multiple Predictions actions together into a sequence. The following action sequences are supported: - -- `identifyText -> translateText -> convertTextToSpeech` -- `identifyLabels -> translateText -> convertTextToSpeech` -- `translateText -> convertTextToSpeech` - -In the example below, `speakTranslatedImageText` identifies text from an image, then translates it into another language, and finally converts the translated text to speech. - -```graphql -type Query { - speakTranslatedImageText: String - @predictions(actions: [identifyText, translateText, convertTextToSpeech]) -} -``` - -An example of that query will look like: - -```graphql -query SpeakTranslatedImageText($input: SpeakTranslatedImageTextInput!) { - speakTranslatedImageText( - input: { - identifyText: { key: "myimage.jpg" } - translateText: { sourceLanguage: "en", targetLanguage: "es" } - convertTextToSpeech: { voiceID: "Conchita" } - } - ) -} -``` - -A code example of this using the JS Library is shown below: - -```js -import React, { useState } from 'react'; -import { Amplify } from 'aws-amplify'; -import { uploadData, getUrl } from 'aws-amplify/storage'; -import { generateClient } from 'aws-amplify/api'; -import config from './amplifyconfiguration.json'; -import { speakTranslatedImageText } from './graphql/queries'; - -/* Configure Exports */ -Amplify.configure(config); - -const client = generateClient(); - -function SpeakTranslatedImage() { - const [src, setSrc] = useState(''); - const [img, setImg] = useState(''); - - function putS3Image(event) { - const file = event.target.files[0]; - uploadData({ - key: file.name, - data: file - }) - .result.then(async (result) => { - setSrc(await speakTranslatedImageTextOP(result.key)); - setImg((await getUrl({ key: result.key })).url.toString()); - }) - .catch((err) => console.log(err)); - } - - return ( -
-
-

Upload Image

- { - putS3Image(event); - }} - /> -
- {img && } - {src && ( -
- -
- )} -
-
- ); -} - -async function speakTranslatedImageTextOP(key) { - const inputObj = { - translateText: { - sourceLanguage: 'en', - targetLanguage: 'es' - }, - identifyText: { key }, - convertTextToSpeech: { voiceID: 'Conchita' } - }; - const response = await client.graphql({ - query: speakTranslatedImageText, - variables: { input: inputObj } - }); - return response.data.speakTranslatedImageText; -} - -function App() { - return ( -
-

Speak Translated Image

- -
- ); -} -export default App; -``` - -## How it works - -Definition of the `@predictions` directive: - -```graphql -directive @predictions(actions: [PredictionsActions!]!) on FIELD_DEFINITION -enum PredictionsActions { - identifyText # uses Amazon Rekognition to detect text - identifyLabels # uses Amazon Rekognition to detect labels - convertTextToSpeech # uses Amazon Polly in a lambda to output a presigned url to synthesized speech - translateText # uses Amazon Translate to translate text from source to target language -} -``` - -`@predictions` creates resources to communicate with Amazon Rekognition, Translate, and Polly. For each action the following is created: - -- IAM Policy for each service (e.g. Amazon Rekognition `detectText` Policy) -- An AppSync VTL function -- An AppSync DataSource - -Finally, a pipeline resolver is created for the query or field. The pipeline resolver is composed of AppSync functions which are defined by the action list provided in the directive. diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx deleted file mode 100644 index 3cc58b32c50..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx +++ /dev/null @@ -1,954 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Set up custom queries and mutations', - description: 'Add authorization rules to your GraphQL schema to control access to your data.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/custom-business-logic/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -Define your custom business logic in a Lambda function resolver, HTTP resolver, or an AppSync JavaScript or VTL resolver and expose them in a GraphQL query or mutation. Extend or override Amplify-generated GraphQL resolvers to optimize for your specific use cases. - -## Create a custom query or mutation - -While `@model` automatically generates dedicated "create", "read", "update", "delete", and "subscription" queries or mutations for you, there are some cases where you want to define a stand-alone query or mutation. - -1. Define your custom query or mutation - -```graphql -type Mutation { - myCustomMutation(args: String): String # your custom mutations here -} - -type Query { - myCustomQuery(args: String): String # your custom queries here -} -``` - -2. Use one of these resolver choices to handle the query or mutation request: - - - [Lambda function resolver](#lambda-function-resolver): use a custom Lambda function to handle query or mutation - - [HTTP resolver](#http-resolver): call an HTTP endpoint upon a query or mutation - - [AppSync JavaScript or VTL resolver](#appsync-javascript-or-vtl-resolver) (most advanced): use AppSync's JavaScript resolver or AppSync's VTL mapping templates to customize the query and mutation logic - -3. Secure your custom query or mutation with [field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) - - Note: Dynamic authorization rules are not supported on a custom query or mutation. - -## Lambda function resolver - -The `@function` directive allows you to quickly & easily configure a AWS Lambda resolvers with your GraphQL API. You can use any AWS Lambda functions that was created with the Amplify CLI, AWS CDK or reference an existing AWS Lambda function created with any other means. - - - - - -For example, use `amplify add function` to add a Lambda function called "echofunction" with the following handler: - -```js -exports.handler = async function (event, context) { - return event.arguments.msg; -}; -``` - -To connect an AWS Lambda resolver to the GraphQL API, add the `@function` directive to a field in your schema. - -```graphql -type Query { - echo(msg: String): String @function(name: "echofunction-${env}") -} -``` - -The Amplify CLI provides support for maintaining multiple environments. When you deploy a function via `amplify add function`, it will automatically add the environment suffix to your Lambda function name. For example, if you create a function named `echofunction` using `amplify add function` in the `dev` environment, the deployed function will be named `echofunction-dev`. The `@function` directive allows you to use `${env}` to reference the current Amplify CLI environment. - - - - - -First, create your Lambda function in CDK with your logic and set the `functionName` parameter. - -```ts -const echoLambda = new lambda.Function(this, 'EchoLambda', { - functionName: 'echofunction', // MAKE SURE THIS MATCHES THE @function's "name" PARAMETER - code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_18_X -}); - -const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); -``` - -To connect an AWS Lambda resolver to the GraphQL API, add the `@function` directive to a field in your GraphQL schema. - -```graphql -type Query { - echo(msg: String): String @function(name: "echofunction") -} -``` - -Optionally, if you don't want to hard-code the function name into the GraphQL schema, you can also set an arbitrary name in the GraphQL schema and then map a function within CDK to the function name. - -```ts -const coolLambdaFunction = new lambda.Function(this, 'MyCoolLambdaFunction', { - code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_18_X -}); - -const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - }, - functionNameMap: { - echofunction: coolLambdaFunction // Remap the function name to any function you define or reference within CDK. - } -}); -``` - - - - - -If you deployed your Lambda function without Amplify CLI then you must provide the full Lambda function name in the `name` parameter. If you deployed the same function with the name echofunction then you would have: - -```graphql -type Query { - echo(msg: String): String @function(name: "echofunction") -} -``` - - - - - -### Structure of the function event - -When writing Lambda functions that are connected via the `@function` directive, you can expect the following structure for the AWS Lambda `event` object. - -| Key | Description | -| --- | --- | -| typeName | The name of the parent object type of the field being resolved. | -| fieldName | The name of the field being resolved. | -| arguments | A map containing the arguments passed to the field being resolved. | -| identity | A map containing identity information for the request. Contains a nested key 'claims' that will contains the JWT claims if they exist. | -| source | When resolving a nested field in a query, the source contains parent value at runtime. For example when resolving `Post.comments`, the source will be the Post object. | -| request | The AppSync request object. Contains header information. | -| prev | When using pipeline resolvers, this contains the object returned by the previous function. You can return the previous value for auditing use cases. | - -Your function should follow the Lambda handler guidelines for your specific language. See the developer guides from the [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) documentation for your chosen language. If you choose to use structured types, your type should serialize the AWS Lambda event object outlined above. For example, if using Golang, you should define a struct with the above fields. - -### Calling functions in different regions - -By default, you expect the function to be in the same region as the Amplify project. If you need to call a function in a different or a specific region, you can provide the **region** argument. - -```graphql -type Query { - echo(msg: String): String @function(name: "echofunction", region: "us-east-1") -} -``` - -Calling functions in different AWS accounts is not supported via the `@function` directive but is supported by AWS AppSync. - -### Chaining functions - -You can chain together multiple `@function` resolvers such that they are invoked in series when your field's resolver is invoked. To create a pipeline resolver that calls to multiple AWS Lambda functions in series, use multiple `@function` directives on the field. Similarly, `@function` can be combined with field-level `@auth`. When combining these field directives, the ordering in the schema matches the ordering in the pipeline resolver. You can choose to have functions before and/or after field level authorization is applied. - -> **Note:** Be careful when using @auth directives on a field in a root type. @auth directives on field definitions use the source object to perform authorization logic and the source will be an empty object for fields on root types. Static group authorization should perform as expected. - -```graphql -type Mutation { - doSomeWork(msg: String): String - @function(name: "worker-function") - @function(name: "audit-function") -} -``` - -In the example above when you run a mutation that calls the `Mutation.doSomeWork` field, the **worker-function** will be invoked first then the **audit-function** will be invoked with an event that contains the results of the **worker-function** under the **event.prev.result** key. The **audit-function** would need to return **event.prev.result** if you want the result of **worker-function** to be returned for the field. - -### How it works - -Definition of `@function` directive: - -```graphql -directive @function(name: String!, region: String) on FIELD_DEFINITION -``` - -Under the hood, Amplify creates an `AppSync::FunctionConfiguration` for each unique instance of `@function` in a document and a pipeline resolver containing a pointer to a function for each `@function` on a given field. - -The `@function` directive generates these resources as necessary: - -1. An AWS IAM role that has permission to invoke the function as well as a trust policy with AWS AppSync. -2. An AWS AppSync data source that registers the new role and existing function with your AppSync API. -3. An AWS AppSync pipeline function that prepares the lambda event and invokes the new data source. -4. An AWS AppSync resolver that attaches to the GraphQL field and invokes the new pipeline functions. - -## HTTP resolver - -The `@http` directive allows you to quickly configure HTTP resolvers within your GraphQL API. - -To connect to an endpoint, add the @http directive to a field in your GraphQL schema. The directive allows you to define URL path parameters, and specify a query string and/or specify a request body. For example, given the definition of a Post type: - -```graphql -type Post { - id: ID! - title: String - description: String - views: Int -} - -type Query { - listPosts: [Post] @http(url: "https://www.example.com/posts") -} -``` - -Amplify generates the definition below that sends a request to the url when the listPosts query is used. - -```graphql -type Query { - listPosts: [Post] -} -``` - -### Request headers - -The `@http` directive generates resolvers that can handle XML and JSON responses. If an HTTP method is not defined, `GET` is used. You can specify a list of static headers to be passed with the HTTP requests to your backend in your directive definition. - -```graphql -type Query { - listPosts: [Post] - @http( - url: "https://www.example.com/posts" - headers: [{ key: "X-Header", value: "X-Header-Value" }] - ) -} -``` - -### Path parameters - -You can create dynamic paths by specifying parameters in the directive URL by using the special `:` notation. Your set of parameters can then be specified in the params input object of the query. Note that path parameters are not added to the request body or query string. You can define multiple parameters. - -```graphql -type Query { - getPost: Post @http(url: "https://www.example.com/posts/:id") -} -``` - -In the example above, the `:id` parameter will generate the appropriate query input as shown below: - -```graphql -type Query { - getPost(params: QueryGetPostParamsInput!): Post -} - -input QueryGetPostParamsInput { - id: String! -} -``` - -You can fetch a specific post by enclosing the id in the params input object. - -```graphql -query post { - getPost(params: { id: "POST_ID" }) { - id - title - } -} -``` - -This executes the following request: - -```console -GET /posts/POST_ID -Host: www.example.com -``` - -### Query String - -You can send a query string with your request by specifying variables for your query. The query string is supported with all request methods. - -Given the definition - -```graphql -type Query { - listPosts(sort: String!, from: String!, limit: Int!): Post - @http(url: "https://www.example.com/posts") -} -``` - -Amplify generates - -```graphql -type Query { - listPosts(query: QueryListPostsQueryInput!): [Post] -} - -input QueryListPostsQueryInput { - sort: String! - from: String! - limit: Int! -} -``` - -You can query for posts using the `query` input object - -```graphql -query posts { - listPosts(query: { sort: "DESC", from: "last-week", limit: 5 }) { - id - title - description - } -} -``` - -which sends the following request: - -```text -GET /posts?sort=DESC&from=last-week&limit=5 -Host: www.example.com -``` - -### Request Body - -The `@http` directive also allows you to specify the body of a request, which is used for `POST`, `PUT`, and `PATCH` requests. To create a new post, you can define the following. - -```graphql -type Mutation { - addPost(title: String!, description: String!, views: Int): Post - @http(method: POST, url: "https://www.example.com/post") -} -``` - -Amplify generates the `addPost` query field with the `query` and `body` input objects since this type of request also supports a query string. The generated resolver verifies that non-null arguments (e.g.: the `title` and `description`) are passed in at least one of the input objects; if not, an error is returned. - -```graphql -type Mutation { - addPost(query: QueryAddPostQueryInput, body: QueryAddPostBodyInput): Post -} - -input QueryAddPostQueryInput { - title: String - description: String - views: Int -} - -input QueryAddPostBodyInput { - title: String - description: String - views: Int -} -``` - -You can add a post by using the `body` input object: - -```graphql -mutation add { - addPost(body: { title: "new post", description: "fresh content" }) { - id - } -} -``` - -which will send - -```text -POST /post -Host: www.example.com -{ - title: "new post" - description: "fresh content" -} -``` - -### Reference Amplify environment name - -The `@http` directive allows you to use `${env}` to reference the current Amplify CLI environment. - -```graphql -type Query { - listPosts: Post @http(url: "https://www.example.com/${env}/posts") -} -``` - -which, in the `DEV` environment, will send - -```text -GET /DEV/posts -Host: www.example.com -``` - -**Combining the different components** - -You can use a combination of parameters, query, body, headers, and environments in your `@http` directive definition. - -Given the definition - -```graphql -type Post { - id: ID! - title: String - description: String - views: Int - comments: [Comment] -} - -type Comment { - id: ID! - content: String -} - -type Mutation { - updatePost( - title: String! - description: String! - views: Int - withComments: Boolean - ): Post - @http( - method: PUT - url: "https://www.example.com/${env}/posts/:id" - headers: [{ key: "X-Header", value: "X-Header-Value" }] - ) -} -``` - -you can update a post with - -```graphql -mutation update { - updatePost( - body: { title: "new title", description: "updated description", views: 100 } - params: { id: "EXISTING_ID" } - query: { withComments: true } - ) { - id - title - description - comments { - id - content - } - } -} -``` - -which, in the `DEV` environment, will send - -```text -PUT /DEV/posts/EXISTING_ID?withComments=true -Host: www.example.com -X-Header: X-Header-Value -{ - title: "new title" - description: "updated description" - views: 100 -} -``` - -### Reference existing field data - -In some cases, you may want to send a request based on existing field data. Take a scenario where you have a post and want to fetch comments associated with the post in a single query. Let's use the previous definition of `Post` and `Comment`. - -```graphql -type Post { - id: ID! - title: String - description: String - views: Int - comments: [Comment] -} - -type Comment { - id: ID! - content: String -} -``` - -A post can be fetched at `/posts/:id` and a post's comments at `/posts/:id/comments`. You can fetch the comments based on the post id with the following updated definition. `$ctx.source` is a map that contains the resolution of the parent field (`Post`) and gives access to `id`. - -```graphql -type Post { - id: ID! - title: String - description: String - views: Int - comments: [Comment] - @http(url: "https://www.example.com/posts/${ctx.source.id}/comments") -} - -type Comment { - id: ID! - content: String -} - -type Query { - getPost: Post @http(url: "https://www.example.com/posts/:id") -} -``` - -You can retrieve the comments of a specific post with the following query and selection set. - -```graphql -query post { - getPost(params: { id: "POST_ID" }) { - id - title - description - comments { - id - content - } - } -} -``` - -Assuming that `getPost` retrieves a post with the id `POST_ID`, the comments field is resolved by sending this request to the endpoint - -```text -GET /posts/POST_ID/comments -Host: www.example.com -``` - -Note that there is no check to ensure that the reference variable (here the post ID) exists. When using this technique, it is recommended to make sure the referenced field is non-null. - -### How it works - -Definition of `@http` directive: - -```graphql -directive @http( - method: HttpMethod - url: String! - headers: [HttpHeader] -) on FIELD_DEFINITION -enum HttpMethod { - PUT - POST - GET - DELETE - PATCH -} -input HttpHeader { - key: String - value: String -} -``` - -The `@http` transformer will create one HTTP datasource for each identified base URL. For example, if multiple HTTP resolvers are created that interact with the "https://www.example.com" endpoint, only a single datasource is created. Each directive generates one resolver. Depending on the definition, the appropriate `body`, `params`, and `query` input types are created. Note that `@http` transformer does not support calling other AWS services where Signature Version 4 signing process is required. - -## AppSync JavaScript or VTL resolver - -You can use AWS Cloud Development Kit (CDK) to define custom AppSync resolvers for your GraphQL API. `@auth` directives are not supported for custom queries or mutations that are connected to a JavaScript or VTL resolver. This is because you are replacing Amplify's auto-generated capabilities for that particular query or mutation with a custom-defined cloud resources. - -```bash -amplify add custom -``` - -```console -? How do you want to define this custom resource? -❯ AWS CDK -? Provide a name for your custom resource -❯ MyCustomResolvers -``` - -Next, install the AppSync dependencies for your custom resource: - -```bash -cd amplify/backend/custom/MyCustomResolvers -npm i @aws-cdk/aws-appsync@~1.172.0 -``` - -> **Note:** Installations using the '\~' character do not modify the package.json. To use '\~' for default npm configurations, make sure your package.json reflects the right dependency to avoid compatibility errors in CDK. - -Finally, add your custom resolvers into the `cdk-stack.ts` file. You can either add the JavaScript or VTL inline into your `cdk-stack.ts` file. - -#### Unit Resolver - - - - - -Review the [AppSync JavaScript resolver tutorial](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) for JavaScript resolver examples for different data sources. - -```ts -import * as cdk from 'aws-cdk-lib'; -import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; -import * as appsync from 'aws-cdk-lib/aws-appsync'; -import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; -import { Construct } from 'constructs'; - -const jsResolverTemplate = ` -export function request(ctx) { - return { - payload: null - } -} - -export function response(ctx) { - return ctx.arguments.message -} -` - -export class cdkStack extends cdk.Stack { - constructor( - scope: Construct, - id: string, - props?: cdk.StackProps, - amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps - ) { - super(scope, id, props); - /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ - new cdk.CfnParameter(this, 'env', { - type: 'String', - description: 'Current Amplify CLI env name' - }); - - // Access other Amplify Resources - const retVal: AmplifyDependentResourcesAttributes = - AmplifyHelpers.addResourceDependency( - this, - amplifyResourceProps.category, - amplifyResourceProps.resourceName, - [ - { - category: 'api', - resourceName: '' - } - ] - ); - - const resolver = new appsync.CfnResolver(this, 'CustomResolver', { - // apiId: retVal.api.new.GraphQLAPIIdOutput, - // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 - // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. - // Previously the ApiId is the variable Name which is wrong , it should be variable value as below - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - fieldName: 'echo', - typeName: 'Query', // Query | Mutation | Subscription - code: jsResolverTemplate, - dataSourceName: 'NONE_DS', // DataSource name - runtime: { - name: 'APPSYNC_JS', - runtimeVersion: '1.0.0' - } - }); - } -} -``` - - - - - -Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. - -```ts -import * as cdk from 'aws-cdk-lib'; -import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; -import * as appsync from 'aws-cdk-lib/aws-appsync'; -import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; -import { Construct } from 'constructs'; - -const requestVTL = ` - -`; -const responseVTL = ` - -`; - -export class cdkStack extends cdk.Stack { - constructor( - scope: Construct, - id: string, - props?: cdk.StackProps, - amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps - ) { - super(scope, id, props); - /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ - new cdk.CfnParameter(this, 'env', { - type: 'String', - description: 'Current Amplify CLI env name' - }); - - // Access other Amplify Resources - const retVal: AmplifyDependentResourcesAttributes = - AmplifyHelpers.addResourceDependency( - this, - amplifyResourceProps.category, - amplifyResourceProps.resourceName, - [ - { - category: 'api', - resourceName: '' - } - ] - ); - - const resolver = new appsync.CfnResolver(this, 'custom-resolver', { - // apiId: retVal.api.new.GraphQLAPIIdOutput, - // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 - // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. - // Previously the ApiId is the variable Name which is wrong , it should be variable value as below - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - fieldName: 'querySomething', - typeName: 'Query', // Query | Mutation | Subscription - requestMappingTemplate: requestVTL, - responseMappingTemplate: responseVTL, - dataSourceName: 'TodoTable' // DataSource name - }); - } -} -``` - -#### Pipeline Resolver - -```ts -import * as cdk from 'aws-cdk-lib'; -import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; -import * as appsync from 'aws-cdk-lib/aws-appsync'; -import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; -import { Construct } from 'constructs'; - -const beforeMappingVTL = ` - -`; -const afterMappingVTL = ` - -`; -const function1requestVTL = ` - -`; -const function1responseVTL = ` - -`; -const function2requestVTL = ` - -`; -const function2responseVTL = ` - -`; -export class cdkStack extends cdk.Stack { - constructor( - scope: Construct, - id: string, - props?: cdk.StackProps, - amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps - ) { - super(scope, id, props); - /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ - new cdk.CfnParameter(this, 'env', { - type: 'String', - description: 'Current Amplify CLI env name' - }); - - // Access other Amplify Resources - const retVal: AmplifyDependentResourcesAttributes = - AmplifyHelpers.addResourceDependency( - this, - amplifyResourceProps.category, - amplifyResourceProps.resourceName, - [ - { - category: 'api', - resourceName: '' - } - ] - ); - - const function1 = new appsync.CfnFunctionConfiguration(this, 'function1', { - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - dataSourceName: 'NONE_DS', // DataSource name - functionVersion: '2018-05-29', - name: 'function1', - requestMappingTemplate: function1requestVTL, - responseMappingTemplate: function1responseVTL - }); - - const function2 = new appsync.CfnFunctionConfiguration(this, 'function2', { - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - dataSourceName: 'TodoTable', // DataSource name - functionVersion: '2018-05-29', - name: 'function2', - requestMappingTemplate: function2requestVTL, - responseMappingTemplate: function2responseVTL - }); - - const resolver = new appsync.CfnResolver(this, 'pipeline-resolver', { - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - fieldName: 'querySomething', - typeName: 'Query', // Query | Mutation | Subscription - kind: 'PIPELINE', - pipelineConfig: { - functions: [function1.attrFunctionId, function2.attrFunctionId] - }, - requestMappingTemplate: beforeMappingVTL, - responseMappingTemplate: afterMappingVTL - }); - } -} -``` - -> **Note:** Users moving from ElasticSearch to OpenSearch will need to change the datasource name from `ElasticSearchDomain` to `OpenSearchDataSource` if the upgrade process changes the source name. For new @searchable models the datasource name will default to `OpenSearchDataSource`. - -You can alternatively define the VTL templates in another file such as `Query.querySomething.req.vtl` or `Query.querySomething.res.vtl` in `amplify/backend/custom/MyCustomResolvers/`. Then use the following code snippets to retrieve them: - -```ts -requestMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.req.vtl")).renderTemplate(), -responseMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.res.vtl")).renderTemplate(), -``` - -> **Note:** the `..` is added to the path because the path is always relative to the `build` folder of the custom resource. - - - - - -## Add authorization rules to custom queries and mutations - -Authorization rules can be applied with the `@auth` directive in the same way as field-level authorization rules. See [Field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules) for details. - -In the example below, `myCustomMutation` can only be executed by signed-in customers who are authenticated with IAM: - -```graphql -type Mutation { - myCustomMutation(args: String): String - @auth(rules: [{ allow: private, provider: iam }]) -} -``` - -> **Known limitation:** You can't combine the `@auth` directive with a custom query or mutation that is using a VTL resolver. - -## Override Amplify-generated resolvers - -Amplify generates [AWS AppSync pipeline resolver](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) for your queries and mutations. The resolvers are listed the following API resource's folder `amplify/backend/api//build/resolvers/`. - -To override an Amplify-generated resolver: - -1. Find the resolver file name you want to override under `build/resolvers` -2. Place a `.vtl` with the same file name the resource's `resolvers/` (not under `build/`) -3. Upon the next `amplify api gql-compile` or `amplify push` the Amplify-generated resolver file will be replaced with your overwritten resolver file - -```console -amplify/backend/api/ -├── build -│   ├── ... -│   ├── resolvers -│   │   ├── ... -│   │   ├── Query.searchTodos.req.vtl # Find resolver file name -│   │   └── ... -| ... -├── resolvers -│   └── Query.searchTodos.req.vtl # Place resolver overrides with the same file name here -``` - -The example above shows how the `Query.searchTodos.req.vtl` is overwritten with a custom resolver. Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. - -## Extend Amplify-generated resolvers - -Amplify generates [AWS AppSync pipeline resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) for your queries and mutations. You can "slot" in your custom business logic between Amplify-generated resolvers. You can find Amplify-generated resolvers under your API resources' `build/resolvers/` folder. The resolver functions file name determines its placement within the slot sequence. - -```console -File name convention: - [Query|Mutation|Subscription].[field name].[slot name].[slot placement].[req|res].vtl -Example: - Mutation.createTodo.postAuth.1.req.vtl -``` - -To extend an Amplify-generated resolver: - -1. Find the [resolver slot](#supported-resolver-slots) you want to add your custom business logic to -2. Place a `.vtl` file with the correct the file naming convention into `resolvers/` (not under `build/`) -3. Upon the next `amplify api gql-compile` or `amplify push` the Amplify-generated resolver file will be replaced within the desired slot within the resolver sequence. - -```console -amplify/backend/api/ -├── build -│   ├── ... -│   ├── resolvers -│   │   ├── ... -│   │   ├── Mutation.createTodo.postAuth.1.req.vtl # Amplify-generated resolvers -│   │   └── ... -| ... -├── resolvers -│   └── Mutation.createTodo.postAuth.2.req.vtl # Custom resolver slotted in after postAuth.1 resolver -``` - -For example, the a resolver function file named `Mutation.createTodo.postAuth.2.req.vtl` will be slotted in right after the `Mutation.createTodo.postAuth.1.req.vtl` resolver. Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. - -### Supported resolver slots - -#### Query - -| Sequence | Slot name | Description | -| --- | --- | --- | -| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | -| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | -| 3 | auth | Resolvers that implement authorization rule checks. | -| 4 | postAuth | Resolvers that are run after authorization rule checks. | -| 5 | preDataLoad | Resolvers to configure values to make a request to the data source. | -| 6 | postDataLoad | Resolvers for post-processing after request to data source. | -| 7 | finish | Final set of resolvers before response is returned to client. Typically used for clean-up. | - -#### Mutation - -| Sequence | Slot name | Description | -| --- | --- | --- | -| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | -| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | -| 3 | auth | Resolvers that implement authorization rule checks. | -| 4 | postAuth | Resolvers that are run after authorization rule checks. | -| 5 | preUpdate | Resolvers to configure values to make a request to the data source. | -| 6 | postUpdate | Resolvers for post-processing after request to data source. | -| 7 | finish | Final set of resolvers before response is returned to client. Typically used for clean-up. | - -#### Subscription - -| Sequence | Slot name | Description | -| --- | --- | --- | -| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | -| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | -| 3 | auth | Resolvers that implement authorization rule checks. | -| 4 | postAuth | Resolvers that are run after authorization rule checks. | -| 5 | preSubscribe | Resolver slot that executes after auth but before the subscription returns | diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx deleted file mode 100644 index c6dbad3c5f8..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx +++ /dev/null @@ -1,49 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure authorization modes', - description: - "Learn more about how to configure authorization modes in Amplify's API category", - platforms: ['flutter'] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import ios0 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; - - - -import android1 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; - - - -import js2 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; - - - -import reactnative0 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; - - - -import flutter3 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; - - diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx deleted file mode 100644 index acd39039b98..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx +++ /dev/null @@ -1,933 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Customize authorization rules', - description: 'Add authorization rules to your GraphQL schema to control access to your data.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/swift/build-a-backend/graphqlapi/customize-authorization-rules/' - }, - { - platforms: [ - 'angular', - 'nextjs', - 'javascript', - 'react', - 'vue', - 'react-native' - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/customize-authorization-rules/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -Use the `@auth` directive to configure authorization rules for public, sign-in user, per user, and per user group data access. **Authorization rules operate on the deny-by-default principle**. Meaning that if an authorization rule is not specifically configured, it is denied. - -```graphql -type Todo @model @auth(rules: [{ allow: owner }]) { - content: String -} -``` - -In the example above, each signed-in user, or also known as "owner", of a Todo can create, read, update, and delete their own Todos. - -Amplify also allows you to restrict the allowed operations, combine multiple authorization rules, and apply fine-grained field-level authorization. - -```graphql -type Todo - @model - @auth(rules: [{ allow: public, operations: [read] }, { allow: owner }]) { - content: String -} -``` - -In the example above, everyone (`public`) can read every Todo but owner (authenticated users) can create, read, update, and delete their own Todos. - -### Global authorization rule (only for getting started) - -To help you get started, there's a global authorization rule defined when you create a new GraphQL schema. For production environments, remove the global authorization rule and apply rules on each model instead. - - - - - -```graphql -input AMPLIFY { - globalAuthRule: AuthRule = { allow: public } -} -``` - - - - -In the CDK construct, we call this the "sandbox mode" that you need to explicitly enable via an input parameter. - -```ts -new AmplifyGraphqlApi(this, "MyNewApi", { - ..., - translationBehavior: { - sandboxModeEnabled: true - } -}); -``` - - - - - -The global authorization rule (in this case `{ allow: public }` - allows anyone to create, read, update, and delete) is applied to every data model in the GraphQL schema. - -**Note:** Amplify will always use the most specific authorization rule that's present. For example, a field-level authorization rule will be used in favor of a model-level authorization rule; similarly, a model-level authorization rule will be used in favor of a global authorization rule. - - - -Currently, only `{ allow: public }` is supported as a global authorization rule. - - - -## Authorization strategies - -Use the guide below to select the correct authorization strategy for your use case: - -| **Recommended use case** | **Strategy** | **Provider** | -| --- | --- | --- | -| Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access. | [`public`](#public-data-access) | `apiKey` | -| Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using AWS IAM controls. | [`public`](#public-data-access) | `iam` (or `identityPool` when using CDK construct) | -| Per user data access. Access is restricted to the "owner" of a record. Leverages `amplify add auth` Cognito user pool by default. | [`owner`](#per-user--owner-based-data-access) | `userPools` / `oidc` | -| Any signed-in data access. Unlike owner-based access, **any** signed-in user has access. | [`private`](#signed-in-user-data-access) | `userPools` / `oidc` / `iam` | -| Per user group data access. A specific or dynamically configured group of users have access | [`groups`](#user-group-based-data-access) | `userPools` / `oidc` | -| Define your own custom authorization rule within a Lambda function | [`custom`](#custom-authorization-rule) | `function` | - -### Public data access - -To grant everyone access, use the `public` authorization strategy. Behind the scenes, the API will be protected with an API Key. - -```graphql -type Todo @model @auth(rules: [{ allow: public }]) { - content: String -} -``` - -You can also override the authorization provider. In the example below, you can use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API Key. - - - - -When you run `amplify add auth`, the Amplify CLI generates scoped down IAM policies for the "Unauthenticated role" in Cognito identity pool automatically. - -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: public, provider: iam }]) { - id: ID! - title: String! -} -``` - - - - -Designate an Amazon Cognito identity pool's role for unauthenticated identities by setting the `identityPoolConfig` property: - -```ts -// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well -import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha'; - -const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', { - allowUnauthenticatedIdentities: true, - authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({ - userPool: , - userPoolClient: , - })] }, -}); - -new AmplifyGraphqlApi(this, "MyNewApi", { - definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - }, - identityPoolConfig: { - identityPoolId: identityPool.identityPoolId, - authenticatedUserRole: identityPool.authenticatedRole, - unauthenticatedUserRole: identityPool.unauthenticatedRole, - } - }, -}) -``` - -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: public, provider: identityPool }]) { - id: ID! - title: String! -} -``` - - - -In the Amplify Library's client configuration file (`amplifyconfiguration.json`) set `allowGuestAccess` to `true`. This lets the Amplify Library use the unauthenticated role from your Cognito identity pool when your user isn't logged in. - -```json -{ - "Auth": { - "Cognito": { - "userPoolId": "YOUR_USER_POOL_ID", - "userPoolClientId": "YOUR_USER_POOL_CLIENT_ID", - "identityPoolId": "YOUR_IDENTITY_POOL_ID", - "allowGuestAccess": true - }, - }, - "API": { - "GraphQL": { - "endpoint": "YOUR_API_ENDPOINT", - "region": "YOUR_API_REGION", - "defaultAuthMode": "YOUR_DEFAULT_AUTHORIZATION_MODE", - }, - }, -} -``` - - - - - - - - -### Per-user / owner-based data access - -To restrict a record's access to a specific user, use the `owner` authorization strategy. When `owner` authorization is configured, only the record's `owner` is allowed the specified operations. - -```graphql -# The "owner" of a Todo is allowed to create, read, update, and delete their own todos -type Todo @model @auth(rules: [{ allow: owner }]) { - content: String -} - -# The "owner" of a Todo record is only allowed to create, read, and update it. -# The "owner" of a Todo record is denied to delete it. -type Todo - @model - @auth(rules: [{ allow: owner, operations: [create, read, update] }]) { - content: String -} -``` - -Behind the scenes, Amplify will automatically add a `owner: String` field to each record which contains the record owner's identity information upon record creation. - -By default, the Cognito user pool's user information is populated into the `owner` field. The value saved includes `sub` and `username` in the format `::`. The API will authorize against the full value of `::` or `sub` / `username` separately and return `username`. You can alternatively configure [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). - -You can override the `owner` field to your own preferred field, by specifying a custom `ownerField` in the authorization rule. - - - -Do not set `ownerField` to your `@primaryKey` field or `id` field if no primary key is specified. If you want to query by the `ownerField`, use an `@index` on that `ownerField` to create a secondary index. - - - -```graphql -type Todo @model @auth(rules: [{ allow: owner, ownerField: "author" }]) { - content: String #^^^^^^^^^^^^^^^^^^^^ - author: String # record owner information now stored in "author" field -} -``` - - - -**By default, owners can reassign the owner of their existing record to another user.** - -To prevent an owner from reassigning their record to another user, protect the owner field (by default `owner: String`) with a [field-level authorization rule](#field-level-authorization-rules). For example, in a social media app, you would want to prevent Alice from being able to reassign Alice's Post to Bob. - -```graphql -type Todo @model @auth(rules: [{ allow: owner }]) { - id: ID! - description: String - owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }]) -} -``` - - - -### Multi-user data access - -If you want to grant a set of users access to a record, you can override the `ownerField` to a list of owners. Use this if you want a dynamic set of users to have access to a record. - -```graphql -type Todo @model @auth(rules: [{ allow: owner, ownerField: "authors" }]) { - content: String - authors: [String] -} -``` - -In the example above, upon record creation, the `authors` list is populated with the creator of the record. The creator can then update the `authors` field with additional users. Any user listed in the `authors` field can access the record. - -### Signed-in user data access - -To restrict a record's access to every signed-in user, use the `private` authorization strategy. - -> If you want to restrict a record's access to a specific user, see [Per-user / owner-based data access](#per-user--owner-based-data-access). `private` authorization applies the authorization rule to **every** signed-in user access. - -```graphql -type Todo @model @auth(rules: [{ allow: private }]) { - content: String -} -``` - -In the example above, anyone with a valid JWT token from Cognito user pool are allowed to access all Todos. - -You can also override the authorization provider. In the example below, you can use an "Authenticated Role" from the Cognito identity pool for granting access to signed-in users. - - - - -When you run `amplify add auth`, the Amplify CLI generates scoped down IAM policies for the "Authenticated role" in Cognito identity pool automatically. - -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: private, provider: iam }]) { - id: ID! - title: String! -} -``` - - - -Designate an Amazon Cognito identity pool role for authenticated identities by setting the `identityPoolConfig` property: - -```ts -// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well -import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha'; - -const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', { - allowUnauthenticatedIdentities: true, - authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({ - userPool: , - userPoolClient: , - })] }, -}); - -new AmplifyGraphqlApi(this, "MyNewApi", { - definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - }, - identityPoolConfig: { - identityPoolId: identityPool.identityPoolId, - authenticatedUserRole: identityPool.authenticatedRole, - unauthenticatedUserRole: identityPool.unauthenticatedRole, - } - }, -}) -``` - -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: private, provider: identityPool }]) { - id: ID! - title: String! -} -``` - - - - - -In addition, you can also use OpenID Connect with `private` authorization. See [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). - -**Note:** If you have a connected child model that allows `private` level access, any user authorized to fetch it from the parent model will be able to read the connected child model. For example, - -```graphql -type Todo @model @auth(rules: [{ allow: owner }]) { - id: ID! - name: String! - task: [Task] @hasMany -} - -type Task - @model - @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) { - id: ID! - description: String! -} -``` - -In the above relationship, the owner of a `Todo` record can query all the tasks connected to it, since the `Task` model allows `private` read access. - -### User group-based data access - -To restrict access based on user groups, use the `group` authorization strategy. - -**Static group authorization**: When you want to restrict access to a specific set of user groups, provide the group names in the `groups` parameter. - -```graphql -type Salary @model @auth(rules: [{ allow: groups, groups: ["Admin"] }]) { - id: ID! - wage: Int - currency: String -} -``` - -In the example above, only users that are part of the "Admin" user group are granted access to the Salary model. - - -**Dynamic group authorization**: When you want to restrict access to a set of user groups. - -```graphql -# Dynamic group authorization with multiple groups -type Post @model @auth(rules: [{ allow: groups, groupsField: "groups" }]) { - id: ID! - title: String - groups: [String] -} - -# Dynamic group authorization with a single group -type Post @model @auth(rules: [{ allow: groups, groupsField: "group" }]) { - id: ID! - title: String - group: String -} -``` - -With dynamic group authorization, each record contains an attribute specifying what Cognito groups should be able to access it. Use the `groupsField` argument to specify which attribute in the underlying data store holds this group information. To specify that a single group should have access, use a field of type `String`. To specify that multiple groups should have access, use a field of type `[String]`. - -By default, `group` authorization leverages Amazon Cognito user pool groups but you can also use OpenID Connect with `group` authorization. See [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). - -**Known limitations for real-time subscriptions when using dynamic group authorization**: - -1. If you authorize based on a single group per record, then subscriptions are only supported if the user is part of 5 or fewer user groups -2. If you authorize via an array of groups (`groups: [String]` example above), - -- subscriptions are only supported if the user is part of 20 or fewer groups -- you can only authorize 20 or fewer user groups per record - - -### Custom authorization rule - -You can define your own custom authorization rule with a Lambda function. - -```graphql -type Salary @model @auth(rules: [{ allow: custom }]) { - id: ID! - wage: Int - currency: String -} -``` - -The Lambda function of choice will receive an authorization token from the client and execute the desired authorization logic. The AppSync GraphQL API will receive a payload from Lambda after invocation to allow or deny the API call accordingly. - - - - -Configure the GraphQL API with the Lambda authorization mode, run the following command in your Terminal: - -```bash -amplify update api -``` - -``` -? Select a setting to edit: -> Authorization modes - -> Lambda - -? Choose a Lambda source: -> Create a new Lambda function -``` - - - - -To configure a Lambda function as the authorization mode, set the `lambdaConfig` in the CDK construct. Use the `ttl` to designate the toke expiry time. - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'AWS_LAMBDA', - lambdaConfig: { - function: new lambda.Function(this, 'MyAuthLambda', { - code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/auth')), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_18_X - }), - ttl: cdk.Duration.seconds(10) - } - } -}); -``` - -You can leverage this Lambda function code template as a starting point to author your authorization handler code: - -```js -// This is sample code. Please update this to suite your schema - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -exports.handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - const { - authorizationToken, - requestContext: { apiId, accountId } - } = event; - const response = { - isAuthorized: authorizationToken === 'custom-authorized', - resolverContext: { - // eslint-disable-next-line spellcheck/spell-checker - userid: 'user-id', - info: 'contextual information A', - more_info: 'contextual information B' - }, - deniedFields: [ - `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`, - `Mutation.createEvent` - ], - ttlOverride: 300 - }; - console.log(`response >`, JSON.stringify(response, null, 2)); - return response; -}; -``` - - - - -You can use the default Amplify provided template as a starting point for your custom authorization rule. The authorization Lambda function receives: - -```json -{ - "authorizationToken": "ExampleAuthToken123123123", # Authorization token specified by client - "requestContext": { - "apiId": "aaaaaa123123123example123", # AppSync API ID - "accountId": "111122223333", # AWS Account ID - "requestId": "f4081827-1111-4444-5555-5cf4695f339f", - "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", # GraphQL query - "operationName": "MyQuery", # GraphQL operation name - "variables": {} # any additional variables supplied to the operation - } -} -``` - -Your Lambda authorization function needs to return the following JSON: - -```json -{ - // required - "isAuthorized": true, // if "false" then an UnauthorizedException is raised, access is denied - "resolverContext": { "banana": "very yellow" }, // JSON object visible as $ctx.identity.resolverContext in VTL resolver templates - - // optional - "deniedFields": ["TypeName.FieldName"], // Forces the fields to "null" when returned to the client - "ttlOverride": 10 // The number of seconds that the response should be cached for. Overrides default specified in "amplify update api" -} -``` - -Review the Amplify Library documentation to set the custom authorization token for [GraphQL API](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules#aws-lambda) and [DataStore](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/#configure-custom-authorization-logic-with-aws-lambda). - -## Configure multiple authorization rules - -When combining multiple authorization rules, they are "logically OR"-ed. - -```graphql -type Post - @model - @auth( - rules: [ - { allow: public, operations: [read], provider: iam } - { allow: owner } - ] - ) { - title: String - content: String -} -``` - -```js -import { createPost } from './graphql/mutations'; -import { listPosts } from './graphql/queries'; - -// Creating a post is restricted to Cognito User Pools -const newPostResult = await client.graphql({ - query: queries.createPost, - variables: { input: { title: 'Hello World' } }, - authMode: 'userPool' -}); - -// Listing posts is available to all users (verified by IAM) -const listPostsResult = await client.graphql({ - query: queries.listPosts, - authMode: 'iam' -}); -``` - -In the example above: - -- any user (signed in or not, verified by IAM) is allowed to read all posts -- owners are allowed to create, read, update, and delete their own posts. - -If you are using DataStore and have multiple authorization rules, you can let DataStore automatically determine the best authorization mode client-side. Review how to [Configure Multiple Authorization Types](/[platform]/build-a-backend/more-features/datastore/authz-rules-setup/#configure-multiple-authorization-types) on DataStore for more details. - -## Field-level authorization rules - -When an authorization rule is added to a field, it'll strictly define the authorization rules applied on the field. Field-level authorization rules **do not** inherit model-level authorization rules. Meaning, only the specified field-level authorization rule is applied. - -```graphql -type Employee - @model - @auth(rules: [{ allow: private, operations: [read] }, { allow: owner }]) { - name: String - email: String - ssn: String @auth(rules: [{ allow: owner }]) -} -``` - -In the example above: - -- Owners are allowed to create, read, update, and delete Employee records they own -- Any signed in user has read access -- Any signed in user can read data with the exception of the `ssn` field. This field only has owner auth applied, the field-level auth rule means that model-level auth rules are not applied - - - -To prevent sensitive data from being sent over subscriptions, the GraphQL Transformer needs to alter the response of mutations for those fields by setting them to null. Therefore, to facilitate field-level authorization with subscriptions, you need to either apply field-level authorization rules to all **required** fields, make the other fields nullable, or disable subscriptions by setting it to public or off. - - - -In the example above: - -- **any signed in user** is allowed to read the list of employees' `name` and `email` fields -- **only the employee/owner themselves** have CRUD access to their `ssn` field - - - -To prevent unintended loss of data, the user or role that attempts to `delete` a record should have delete permissions on every field of the `@model` annotated GraphQL type. For example, in the schema below: - -```graphql -type Todo - @model - @auth( - rules: [ - { allow: private, provider: iam } - { allow: groups, groups: ["Admin"] } - ] - ) { - id: ID! - name: String! - @auth( - rules: [ - { allow: private, provider: iam } - { allow: groups, groups: ["Admin"] } - ] - ) - description: String @auth(rules: [{ allow: private, provider: iam }]) -} -``` - -Since the `description` field is not accessible by "Admin" Cognito group users, they cannot delete any `Todo` records. - - - -## Advanced - -### Review and print access control matrix - -Verify your API's access control matrix, by running the following command: - -```bash -amplify status api -acm Blog -``` - -```console -iam:public - ┌─────────┬────────┬──────┬────────┬────────┐ - │ (index) │ create │ read │ update │ delete │ - ├─────────┼────────┼──────┼────────┼────────┤ - │ title │ false │ true │ false │ false │ - │ content │ false │ true │ false │ false │ - └─────────┴────────┴──────┴────────┴────────┘ -userPools:owner:owner - ┌─────────┬────────┬──────┬────────┬────────┐ - │ (index) │ create │ read │ update │ delete │ - ├─────────┼────────┼──────┼────────┼────────┤ - │ title │ true │ true │ true │ true │ - │ content │ true │ true │ true │ true │ - └─────────┴────────┴──────┴────────┴────────┘ -``` - -### Use IAM authorization within the AppSync console - - - - - -IAM-based `@auth` rules are scoped down to only work with Amplify-generated IAM roles. To access the GraphQL API with IAM authorization within your AppSync console, you need to explicitly allow list the IAM user's name. Add the allow-listed IAM users by adding them to `amplify/backend/api//custom-roles.json`. (Create the `custom-roles.json` file if it doesn't exist). Append the `adminRoleNames` array with the IAM role or user names: - -```json -{ - "adminRoleNames": [""] -} -``` - - - - -To grant any IAM principal (AWS Resource, IAM role, IAM user, etc) access, **with the exception of Amazon Cognito identity pool roles**, to this GraphQL API in CDK, you need to enable IAM authorization mode via the `iamConfig` property of the CDK construct. - -```typescript -const userRole = Role.fromRoleName( - this, - 'MyUserRole', - '' -); - -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - }, - iamConfig: { - // Set this value to true. - enableIamAuthorizationMode: true - } - } -}); -``` - -These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. - - - -These "Admin Roles" have special access privileges that are scoped based on their IAM policy instead of any particular `@auth` rule. - -### Using OIDC authorization provider - -`private`, `owner`, and `group` authorization can be configured with an OpenID Connect (OIDC) authorization mode. Add `provider: oidc` to the authorization rule. - - - - -Upon the next `amplify push`, Amplify CLI prompts you for the _OpenID Connect provider domain_, _Client ID_, _Issued at TTL_, and _Auth Time TTL_. - - - - -Use the `oidcConfig` property to configure the _OpenID Connect provider domain_, _Client ID_, _Issued at TTL_, and _Auth Time TTL_. - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'OPENID_CONNECT', - oidcConfig: { - oidcIssuerUrl: '...', - oidcProviderName: '...', - tokenExpiryFromAuth: '...', - tokenExpiryFromIssue: '...', - clientId: '...' - } - } -}); -``` - - - - -```graphql -type Todo - @model - @auth( - rules: [ - { allow: owner, provider: oidc, identityClaim: "user_id" } - { allow: private, provider: oidc } - { allow: group, provider: oidc, groupClaim: "user_groups" } - ] - ) { - content: String -} -``` - -The example above highlights the supported authorization strategies with `oidc` authorization provider. For `owner` and `group` authorization, you also need to [specify a custom identity and group claim](#configure-custom-identity-and-group-claims). - -### Configure custom identity and group claims - -`@auth` supports using custom claims if you do not wish to use the default Amazon Cognito-provided "cognito:groups" or the double-colon-delimited claims, "sub::username", from your JWT token. This can be helpful if you are using tokens from a 3rd party OIDC system or if you wish to populate a claim with a list of groups from an external system, such as when using a [Pre Token Generation Lambda Trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html) which reads from a database. To use custom claims specify `identityClaim` or `groupClaim` as appropriate like in the example below: - -```graphql -type Post - @model - @auth( - rules: [ - { allow: owner, identityClaim: "user_id" } - { allow: groups, groups: ["Moderator"], groupClaim: "user_groups" } - ] - ) { - id: ID! - owner: String - postname: String - content: String -} -``` - -In this example the record owner will check against a `user_id` claim. Similarly, if the `user_groups` claim contains a "Moderator" string then access will be granted. - -### Grant Lambda function access to GraphQL API - -Lambda functions' IAM execution role do not immediately grant access to Amplify's GraphQL API because the API operates on a "deny-by-default"-basis. Access need to be explicitly granted. Depending on how your function is deployed, the workflow slightly differ - - - - - -If you grant a Lambda function in your Amplify project access to the GraphQL API via `amplify update function`, then the Lambda function's IAM execution role is allow-listed to honor the permissions granted on the `Query`, `Mutation`, and `Subscription` types. - -Therefore, these functions have special access privileges that are scoped based on their IAM policy instead of any particular `@auth` rule. - - - -Once you grant a function access to the GraphQL API, it is required to redeploy the API to apply the permissions. To do so, run the command `amplify api gql-compile --force` before deployment via `amplify push`. - - - - - - -To grant any IAM principal (AWS Resource, IAM role, IAM user, etc), **with the exception of Amazon Cognito identity pool roles**, to this GraphQL API in CDK, you need to enable IAM authorization mode on the CDK construct. - -```typescript -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - }, - iamConfig: { - // Must be set to `true`. Then grant your Lambda function's execution role access to the API - enableIamAuthorizationMode: true - } - } -}); -``` - -These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. - - - - - -To grant an external AWS Resource or an IAM role access to this GraphQL API, you need to explicitly list the IAM role's name or the AWS Resource's name by adding it to `amplify/backend/api//custom-roles.json`. (Create the `custom-roles.json` file if it doesn't exist). Append the `adminRoleNames` array with the IAM role name or AWS Resource name: - -```json -{ - "adminRoleNames": ["", ""] -} -``` - -You can use the symbol `${env}` to reference the current Amplify CLI environment. - -These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. - - - - - - - -Refer to the [sample code](/[platform]/build-a-backend/graphqlapi/connect-from-server-runtime/#iam-authorization) to learn how to sign the request to call the GraphQL API using IAM authorization. - - - -### How it works - -Definition of the `@auth` directive: - -```graphql -# When applied to a type, augments the application with -# owner and group-based authorization rules. -directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION -input AuthRule { - allow: AuthStrategy! - provider: AuthProvider - ownerField: String # defaults to "owner" when using owner auth - identityClaim: String # defaults to "sub::username" when using owner auth - groupClaim: String # defaults to "cognito:groups" when using Group auth - groups: [String] # Required when using Static Group auth - groupsField: String # defaults to "groups" when using Dynamic Group auth - operations: [ModelOperation] # Required for finer control -} - -enum AuthStrategy { - owner - groups - private - public - custom -} -enum AuthProvider { - apiKey - iam - oidc - userPools - function -} -enum ModelOperation { - create - update - delete - read # Short-hand to allow "get", "list", "sync", "listen", and "search" - get # Retrieves an individual item - list # Retrieves a list of items - sync # Enables ability to sync offline/online changes (including via DataStore) - listen # Subscribes to real-time changes - search # Enables ability to search using @searchable directive -} -``` - -Authorization rules consists of: - -- **authorization strategy** (`allow`): who the authorization rule applies to -- **authorization provider** (`provider`): which mechanism is used to apply the authorization rule (API Key, IAM, Amazon Cognito user pool, OIDC) -- **authorized operations** (`operations`): which operations are allowed for the given strategy and provider. If not specified, `create`, `read`, `update`, and `delete` operations are allowed. - - **`read` operation**: `read` operation can be replaced with `get`, `list`, `sync`, `listen`, and `search` for a more granular query access - - - -If you use DataStore instead of the API category to connect to your AppSync API, then you must allow `listen` and `sync` operations for your data model. - - - -**API Keys** are best used for public APIs (or parts of your schema which you wish to be public) or prototyping, and you must specify the expiration time before deploying. **IAM** authorization uses [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) to make request with policies attached to Roles. OIDC tokens provided by **Amazon Cognito user pool** or **3rd party OpenID Connect** providers can also be used for authorization, and enabling this provides a simple access control requiring users to authenticate to be granted top level access to API actions. diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx deleted file mode 100644 index 68d7cbfbc92..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx +++ /dev/null @@ -1,1262 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Customize your data model', - description: 'Customize your data model with primary keys, secondary indexes, and model relationships.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/data-modeling/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -Amplify automatically creates Amazon DynamoDB database tables for GraphQL types annotated with the `@model` directive in your GraphQL schema. You can create relations between the data models via the `@hasOne`, `@hasMany`, `@belongsTo`, and `@manyToMany` directives. - -## Setup database tables - -The following GraphQL schema automatically creates a database table for "Todo". `@model` will also automatically add an `id` field as a primary key to the database table. _See [Configure a primary key](#configure-a-primary-key) to learn how to customize the primary key._ - -```graphql -type Todo @model { - content: String -} -``` - -Upon `amplify push` or `cdk deploy`, Amplify deploys the Todo database table and a corresponding GraphQL API to perform create, read, update, delete, and list operations. - -In addition, `@model` also adds the helper fields `createdAt` and `updatedAt` to your type. The values for those fields are read-only by clients unless explicitly overwritten. See [Customize creation and update timestamps](#customize-creation-and-update-timestamps) to learn more. - -Try listing all the todos by executing the following query: - -```graphql -query QueryAllTodos { - listTodos() { - todos { - items { - id - content - createdAt - updatedAt - } - } - } -} -``` - - - -```js -import { Amplify } from 'aws-amplify'; -import { generateClient } from 'aws-amplify/api'; -import config from './amplifyconfiguration.json'; -import { listTodos } from './graphql/queries'; - -const client = generateClient(); - -Amplify.configure(config); - -try { - const result = await client.graphql({ query: listTodos }); - const todos = result.data.listTodos; -} catch (res) { - const { errors } = res; - console.error(errors); -} -``` - - - -### Configure a primary key - -Every GraphQL type with the `@model` directive will automatically have an `id` field set as the primary key. You can override this behavior by marking another required field with the `@primaryKey` directive. - -In the example below, `todoId` is the database's primary key and an `id` field will no longer be created automatically anymore by the `@model` directive. - -```graphql -type Todo @model { - todoId: ID! @primaryKey - content: String -} -``` - -Without any further configuration, you'll only be able to query for a Todo via an exact equality match of its primary key field. In the example above, this is the `todoId` field. - -> Note: After a primary key is configured and deployed, you can't change it without deleting and recreating your database table. - -You can also specify "sort keys" to use a combination of different fields as a primary key. This also allows you to apply more advanced sorting and filtering conditions on the specified "sort key fields". - -```graphql -type Inventory @model { - productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) - warehouseID: ID! - InventoryAmount: Int! -} -``` - -The schema above will allow you to pass different conditions to query the correct inventory item: - -```graphql -query QueryInventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { - getInventory(productID: $productID, warehouseID: $warehouseID) { - productID - warehouseID - inventoryAmount - } -} -``` - - - -```js -import { getInventory } from './graphql/queries'; - -const result = await client.graphql({ - query: getInventory, - variables: { - productID: 'product-id', - warehouseID: 'warehouse-id' - } -}); -const inventory = result.data.getInventory; -``` - - - -### Configure a secondary index - -Amplify uses Amazon DynamoDB tables as the underlying data source for @model types. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `@index` directive to configure a secondary index. - -> **Amazon DynamoDB** is a key-value and document database that delivers single-digit millisecond performance at any scale but making it work for your access patterns requires a bit of forethought. DynamoDB query operations may use at most two attributes to efficiently query data. The first query argument passed to a query (the hash key) must use strict equality and the second attribute (the sort key) may use gt, ge, lt, le, eq, beginsWith, and between. DynamoDB can effectively implement a wide variety of access patterns that are powerful enough for the majority of applications. - -A secondary index consists of a "hash key" and, optionally, a "sort key". Use the "hash key" to perform strict equality and the "sort key" for greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), equals (eq), begins with, and between operations. - -```graphql -type Customer @model { - id: ID! - name: String! - phoneNumber: String - accountRepresentativeID: ID! @index -} -``` - -The example client query below allows you to query for "Customer" records based on their `accountRepresentativeID`: - -```graphql -query QueryCustomersForAccountRepresentative($accountRepresentativeID: ID!) { - customersByAccountRepresentativeID( - accountRepresentativeID: $accountRepresentativeID - ) { - customers { - items { - id - name - phoneNumber - } - } - } -} -``` - - - - -```js -import { customersByAccountRepresentativeID } from './graphql/queries'; - -const result = await client.graphql({ - query: customersByAccountRepresentativeID, - variables: { - accountRepresentativeID: 'account-rep-id' - } -}); -const customers = result.data.customersByAccountRepresentativeID; -``` - - - -You can also overwrite the `queryField` or `name` to customize the GraphQL query name, or secondary index name respectively: - -```graphql -type Customer @model { - id: ID! - name: String! - phoneNumber: String - accountRepresentativeID: ID! - @index(name: "byRepresentative", queryField: "customerByRepresentative") -} -``` - -```graphql -query QueryCustomersForAccountRepresentative($representativeId: ID!) { - customerByRepresentative(accountRepresentativeID: $representativeId) { - customers { - items { - id - name - phoneNumber - } - } - } -} -``` - - - -```js -import { customerByRepresentative } from './graphql/queries'; - -const result = await client.graphql({ - query: customerByRepresentative, - variables: { - accountRepresentativeID: 'account-rep-id' - } -}); -const customer = result.data.customerByRepresentative; -``` - - - -To optionally configure sort keys, provide the additional fields in the `sortKeyFields` parameter: - -```graphql -type Customer @model @auth(rules: [{ allow: public }]) { - id: ID! - name: String! @index(name: "byNameAndPhoneNumber", sortKeyFields: ["phoneNumber"], queryField: "customerByNameAndPhone") - phoneNumber: String - accountRepresentativeID: ID! @index -``` - -The example client query below allows you to query for "Customer" based on their `name` and filter based on `phoneNumber`: - -```graphql -query MyQuery { - customerByNameAndPhone(phoneNumber: { beginsWith: "+1" }, name: "Rene") { - items { - id - name - phoneNumber - } - } -} -``` - - - -```js -import { customerByNameAndPhone } from './graphql/queries'; - -const result = await client.graphql({ - query: customerByNameAndPhone, - variables: { - phoneNumber: { beginsWith: '+1' }, - name: 'Rene' - } -}); - -const customer = result.data.customerByNameAndPhone; -``` - - - - - -## Setup relationships between models - -Create "has one", "has many", "belongs to", and "many to many" relationships between `@model` types. - -| Relationship | Description | -| --- | --- | -| `@hasOne` | Create a one-directional one-to-one relationship between two models. For example, a Project "has one" Team. This allows you to query the team from the project record. | -| `@hasMany` | Create a one-directional one-to-many relationship between two models. For example, a Post has many comments. This allows you to query all the comments from the post record. | -| `@belongsTo` | Use a "belongs to" relationship to make a "has one" or "has many" relationship bi-directional. For example, a Project has one Team and a Team belongs to a Project. This allows you to query the team from the project record and vice versa. | -| `@manyToMany` | Configures a "join table" between two models to facilitate a many-to-many relationship. For example, a Blog has many Tags and a Tag has many Blogs. | - -### Has One relationship - -import gqlv2callout from '/src/fragments/cli/gqlv2callout.mdx'; - - - -Create a one-directional one-to-one relationship between two models using the `@hasOne` directive. - -In the example below, a Project has a Team. - -```graphql -type Project @model { - id: ID! - name: String - team: Team @hasOne -} - -type Team @model { - id: ID! - name: String! -} -``` - -This generates queries and mutations that allow you to retrieve the related record from the source record: - -```graphql -mutation CreateProject { - createProject(input: { projectTeamId: "team-id", name: "Some Name" }) { - team { - name - id - } - name - id - } -} -``` - - -```js -import { createProject } from './graphql/mutations'; - -const result = await client.graphql({ - query: createProject, - variables: { - input: { projectTeamId: 'team-id', name: 'Some Name' } - } -}); - -const project = result.data.createProject; -``` - -To customize the field to be used for storing the relationship information, set the `fields` array argument and matching it to a field on the type: - -```graphql -type Project @model { - id: ID! - name: String - teamID: ID - team: Team @hasOne(fields: ["teamID"]) -} - -type Team @model { - id: ID! - name: String! -} -``` - -In this case, the Project type has a `teamID` field added as an identifier for the team. @hasOne can then get the connected Team object by querying the Team table with this `teamID`: - -```graphql -mutation CreateProject { - createProject(input: { name: "New Project", teamID: "a-team-id" }) { - id - name - team { - id - name - } - } -} -``` - - -```js -import { createProject } from './graphql/mutations'; - -const result = await client.graphql({ - query: createProject, - variables: { - input: { - teamID: 'team-id', - name: 'New Project' - } - } -}); -const project = result.data.createProject; -``` - -A `@hasOne` relationship always uses a reference to the primary key of the related model, by default `id` unless overridden with the [`@primaryKey` directive](#configure-a-primary-key). - -### Has Many relationship - - - -Create a one-directional one-to-many relationship between two models using the `@hasMany` directive. - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany -} - -type Comment @model { - id: ID! - content: String! -} -``` - -This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record: - -```graphql -mutation CreatePost { - createPost(input: { title: "Hello World!!" }) { - title - id - comments { - items { - id - content - } - } - } -} -``` - - -```js -import { createPost } from './graphql/mutations'; - -const result = await client.graphql({ - query: createPost, - variables { - input: { title: 'Hello World!!' }, - } -}); -const post = result.data.createPost; -const comments = post.comments.items; -``` - -Under the hood, `@hasMany` configures a default secondary index on the related table to enable you to query the related model from the source model. - -To customize the specific secondary index used for the "has many" relationship, create an `@index` directive on the field in the related table where you want to assign the secondary index. - -Next, provide the secondary index with a `name` attribute and a value. Optionally, you can configure a “sort key” on the secondary index by providing a `sortKeyFields` attribute and a designated field as its value. - -On the `@hasMany` configuration, pass in the name value from your secondary index as the value for the `indexName` parameter. Then, pass in the respective `fields` that match the connected index. - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) -} - -type Comment @model { - id: ID! - postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) - content: String! -} -``` - -In this case, the Comment type has a `postID` field added to store the reference of Post record. The `id` field referenced by `@hasMany` is the `id` on the `Post` type. `@hasMany` can then get the connected Comment object by querying the Comment table's secondary index "byPost" with this `postID`: - -```graphql -mutation CreatePost { - createPost(input: { title: "Hello world!" }) { - comments { - items { - postID - content - id - } - } - title - id - } -} -``` - -```js -import { createPost, createComment } from './graphql/mutations'; -import { getPost } from './graphql/mutations'; - -// create post -const result = await client.graphql({ - query: createPost, - variables: { - input: { title: 'Hello World!!' } - } -}); -const post = result.data.createPost; - -// create comment -await client.graphql({ - query: createComment, - variables: { - input: { content: 'Hi!', postID: post.id } - } -}); - -// get post -const result = await client.graphql({ - query: getPost, - variables: { id: post.id } -}); - -const postWithComments = result.data.createPost; -const postComments = postWithComments.comments.items; // access comments from post -``` - - -### Belongs To relationship - -Make a "has one" or "has many" relationship bi-directional with the `@belongsTo` directive. - - - - - -For 1:1 relationships, the @belongsTo directive solely facilitates the ability for you to query from both sides of the relationship. When updating a bi-directional hasOne relationship, you must update both sides of the relationship with corresponding IDs. - -```graphql -type Project @model { - id: ID! - name: String - team: Team @hasOne -} - -type Team @model { - id: ID! - name: String! - project: Project @belongsTo -} -``` - -This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: - -```graphql -mutation CreateProject { - createProject(input: { name: "New Project", teamID: "a-team-id" }) { - id - name - team { - # query team from project - id - name - project { - # bi-directional query: team to project - id - name - } - } - } -} -``` - -```js -import { createProject, createTeam, updateTeam } from './graphql/mutations'; - -// create team -const result = await client.graphql({ - query: createTeam, - variables: { - input: { name: 'New Team' } - } -}); -const team = result.data.createTeam; - -// create project -const result = await client.graphql({ - query: createProject, - variables: { - input: { name: 'New Project', projectTeamId: team.id } - } -}); -const project = result.data.createProject; -const projectTeam = project.team; // access team from project - -// update team -const updateTeamResult = await client.graphql({ - query: updateTeam, - variables: { - input: { id: team.id, teamProjectId: project.id } - } -}); - -const updatedTeam = updateTeamResult.data.updateTeam; -const teamProject = updatedTeam.project; // access project from team -``` - - - - - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany -} - -type Comment @model { - id: ID! - content: String! - post: Post @belongsTo -} -``` - -This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: - -```graphql -mutation CreatePost { - createPost(input: { title: "Hello World!!" }) { - title - id - comments { - # query comments from the post - items { - id - content - post { - # bi-directional query: comment to post - id - title - } - } - } - } -} -``` - -```js -import { createPost, createComment } from './graphql/mutations'; -import { getPost } from './graphql/mutations'; - -// create post -const result = await client.graphql({ - query: createPost, - variables: { - input: { title: 'Hello World!!' } - } -}); -const post = result.data.createPost; - -// create comment -await client.graphql({ - query: createComment, - variables: { - input: { content: 'Hi!', postID: post.id } - } -}); - -// get post -const result = await client.graphql({ - query: getPost, - variables: { id: post.id } -}); - -const postWithComments = result.data.createPost; -const postComments = postWithComments.comments.items; // access comments from post - -const commentPost = postComments[0].post; // access post from comment; -``` - - - - - -`@belongsTo` can be used without the `fields` argument. In those cases, a field is automatically generated to reference the parent’s primary key. - -Alternatively, you set up a custom field to store the reference of the parent object. An example bidirectional “has many” relationship is shown below. - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) -} - -type Comment @model { - id: ID! - postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) - content: String! - post: Post @belongsTo(fields: ["postID"]) -} -``` - -> Note: The `@belongsTo` directive requires that a `@hasOne` or `@hasMany` relationship already exists from parent to the related model. - -### Many-to-many relationship - -Create a many-to-many relationship between two models with the `@manyToMany` directive. Provide a common `relationName` on both models to join them into a many-to-many relationship. - -```graphql -type Post @model { - id: ID! - title: String! - content: String - tags: [Tag] @manyToMany(relationName: "PostTags") -} - -type Tag @model { - id: ID! - label: String! - posts: [Post] @manyToMany(relationName: "PostTags") -} -``` - -Under the hood, the `@manyToMany` directive will create a "join table" named after the `relationName` to facilitate the many-to-many relationship. This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: - -```graphql -mutation CreatePost { - createPost(input: { title: "Hello World!!" }) { - id - title - content - tags { - # queries the "join table" PostTags - items { - tag { - # related Tag records from Post - id - label - posts { - # queries the "join table" PostTags - items { - post { - # related Post records from Tag - id - title - content - } - } - } - } - } - } - } -} -``` - -```js -import { createPost, createTag, createPostTags } from './graphql/mutations'; -import { listPosts } from './graphql/queries'; - -// create post -const result = await client.graphql({ - query: createPost, - variables: { - input: { title: 'Hello World' } - } -}); -const post = result.data.createPost; - -// create tag -const tagResult = await client.graphql({ - query: createTag, - variables: { - input: { - label: 'My Tag' - } - } -}); -const tag = tagResult.data.createTag; - -// connect post and tag -await client.graphql({ - query: createPostTags, - variables: { - input: { - postId: post.id, - tagId: tag.id - } - } -}); - -// get posts -const listPostsResult = await client.graphql({ query: listPosts }); -const posts = listPostsResult.data.listPosts; - -const postTags = posts[0].tags; // access tags from post -``` - -## Assign default values for fields - -You can use the `@default` directive to specify a default value for optional [scalar type fields](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html) such as `Int`, `String`, and more. - -```graphql -type Todo @model { - content: String @default(value: "My new Todo") - # Note: all "value" parameters must be passed as a string value. - # Under the hood, Amplify will parse the string values into respective types. - # For example, to set a default value for an integer field, - # you must pass in `"0"` instead of `0` without the double-quotes. - likes: Int @default(value: "0") # -} -``` - -If you create a new Todo and don't supply a `content` input, Amplify will ensure that `My new Todo` is auto populated as a value. When `@default` is applied, non-null assertions using `!` are disregarded. For example, `String!` is treated the same as `String`. - -## Server-side filtering for subscriptions - -A server-side subscription filter expression is automatically generated for any `@model` type. - -```graphql -type Task @model { - title: String! - description: String - type: String - priority: Int -} -``` - -You can filter the subscriptions server-side by passing a filter expression. For example: If you want to subscribe to tasks of type `Security` and priority greater than `5`, you can set the `filter` argument accordingly. - -```graphql -subscription OnCreateTask { - onCreateTask( - filter: { and: [{ type: { eq: "Security" } }, { priority: { gt: 5 } }] } - ) { - title - description - type - priority - } -} -``` - -```js -import { onCreateTask } from './graphql/subscriptions'; - -const subscription = client.graphql({ - query: onCreateTask, - variables: { - filter: { - and: [ - { type: { eq: "Security" } } - { priority: { gt: 5 } } - ] - } - } -}).subscribe({ - next: ({ data }) => console.log(data), - error: (error) => console.warn(error) -}); -``` - -If you want to get all subscription events, don’t pass any `filter` parameters. - - - -**Important**: Passing an empty object `{}` as a filter is NOT recommended. Using `{}` as a filter might cause inconsistent behavior based on your data model's authorization rules. - - - - - -## Advanced - -### Rename generated queries, mutations, and subscriptions - -You can override the names of any `@model`-generated GraphQL queries, mutations, and subscriptions by supplying the desired name. - -```graphql -type Todo @model(queries: { get: "queryFor" }) { - name: String! - description: String -} -``` - -In the example above, you will be able to run a `queryForTodo` query to get a single Todo element. - -### Disable generated queries, mutations, and subscriptions - -You can disable specific operations by assigning their value to `null`. - -```graphql -type Todo @model(queries: { get: null }, mutations: null, subscriptions: null) { - name: String! - description: String -} -``` - -The example above disables the `getTodo` query, all mutations, and all subscriptions while allowing the generation of other queries such as `listTodo`. - -### Creating a custom query - -You can disable the `get` query and create a custom query that enables us to retrieve a single Todo model. - -```graphql -type Query { - getMyTodo(id: ID!): Todo @function(name: "getmytodofunction-${env}") -} -``` - -The example above creates a custom query that utilizes the `@function` directive to call a Lambda function for this query. - -For the type definitions of queries, mutations, and subscriptions, see [Type Definitions of the `@model` Directive](#type-definition-of-the-`@model`-directive). - -### Customize creation and update timestamps - -The `@model` directive automatically adds `createdAt` and `updatedAt` timestamps to each entity. The timestamp field names can be changed by passing timestamps attribute to the directive. - -```graphql -type Todo - @model(timestamps: { createdAt: "createdOn", updatedAt: "updatedOn" }) { - name: String! - description: String -} -``` - -For example, the schema above will allow you to query for the following contents: - -```graphql -type Todo { - id: ID! - name: String! - description: String - createdOn: AWSDateTime! - updatedOn: AWSDateTime! -} -``` - -### Modify subscriptions (real-time updates) access level - -By default, real-time updates are on for all `@model` types, which means customers receive real-time updates and authorization rules are applied during initial connection time. You can also turn off subscriptions for that model or make the real-time updates public, receivable by all subscribers. - -```graphql -type Todo - @model(subscriptions: { level: off }) { # or level: public - name: String! - description: String -} -``` - -### Create multiple relationships between two models - -You need to explicitly specify the connection field names if relational directives are used to create two connections of the same type between the two models. - -```graphql -type Individual @model { - id: ID! - homeAddress: Address @hasOne - shippingAddress: Address @hasOne -} - -type Address @model { - id: ID! - homeIndividualID: ID - shippingIndividualID: ID - homeIndividual: Individual @belongsTo(fields: ["homeIndividualID"]) - shipIndividual: Individual @belongsTo(fields: ["shippingIndividualID"]) -} -``` - -### Relationships to a model with a composite primary key - -When a primary key is defined by a _sort key_ in addition to the _hash key_, then it's called a **composite primary key**. - -If you explicitly define the `fields` argument on the `@hasOne`, `@hasMany`, or `@belongsTo` directives and reference a model that has a composite primary key, then you must set the values in the `fields` argument in a specific order: - -- The first value should always be the primary key of the related model. -- Remaining values should match the `sortKeyFields` specified in the `@primaryKey` directive of the related model. - - - - - -```graphql -type Project @model { - projectId: ID! @primaryKey(sortKeyFields: ["name"]) - name: String! - team: Team @hasOne(fields: ["teamId", "teamName"]) - teamId: ID # customized foreign key for child primary key - teamName: String # customized foreign key for child sort key -} - -type Team @model { - teamId: ID! @primaryKey(sortKeyFields: ["name"]) - name: String! -} -``` - - - - - -```graphql -type Project @model { - projectId: ID! @primaryKey(sortKeyFields: ["name"]) - name: String! - team: Team @hasOne(fields: ["teamId", "teamName"]) - teamId: ID # customized foreign key for child primary key - teamName: String # customized foreign key for child sort key -} - -type Team @model { - teamId: ID! @primaryKey(sortKeyFields: ["name"]) - name: String! - project: Project @belongsTo(fields: ["projectId", "projectName"]) - projectId: ID # customized foreign key for parent primary key - projectName: String # customized foreign key for parent sort key -} -``` - - - - - -```graphql -type Post @model { - postId: ID! @primaryKey(sortKeyFields: ["title"]) - title: String! - comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"]) -} - -type Comment @model { - commentId: ID! @primaryKey(sortKeyFields: ["content"]) - content: String! - postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key - postTitle: String # customized foreign key for parent sort key -} -``` - - - - - -```graphql -type Post @model { - postId: ID! @primaryKey(sortKeyFields: ["title"]) - title: String! - comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"]) -} - -type Comment @model { - commentId: ID! @primaryKey(sortKeyFields: ["content"]) - content: String! - post: Post @belongsTo(fields: ["postId", "postTitle"]) - postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key - postTitle: String # customized foreign key for parent sort key -} -``` - - - - - -### Generate a secondary index without a GraphQL query - -Because query creation against a secondary index is automatic, if you wish to define a secondary index that does not have a corresponding query in your API, set the `queryField` parameter to `null`. - -```graphql -type Customer @model { - id: ID! - name: String! - phoneNumber: String - accountRepresentativeID: ID! @index(queryField: null) -} -``` - -### Split GraphQL files - - -Amplify Studio does not support splitting GraphQL schemas. - -If using Amplify Studio, please follow the [Limitations](https://docs.amplify.aws/javascript/tools/console/data/data-model/#split-graphql-files) section of the Data Modeling documentation for Amplify Studio. - - -AWS Amplify supports splitting your GraphQL schema into separate `.graphql` files. - -You can start by creating a `amplify/backend/api//schema/` directory. As an example, you might split up the schema for a blog site by creating `Blog.graphql`, `Post.graphql`, and `Comment.graphql` files. - -You can then run `amplify api gql-compile` and the output build schema will include all the types declared across your schema files. - -As your project grows, you may want to organize your custom queries, mutations, and subscriptions depending on the size and maintenance requirements of your project. You can either consolidate all of them into one file or colocate them with their corresponding models. - -**Using a Single `Query.graphql` File** - -This method involves consolidating all queries into a single `Query.graphql` file. It is useful for smaller projects or when you want to keep all queries in one place. - -1. In the `amplify/backend/api//schema/` directory, create a file named `Query.graphql`. - -2. Copy all query type definitions from your multiple schema files into the `Query.graphql` file. - -3. Make sure all your queries are properly formatted and enclosed within a single `type Query { ... }` block. - -**Using the `extend` Keyword** - -Declaring a `Query` type in separate schema files will result in schema validation errors similar to the following when running `amplify api gql-compile`: - -```sh -🛑 Schema validation failed. - -There can be only one type named "Query". -``` - -Amplify GraphQL schemas support the `extend` keyword, which allows you to extend types with additional fields. In this case, it also allows you to split your custom queries, mutations, and subscriptions into multiple files. This may be more ideal for larger, more complex projects. - -1. Organize your GraphQL schema into multiple files as per your project's architecture. - -2. In one of the files (e.g., `schema1.graphql`), declare your type normally: - -```graphql -type Query { - # initial custom queries -} -``` - -3. In other schema files (e.g., `schema2.graphql`), use the `extend` keyword to add to the type: - -```graphql -extend type Query { - # additional custom queries -} -``` - -The order in which the Query types are extended does not affect the compilation of separate schema files. - - - -Declaring custom Query, Mutation, and/or Subscription with the same field names in another schema file will result in schema validation errors similar to the following: - -`🛑 Object type extension 'Query' cannot redeclare field getBlogById` - - - -## How it works - -### Model directive - -The `@model` directive will generate: - -- An Amazon DynamoDB table with PAY_PER_REQUEST billing mode enabled by default. -- An AWS AppSync DataSource configured to access the table above. -- An AWS IAM role attached to the DataSource that allows AWS AppSync to call the above table on your behalf. -- Up to 8 resolvers (create, update, delete, get, list, onCreate, onUpdate, onDelete) but this is configurable via the queries, mutations, and subscriptions arguments on the @model directive. -- Input objects for create, update, and delete mutations. -- Filter input objects that allow you to filter objects in list queries and relationship fields. -- For list queries the default number of objects returned is 100. You can override this behavior by setting the limit argument. - -**Type definition of the `@model` directive** - -```graphql -directive @model( - queries: ModelQueryMap - mutations: ModelMutationMap - subscriptions: ModelSubscriptionMap - timestamps: TimestampConfiguration -) on OBJECT - -input ModelMutationMap { - create: String - update: String - delete: String -} - -input ModelQueryMap { - get: String - list: String -} - -input ModelSubscriptionMap { - onCreate: [String] - onUpdate: [String] - onDelete: [String] - level: ModelSubscriptionLevel -} - -enum ModelSubscriptionLevel { - off - public - on -} - -input TimestampConfiguration { - createdAt: String - updatedAt: String -} -``` - -### Relational directives - -The relational directives are `@hasOne`, `@hasMany`, `@belongsTo` and `@manyToMany`. - - - - -The `@hasOne` will generate: - -- Foreign key fields in parent type that refer to the primary key and sort key fields of the child model. -- Foreign key fields in parent input object of `create` and `update` mutations. - -**Type definition of the `@hasOne` directive** - -```graphql -directive @hasOne(fields: [String!]) on FIELD_DEFINITION -``` - - - - -The `@hasMany` will generate: - -- Foreign key fields in child type that refer to the primary key and sort key fields of the parent model. -- Foreign key fields in child input object of `create` and `update` mutations. -- A global secondary index (GSI) in the child type Amazon DynamoDB table. - -**Type definition of the `@hasMany` directive** - -```graphql -directive @hasMany( - indexName: String - fields: [String!] - limit: Int = 100 -) on FIELD_DEFINITION -``` - -- The default number of nested objects returned is 100. You can override this behavior by setting the limit argument. - - - - -The `@belongsTo` will generate: - -- Foreign key fields that refer to the primary key and sort key fields of the related model. -- Foreign key fields in the input object of `create` and `update` mutations. - -**Type definition of the `@belongsTo` directive** - -```graphql -directive @belongsTo(fields: [String!]) on FIELD_DEFINITION -``` - - - - -The `@manyToMany` will generate: - -- A joint table defining the intermediate model type with the name of `relationName`. -- Foreign key fields in the joint table that refer to the primary key and sort key fields of both models. -- Foreign key fields in the intermediate model input object of `create` and `update` mutations. - -**Type definition of the `@manyToMany` directive** - -```graphql -directive @manyToMany( - relationName: String! - limit: Int = 100 -) on FIELD_DEFINITION -``` - -- The default number of nested objects returned is 100. You can override this behavior by setting the limit argument. - - - diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx deleted file mode 100644 index 4385045cc55..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx +++ /dev/null @@ -1,500 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Modify Amplify-generated resources', - description: 'Learn more about how to modify Amplify-generated resources for Amplify GraphQL APIs. This allows you to modify underlying AppSync, DynamoDB, Lambda, and OpenSearch resources.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/modify-amplify-generated-resources/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -Amplify GraphQL API uses a variety of auto-generated, underlying AWS services and resources. You can customize these underlying resources to optimize the deployed stack for your specific use case. - - - - - -```bash -amplify override api -``` - -Run the command above to override Amplify-generated GraphQL API resources including AWS AppSync API, Amazon DynamoDB table, Amazon OpenSearch domain, and more. - - - -If you need to customize a specific Amplify-generated VTL resolver, review [Override Amplify-generated resolvers](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#override-amplify-generated-resolvers) first. - - - -The command creates a new `overrides.ts` file under `amplify/backend/api//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). - - - - -In your CDK project, you can access every underlying resource as an ["L2"](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_using) or ["L1" construct](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_l1_using). Access the generated resources as L2 constructs via the `.resources` property on the returned stack or access the generated resources as L1 constructs using the `.resources.cfnResources` property. - -```ts -const api = new AmplifyGraphQlApi(this, 'api', { }); - -// Access L2 resources under `.resources` -api.resources.tables["Todo"].tableArn; - -// Access L1 resources under `.resources.cfnResources` -api.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true; -Object.values(api.resources.cfnResources.cfnTables).forEach(table => { - table.pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: false }; -}); -``` - - - - - -## Customize Amplify-generated AppSync GraphQL API resources - - - - - -Apply all the overrides in the `override(...)` function. For example to enable X-Ray tracing for the AppSync GraphQL API: - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.api.GraphQLAPI.xrayEnabled = true; -} -``` - -You can override the following GraphQL API resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [GraphQLAPI](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlapi.html) | AWS AppSync GraphQL API resource | -| [GraphQLAPIDefaultApiKey](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-apikey.html) | API Key resource for the AppSync GraphQL API | -| [GraphQLAPITransformerSchema](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlschema.html) | The GraphQL schema that's being deployed. (The output of the GraphQL Transformer) | -| [GraphQLAPINONEDS](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | A "none" data source that is used for requests that don't exit the AppSync API | -| [AmplifyDataStore](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The delta sync table used for Amplify DataStore's conflict resolution | -| [AmplifyDataStoreIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role used to access the delta sync table for DataStore | -| [DynamoDBAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access the DynamoDB resources from AppSync | - - - - -Apply all the customizations on `.resources.graphqlApi` or `.resources.cfnResources.cfnGraphqlApi`. For example to enable X-Ray tracing for the AppSync GraphQL API: - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); - -amplifyApi.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true; -``` - - - - - -## Customize Amplify-generated resources for @model directive - - - - - -Apply all the overrides in the `override(...)` function. Pass in the @model type name into `resources.models[...]` to modify the resources generated for that particular @model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Todo'].modelDDBTable.timeToLiveSpecification = { - attributeName: 'ttl', - enabled: true - }; -} -``` - -You can override the following @model directive resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [modelStack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html) | The nested stack containing all resources for the @model type | -| [modelDDBTable](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The DynamoDB table containing the data for this @model type | -| [modelIamRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access the DynamoDB table for this @model type | -| [modelIamRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access the delta sync table for this @model type in case DataStore is enabled | -| [dynamoDBAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | Default policy associated with the IAM role to access the DynamoDB table for this @model type | -| [modelDatasource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | The AppSync DataSource to representing the DynamoDB table | -| [invokeLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for Lambda-based conflict resolution function | - -For example, you can override a model generated DynamoDB table configuration. - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Todo'].modelDatasource.dynamoDbConfig['deltaSyncConfig'][ - 'baseTableTtl' - ] = '3600'; -} -``` - - - - -Apply all the customizations on `.resources.tables["MODEL_NAME"]` or `.resources.cfnResources.cfnTables["MODEL_NAME"]`. Pass in the @model type name into `.resources.cfnResources.cfnTables["MODEL_NAME"]` to modify the resources generated for that particular @model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); - -amplifyApi.resources.cfnResources.cfnTables['Todo'].timeToLiveSpecification = { - attributeName: 'ttl', - enabled: true -}; -``` - - - - - -### Example - Configure DynamoDB table's billing mode - -Set the [DynamoDB billing mode](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-billingmode) for the DynamoDB table. Either "PROVISIONED" or "PAY_PER_REQUEST". - - - - - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Post'].modelDDBTable.billingMode = 'PAY_PER_REQUEST'; -} -``` - - - - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); - -amplifyApi.resources.cfnResources.cfnTables['Todo'].billingMode = - 'PAY_PER_REQUEST'; -``` - - - - - -### Example - Configure provisioned throughput for DynamoDB table or Global Secondary Index - -Override the default [ProvisionedThroughput](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-provisionedthroughput) provisioned for each `@model` table and its Global Secondary Indexes (GSI). - -Only valid if the "DynamoDBBillingMode" is set to "PROVISIONED" - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Post'].modelDDBTable.provisionedThroughput = { - readCapacityUnits: 5, - writeCapacityUnits: 5 - }; - - /** - * When billing mode is set to "PROVISIONED", it is necessary to specify `provisionedThroughput` for every Global Secondary Index (GSI) that exists in the table. - */ - - resources.models[ - 'Post' - ].modelDDBTable.globalSecondaryIndexes[0].provisionedThroughput = { - readCapacityUnits: 5, - writeCapacityUnits: 5 - }; -} -``` - -### Example - Enable point-in-time recovery for DynamoDB table - -Enable/disable [DynamoDB point-in-time recovery](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-pointintimerecoveryspecification.html) for each `@model` table. - - - - - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Post'].modelDDBTable.pointInTimeRecoverySpecification = { - pointInTimeRecoveryEnabled: true - }; -} -``` - - - - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); - -amplifyApi.resources.cfnResources.cfnTables[ - 'Todo' -].pointInTimeRecoverySpecification = { - pointInTimeRecoveryEnabled: true -}; -``` - - - - - -## Customize Amplify-generated resources for @searchable (OpenSearch) directive - -Apply all the overrides in the `override(...)` function. For example, to modify the OpenSearch instance count: - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { - ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, - instanceCount: 6 - }; -} -``` - -You can override the following @searchable directive resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [OpenSearchDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | The AppSync data source representing the OpenSearch integration | -| [OpenSearchAccessIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access OpenSearch domain | -| [OpenSearchAccessIAMRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access OpenSearch domain | -| [OpenSearchDomain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticsearch-domain.html) | OpenSearch domain containing the @searchable data | -| [OpenSearchStreamingLambdaIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to stream DynamoDB data to OpenSearch domain | -| [OpenSearchStreamingLambdaIAMRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to stream DynamoDB data to OpenSearch domain | -| [CloudwatchLogsAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for granting CloudWatch logs access | -| [OpenSearchStreamingLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | Lambda function to stream DynamoDB data to OpenSearch domain | -| [OpenSearchModelLambdaMapping](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html) | Event source mapping for DynamoDB table stream to Lambda function | - -### Example - Configure Runtime for Streaming Lambda - -You can define the runtime for the `@searchable` by setting the runtime value on the function object itself. This can be done to resolve a deprecated runtime in the event that you cannot upgrade your version of the Amplify CLI. - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchStreamingLambdaFunction.runtime = 'python3.9'; -} -``` - -### Example - Configure OpenSearch Streaming function name - -Override the name of the AWS Lambda searchable streaming function - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchStreamingLambdaFunction.FunctionName = - 'CustomFunctionName'; -} -``` - -### Example - Configure OpenSearch instance version - -Override the `elasticsearchVersion` in the OpenSearch domain created by `@searchable` - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.elasticsearchVersion = 'OpenSearch_1.3'; -} -``` - -### Example - Configure OpenSearch instance type - -Override the type of instance launched into the OpenSearch domain created by `@searchable` - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { - ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, - instanceType: 'm3.medium.elasticsearch' - }; -} -``` - -### Example - Configure OpenSearch instance count - -Override the number of instances launched into the OpenSearch domain created by `@searchable` - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { - ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, - instanceCount: 2 - }; -} -``` - -### Example - Configure OpenSearch EBS volume size - -Override the amount of disk space allocated to each instance in the OpenSearch domain created by `@searchable` - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.ebsOptions = { - ...resources.opensearch.OpenSearchDomain.ebsOptions, - volumeSize: 10 - }; -} -``` - -## Customize Amplify-generated resources for @predictions directive - -Apply all the overrides in the `override(...)` function. For example, to add a Path to IAM role that facilitates text translation: - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.predictions.TranslateDataSourceServiceRole.path = - '/my/organization/'; -} -``` - -You can override the following @predictions directive resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [RekognitionDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync HTTP data source to connect to Amazon Rekognition service | -| [RekognitionDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Amazon Rekognition | -| [TranslateDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync HTTP data source to connect to Amazon Translate service | -| [translateTextAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to connect to Amazon Translate | -| [LambdaDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync Lambda data source to connect to Amazon Polly | -| [LambdaDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Lambda function calling Amazon Polly | -| [LambdaDataSourceServiceRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for AppSync to connect to Lambda function calling Amazon Polly | -| [TranslateDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Amazon Translate | -| [predictionsLambdaIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role for Lambda function calling Amazon Polly | -| [predictionsLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | Lambda function calling Amazon Polly | -| [PredictionsLambdaAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for Lambda function to access Amazon Polly | -| [predictionsIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access s3 bucket used by @predictions | -| [PredictionsStorageAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access S3 bucket used by @predictions | -| [identifyTextAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to enable Identify Text | -| [identifyLabelsAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to enable Identify Text | - -## Place AppSync Resolvers in Custom-named Stacks - -If you have a particularly large GraphQL schema, you may run into issues with too many resources defined in a stack. The most common case where this happens is in the ConnectionStack which contains the resolvers for all of the relational directives in the schema. - -Creating a stack mapping does not create an additional root stack for the Amplify environment. All mapped stacks will still be placed under the existing Amplify environment root stack. To map a resolver to a different stack, update `/amplify/api//transform.conf.json` with a "StackMapping" block. The StackMapping defines a map from resolver logical ID to stack name. - -```json -{ - "Version": 5, - "ElasticsearchWarning": true, - "StackMapping": { - "": "Custom stack name" - } -} -``` - -The easiest way to determine a resolver logical ID is to run `amplify api gql-compile` and note the resolver logical ID in the list of Resources in the generated CloudFormation stack. Resolvers for model operations will be of the form `Resolver`. Resolvers for relational directives are of the form `Resolver`. - -### Example - -Given the following schema: - -```graphql -type Blog @model { - id: ID! - name: String! - posts: [Post] @hasMany -} - -type Post @model { - id: ID! - title: String! - content: String - blog: Blog @belongsTo -} -``` - -To map the CreatePostResolver and the relational resolvers to a stack named 'MyCustomStack', add the following in `transform.conf.json`: - -```json -"StackMapping": { - "CreatePostResolver": "MyCustomStack", - "BlogpostsResolver": "MyCustomStack", - "PostblogResolver": "MyCustomStack", -} -``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx deleted file mode 100644 index 4fbca0d68f6..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx +++ /dev/null @@ -1,141 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Evolving GraphQL schemas', - description: 'Evolve your GraphQL schema over time using the @mapsTo directive to retain tables while renaming models', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/schema-evolution/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -GraphQL schemas change over the lifecycle of a project. Sometimes these changes include breaking API changes. One such change is renaming a model in the schema, which Amplify offers a way to do while retaining the underlying records for that model. - -## Renaming models while retaining data - -Amplify supports renaming models in a GraphQL schema by using the `@mapsTo` directive. -Normally when renaming a model, Amplify will remove the underlying table for the model and create a new table with the new name. Once a table contains production data that cannot be deleted, `@mapsTo` can be used to specify the original name. Amplify will use the original name to ensure the underlying DynamoDB tables and other resources point to the existing data. -Other GraphQL API references to the model will use the new name. - -For example, a schema such as: -```graphql -type Todo @model { - id: ID! - title: String! -} -``` -becomes: -```graphql -type Task @model @mapsTo(name: "Todo") { - id: ID! - title: String! -} -``` -Amplify will update all of the GraphQL operations and types to use the name Task, but the Task model will point to the table that Todo was originally using. - - - -- `@mapsTo` cannot be used to point a model to an arbitrarily named table. It can only be used to point a renamed model to it's original name. -- `@mapsTo` can only be used on @model GraphQL types that are backed by a DynamoDB table. - - - -When renaming a model that has relationships with other models, Amplify will automatically map auto-generated foreign key fields to their original name. For example, given: - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany -} - -type Comment @model { - id: ID! - message: String! - # postCommentsId: String is an autogenerated field containing the foreign key -} -``` -Amplify will automatically add a field named `postCommentsId` to the Comment model that contains the foreign key of the Post. If the Post type is renamed to Article: - -```graphql -type Article @model @mapsTo(name: "Post") { - id: ID! - title: String! - comments: [Comment] @hasMany -} - -type Comment @model { - id: ID! - message: String! - # articleCommentsId: String is the new autogenerated field containing the foreign key -} -``` -The underlying table still contains records with `postCommentsId` as the foreign key field in the Comment table. In the new schema the foreign key field is now `articleCommentsId`. -Amplify is aware of this and will automatically map incoming requests with `articleCommentsId` to `postCommentsId` and do the reverse mapping for results. - -## Limitations - -### Constraint on relationship field names with @mapsTo - -In the above example if you renamed Comment to Reaction: -```graphql -type Post @model { - id: ID! - title: String! - comments: [Reaction] @hasMany # this field cannot be renamed and still access existing relationship data -} - -type Reaction @model @mapsTo(name: "Comment") { - id: ID! - message: String! - # autogenerated field postCommentsId: String contains the foreign key -} -``` -The `@hasMany` field `comments` cannot be renamed to `reactions`. This is because the foreign key field in Reaction uses the parent field name as part of the name. Amplify cannot determine the original name if this is changed. - -If a model is renamed multiple times, the value specified in `@mapsTo` must be the _original_ name, not the previous name. - -### Constraints to prevent naming conflicts - -A model in the schema cannot have the same name as the name another type maps to. For example, the following schema is invalid: -```graphql -type Article @model @mapsTo(name: "Post") { - id: ID! -} - -type Post @model { - id: ID! -} -``` -This schema would create a conflict on the Post table. - -Furthermore, even if the Post model is mapped to a different name, it is still not allowed. While this scenario technically does not pose a conflict, it is disallowed to prevent confusion. - -If you are accessing the table of a renamed model directly (ie. without going through AppSync), your access patterns will need to be aware that foreign key fields of records in the database are not renamed. See "How it works" below. - -## How it works -`@mapsTo` does not modify any existing tables or records. Instead, it points AppSync resolvers for the new name to the existing DynamoDB table for the original name. - -To handle renamed autogenerated foreign key fields when using relational directives, Amplify adds additional AppSync pipeline resolvers before and after fetching data from the database. -The resolvers before the fetch map any occurrence of the renamed foreign keys in the request to the original name. Then the resolvers after the fetch map any occurrence of the original name to the current name before returning the result. diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx deleted file mode 100644 index dc62cf0d07f..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx +++ /dev/null @@ -1,479 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Build search and aggregate queries', - description: 'Add authorization rules to your GraphQL schema to control access to your data.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/search-and-result-aggregations/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - -Add the `@searchable` directive to an `@model` type to enable OpenSearch-based data search and result aggregations. This gives you the ability to: - -- search for data using advanced filters, such as substring matching, wildcards, regex, `and`/`or`/`not` conditions -- get aggregation values, such as sum, average, min, max, terms -- retrieve total search result count -- sort the search results across one or multiple fields - -```graphql -type Student @model @searchable { - name: String - dateOfBirth: AWSDate - email: AWSEmail - examsCompleted: Int -} -``` - -> Once the `@searchable` directive is added, all new records added to the model are streamed to OpenSearch. To backfill existing data, see [Backfill OpenSearch index from DynamoDB table](/[platform]/build-a-backend/graphqlapi/troubleshooting/#backfill-opensearch-index-from-dynamodb-table). - -## Search and filter data - -Every model with a `@searchable` directive attached generates a new "search" GraphQL query to search and filter for records. The example above provides you the ability to search for "Student" records using a "searchStudents" query. - -The `filter` parameter allows you to filter for records based on their field values. - -```graphql -query SearchStudentsByEmail { - searchStudents(filter: { name: { eq: "Rene Brandel" } }) { - items { - id - name - email - } - } -} -``` - -In the example above, the search result consists of students with the name "Rene Brandel" - -### Supported search operations - -| Field type | Supported search operations | -| --- | --- | -| String | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp | -| Int | ne, gt, lt, gte, lte, eq, range | -| Float | ne, gt, lt, gte, lte, eq, range | -| Boolean | eq, ne | -| Enum | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp | - -### Nested search conditions (and, or, not) - -Use the filter parameter to pass a nested `and`/`or`/`not` condition. - -```graphql -query MyQuery { - searchStudents( - filter: { - name: { wildcard: "*Brandel" } - or: [{ dateOfBirth: { lt: "2000-01-01" } }, { email: { exists: true } }] - } - ) { - items { - id - name - email - dateOfBirth - } - } -} -``` - -By default, every operation in the filter properties is `and`ed. Use the `or` or `not` properties in the search query's `filter` parameter to override this behavior. - -The query above returns a "Student" if: - -- their name ends with "Brandel" -- `and` - - their date of birth is earlier than 2000-01-01 - - `or` - - their email exists. - -## Sort search results - -Use the `sort` parameter to sort your search results by a field in ascending or descending order. The `field` argument accepts any field available on the model. The `direction` accepts either `asc` or `desc`. - -```graphql -query SearchAndSort { - searchStudents( - filter: { name: { wildcard: "*Brandel" } } - sort: { direction: desc, field: name } - ) { - items { - name - id - } - } -} -``` - -In the example above, the search result is sorted based on their `name` in a `desc`ending order. - -### Sort search result over multiple fields - -To sort over multiple fields, provide array of sort conditions. When sorting over multiple fields, the sort conditions are applied in the `sort` array's order. - -```graphql -query SearchAndSort { - searchStudents( - filter: { name: { wildcard: "*Brandel" } } - sort: [ - { field: name, direction: desc } # Sort condition #1 - { field: dateOfBirth, direction: asc } # Sort condition #2 - ] - ) { - items { - id - name - dateOfBirth - } - } -} -``` - -In the example above, the search result is first sorted by `name` in a `desc`ending order and then by `dateOfBirth` in an `asc`ending order. - -## Paginate over search results - -By default, the search result page size is 100. To customize the page size modify the `limit` parameter. Query for the `nextToken` and use it in your subsequent pagination requests: - -``` -query MyQuery { - searchTodos(nextToken: "") { # Pass in your nextToken in query - items { - description - id - name - createdAt - } - nextToken # Next token to paginate on - } -} -``` - -## Total count of search results - -Add the `total` field in your query response to get the total count of search result hits. - -```graphql -query MyQuery { - searchStudents(filter: { name: { wildcard: "*Brandel" } }) { - items { - id - } - total # Specify to get total counts - } -} -``` - -In the example above, the response's `total` field contains the total search result count for "Students" whose name ends with "Brandel". Note: `total` is calculated based on all records, irrespective of pagination configurations. - -## Aggregate values for search result (minimum, maximum, average, sum, terms) - -Use the `aggregates` parameter to get aggregate values such as "minimum", "maximum", "average", and "sum" returned in the `aggregateItems` field. Note: `aggregates` are calculated based on all records, irrespective of pagination configurations. - - - - -Provide the `min` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchStudents( - aggregates: { - type: min # Specifies that you want the "min" value - field: examsCompleted # Specifies the field for the aggregate value - name: "minimumExams" # provides a name to reference in the response field - } - filter: { name: { wildcard: "Rene*" } } - ) { - aggregateItems { - name - result { - ... on SearchableAggregateScalarResult { - value - } - } - } - } -} -``` - -In the example above, the response includes the minimum value of "examsCompleted" for all Students whose name starts with "Rene". - -```graphql -{ - "data": { - "searchStudents": { - "aggregateItems": [{ - "name": "minimumExams", - "result": { - "value": 7 - } - }] - } - } -} -``` - - - - - -Provide the `max` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchStudents( - aggregates: { - type: max # Specifies that you want the "max" value - field: examsCompleted # Specifies the field for the aggregate value - name: "maximumExams" # provides a name to reference in the response field - } - filter: { name: { wildcard: "Rene*" } } - ) { - aggregateItems { - name - result { - ... on SearchableAggregateScalarResult { - value - } - } - } - } -} -``` - -In the example above, the response includes the maximum value of "examsCompleted" for all Students whose name starts with "Rene". - -```graphql -{ - "data": { - "searchStudents": { - "aggregateItems": [{ - "name": "maximumExams", - "result": { - "value": 28 - } - }] - } - } -} -``` - - - - - -Provide the `avg` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchStudents( - aggregates: { - type: avg # Specifies that you want the "avg" value - field: examsCompleted # Specifies the field for the aggregate value - name: "averageExams" # provides a name to reference in the response field - } - filter: { name: { wildcard: "Rene*" } } - ) { - aggregateItems { - name - result { - ... on SearchableAggregateScalarResult { - value - } - } - } - } -} -``` - -In the example above, the response includes the average value of "examsCompleted" for all Students whose name starts with "Rene". - -```graphql -{ - "data": { - "searchStudents": { - "aggregateItems": [{ - "name": "averageExams", - "result": { - "value": 17.3 - } - }] - } - } -} -``` - - - - - -Provide the `sum` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchStudents( - aggregates: { - type: sum # Specifies that you want the "sum" value - field: examsCompleted # Specifies the field for the aggregate value - name: "examsSum" # provides a name to reference in the response field - } - filter: { name: { wildcard: "Rene*" } } - ) { - aggregateItems { - name - result { - ... on SearchableAggregateScalarResult { - value - } - } - } - } -} -``` - -In the example above, the response includes the sum of all "examsCompleted" values for all Students whose name starts with "Rene". - -```graphql -{ - "data": { - "searchStudents": { - "aggregateItems": [{ - "name": "examsSum", - "result": { - "value": 392 - } - }] - } - } -} -``` - - - - -Provide the `terms` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchTodos( - aggregates: { field: description, type: terms, name: "descriptionTerms" } - ) { - aggregateItems { - result { - ... on SearchableAggregateBucketResult { - __typename - buckets { - doc_count - key - } - } - } - name - } - } -} -``` - -In the example above, the response includes the terms for the description and their count: - -```graphql -{ - "data": { - "searchTodos": { - "aggregateItems": [ - { - "result": { - "__typename": "SearchableAggregateBucketResult", - "buckets": [{ - "doc_count": 2, - "key": "Shopping list" - }, { - "doc_count": 1, - "key": "Me next todo" - }] - }, - "name": "descriptionTerms" - } - ] - } - } -} -``` - - - - -## Set up OpenSearch for production environments - -By default, Amplify CLI will configure a t2.small instance type. This is great for getting started and prototyping but not recommended to be used in the production environment per the [OpenSearch best practices](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp.html). - -To configure the OpenSearch instance type per environment: - -1. Run `amplify env add` to create a new environment (e.g. "prod") -2. Edit the `amplify/team-provider-info.json` file and set `OpenSearchInstanceType` to the instance type that works for your application - -```json -{ - "dev": { - "categories": { - "api": { - "": { - "OpenSearchInstanceType": "t2.small.elasticsearch" - } - } - } - }, - "prod": { - "categories": { - "api": { - "": { - "OpenSearchInstanceType": "t2.medium.elasticsearch" - } - } - } - } -} -``` - -3. Deploy your changes with `amplify push` - -Learn more about Amazon OpenSearch Service instance types [here](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-instance-types.html). - -## How it works - -The `@searchable` directive streams the data of an @model type to Amazon OpenSearch Service and configures search resolvers to query against OpenSearch. - -Type definition of the `@searchable` directive: - -```graphql -# Streams data from DynamoDB to OpenSearch and exposes search capabilities. -directive @searchable(queries: SearchableQueryMap) on OBJECT -input SearchableQueryMap { - search: String -} -``` diff --git a/src/pages/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx b/src/pages/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx deleted file mode 100644 index f1c12938cfc..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx +++ /dev/null @@ -1,92 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Troubleshooting', - description: 'Add authorization rules to your GraphQL schema to control access to your data.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/troubleshooting/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -## Deploying multiple index changes at once - -You can make `@index` updates on one "amplify push". Under the hood, Amplify CLI needs to locally sequence multiple individual deployments to your DynamoDB table because each Global Secondary Index (GSI), managed by `@index`, change requires time to create the new index. - -If your deployment fails locally when updating multiple GSIs, you'll have the ability to run: - -- `amplify push --iterative-rollback` to rollback the last-known-good state -- `amplify push --force` to rollback the last-known-good state and try redeploying your changes again using. - -```console -Attempting to mutate more than 1 global secondary index at the same time. -``` - -If you're running into the error above during `amplify push`, it is likely that you don't have this feature enabled. To enable multiple GSI updates, set the "enableIterativeGsiUpdates" feature flag to true in your `amplify/cli.json` file. - -## Backfill OpenSearch index from DynamoDB table - -When you add `@searchable` to a `@model` type with existing data, then you need to backfill the OpenSearch index. Download the following Python script to help you backfill your OpenSearch index: - -[DynamoDB to OpenSearch backfill script](https://raw.githubusercontent.com/aws-amplify/amplify-category-api/main/packages/graphql-elasticsearch-transformer/scripts/ddb_to_es.py) - -The script creates an event stream of your DynamoDB records and sends them to your OpenSearch Index. Execute the script with the following parameters to initiate the backfill: - -| Parameter | Description | Required | -| --- | --- | --- | -| `--rn` | DynamoDB table region. See [AWS Regions](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) for available options | Yes | -| `--tn` | DynamoDB table name. Format: `{@model type name}-{AppSync API ID}-{Amplify environment}` | Yes | -| `--lf` | ARN of the "DynamoDB to OpenSearch streaming" Lambda function. Format: `arn:aws:lambda:{region}:{AWS Account ID}:function:amplify-{Amplify project name}-{Amplify environment}-{Random string}-OpenSearchStreamingLambd-{Random string}` | Yes | -| `--esarn` | ARN of the DynamoDB table stream. Format: `arn:aws:dynamodb:{region}:{AWS Account ID}:table/{@model type name}-{AppSync API ID}-{Amplify environment}/stream/{Table creation date}` | Yes | -| `--ak` | AWS Access Key ID. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | -| `--sk` | AWS Secret Access Key. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | -| `--st` | AWS Session Token. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | - -In the example below, the `Post` table data in `us-west-2` gets backfilled in the OpenSearch index. - -```bash -python3 ddb_to_es.py \ - --rn 'us-west-2' \ # Use the region in which your table and OpenSearch domain reside - --tn 'Post-XXXX-dev' \ # Table name - --lf 'arn:aws:lambda:us-west-2:<...>:function:amplify-<...>-OpenSearchStreamingLambd-<...>' \ # Lambda function ARN, find the DynamoDB to OpenSearch streaming functions, copy entire ARN - --esarn 'arn:aws:dynamodb:us-west-2:<...>:table/Post-<...>/stream/2019-20-03T00:00:00.350' # Event source ARN, copy the full DynamoDB table ARN -``` - -## Index with multiple sort key fields - -When you add an `@index` directive with 2 or more sort key fields, you will need to backfill the new composite sort key for existing data. With `@index(sortKeyFields: ["status", "date"])`, you will need to backfill the `status#date` field with composite key values made up of each object's `status` and `date` fields joined by a `#`. You do not need to backfill data for `@index` directives with zero to one sort key field(s). - -## Maximum Global Secondary Index limit for DynamoDB table exceeded - -If you have increased the [soft limit of GSI per table](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html) in DynamoDB beyond 20, then iterative deployments will fail with the following warning. - -```console -DynamoDB
can have max of 20 GSIs. -To disable this check, use the --disable-gsi-limit-check option. -``` - -In order to disable this behavior, update your deploy scripts or ci commands to include the `--disable-gsi-limit-check` option to circumvent this validation during pushes. - -```bash -amplify push --disable-gsi-limit-check -``` diff --git a/src/pages/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx b/src/pages/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx deleted file mode 100644 index 9e98241c13f..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx +++ /dev/null @@ -1,286 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure REST API', - description: "Use Amplify CLI's simple guided workflow to add REST APIs to cloud-based web and mobile apps.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/restapi/configure-rest-api/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -The Amplify CLI provides a guided workflow to easily add, develop, test and manage REST APIs to access your AWS resources from your web and mobile applications. - -A REST API or HTTP endpoint will be composed by one or more paths. Eg: `/items`. Each path will use a Lambda function to handle HTTP requests and responses. Amplify CLI creates a single resource in Amazon API Gateway so you can handle all routes, HTTP Methods and paths, with a single Lambda function via a Lambda Proxy integration. HTTP proxy integrations forward all requests and responses directly through to your HTTP endpoint. - -Amplify CLI let's you choose either an existing Lambda function or create a new one. To kickstart your implementation, you can choose between the following templates: - -- Serverless ExpressJS function -- CRUD function for DynamoDB - -> Lambda templates use [serverless-express](https://github.com/awslabs/aws-serverless-express) and provide the building blocks to start your REST API development. - -> See the list of all [supported Lambda runtimes](/[platform]/build-a-backend/functions/set-up-function/). - -Amplify CLI allows you to restrict REST API access to - -- Only authenticated users; or -- Authenticated and Guest users -- User Pool Groups - -See a description of these user types below - -| User type | Description | -| --- | --- | -| Authenticated user | User needs to sign in to use the REST API | -| Guest user | User doesn't need to sign in to use the REST API | -| User Pool Group | User needs to sign in and belong to the User Pool Group to use the REST API | - -For each user type you can further specify what actions it has access to. - -| User type | Actions | Http Method | Authentication Provider | -| --- | --- | --- | --- | -| Authenticated user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | -| Guest user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | -| User Pool Group | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | - -REST APIs have support for [multiple environments](/[platform]/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. - -Because Amplify environments could be in separate AWS accounts, you cannot use the environment feature of API Gateway. Each Amplify environment will have a separate API Gateway resource associated with it. For example: - -```console -https://.execute-api.eu-west-2.amazonaws.com/dev/items -https://.execute-api.eu-west-2.amazonaws.com/prod/items -``` - -## Create a REST API - -Navigate into the root of a JavaScript, iOS, or Android project and run: - -```bash -amplify init -``` - -Follow the wizard to create a new app. After finishing the wizard run: - -```bash -amplify add api -``` - -Select the following options: - -- Please select from one of the below mentioned services: **REST** -- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsApi** -- Provide a path (e.g., /book/\{isbn}): **/items** - -This will be the configuration for `/items` path in API Gateway: - -```console -/ - |_ /items Main resource. Eg: /items - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser - |_ /\{proxy+} Proxy resource. Eg: /items/, /items/id, items/object/\{id} - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser -``` - -By default Amplify CLI creates a greedy path variable `/items/\{proxy+}` that catches all child resources for a path and forwards them to your Lambda. This will match all child routes including `/items/id` and `/items/object/id`. - -- Choose a Lambda source **Create a new Lambda function** -- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsLambda** -- Provide the AWS Lambda function name: **itemsLambda** -- Choose the runtime that you want to use: **NodeJS** -- Choose the function template that you want to use: **Serverless ExpressJS function** - -The Lambda function template **Serverless ExpressJS function** implements route handlers for `GET`, `POST`, `PUT` and `DELETE` Http Methods and paths for `/items` and `/items/*`. Some possible routes examples include: - -```console -GET /items List all items -GET /items/1 Load an item by id -POST /items Create an item -PUT /items Update an item -DELETE /items/1 Delete an item by id -``` - -- Do you want to access other resources in this project from your Lambda function? **No** -- Do you want to invoke this function on a recurring schedule? **No** -- Do you want to configure Lambda layers for this function? **No** -- Do you want to edit the local lambda function now? **Yes** - -> You are not going to change this template but it's good that you have it open as you follow the next steps. - -- Press enter to continue -- Restrict API access **Yes** -- Who should have access? **Authenticated and Guest users** -- What kind of access do you want for Authenticated users? **create, read, update, delete** -- What kind of access do you want for Guest users? **read** - -When configuration of your API is complete, the CLI displays a message confirming that you have configured local CLI metadata for this category. You can confirm this by running `amplify status`. Finally deploy your changes to the cloud: - -Amplify CLI restricts API access combining Amazon Cognito for authentication and AWS IAM (Identity and Access Management) for granting execution permissions on routes. - -- Do you want to add another path? **No** - -Deploy your new API. - -```bash -amplify push -``` - -At the end of this command you can take note of your new REST API url. - -```console -REST API endpoint: https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev -``` - -> REST APIs follow this pattern `https://{restapi-id}.execute-api.\{region}.amazonaws.com/\{environment}/\{path}`. - -Let's see an overview of all the resources created by Amplify CLI. - -```console -REST - |_ /items (path) - |_ itemsApi (Amazon API Gateway) - |_ itemsLambda (AWS Lambda) - |_ Logs (Amazon CloudWatch) -``` - -## Create REST API and restrict specific routes to specific User Pool Groups - -If your app uses User Pool Groups to manage different user types and would like to restrict access of specific routes to specific User Pool Groups. You can accomplish this by the following flow: - -- Create API route. -- Add API route handler function. -- Restrict-access to the API route to the User Pool Group. - -> The following example flow assumes the existence of two User Pool Groups : AdminUsers and GuestUsers for a Book store. The app would like to limit admin functionality like updating book records to the AdminUsers User Pool Group, while borrowing and returning books would be limited to the GuestUsers User Pool Group. -> -> - Path : /book/admin is restricted to AdminUsers and commands are handled by the bookAdminHandler lambda function -> - Path : /book/guest is restricted to GuestUsers and commands are handled by the bookGuestHandler lambda function - -```bash -amplify add api -$> ? Select from one of the below mentioned services: REST -$> ✔ Provide a friendly name for your resource to be used as a label for this category in the project: · mybookapi -$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/admin -$> ✔ Choose a Lambda source · Create a new Lambda function -$> ? Provide an AWS Lambda function name: bookAdminHandler -$> ? Choose the runtime that you want to use: NodeJS -$> ? Choose the function template that you want to use: Hello World -$> ? Do you want to configure advanced settings? No -$> ? Do you want to edit the local lambda function now? No -Successfully added resource bookAdminHandler locally. -$> ✔ Restrict API access? (Y/n) · yes -$> ✔ Restrict access by: · Individual Groups -$> ✔ Select groups: AdminUsers -$> ✔ What permissions do you want to grant to AdminUsers users? · create, read, update, delete -$> ✔ Do you want to add another path? (y/N) · yes -$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/guest -$> ✔ Choose a Lambda source · Create a new Lambda function -$> ? Provide an AWS Lambda function name: bookGuestHandler -$> ? Choose the runtime that you want to use: NodeJS -$> ? Choose the function template that you want to use: Hello World -$> ? Do you want to configure advanced settings? No -$> ? Do you want to edit the local lambda function now? No -Successfully added resource bookGuestHandler locally. -$> ✔ Restrict API access? (Y/n) · yes -$> ✔ Restrict access by: Individual Groups -$> ✔ Select groups: GuestUsers -$> ✔ What permissions do you want to grant to GuestUsers users? create, read, update -$> ✔ Do you want to add another path? (y/N) No -✅ Successfully added resource mybookapi locally -``` - -At the end of this command you can verify the routes and their respective User Pool Group restrictions in the `cli-inputs.json` file at the following path. - -```bash - /amplify/backend/api//cli-inputs.json -``` - -## REST endpoint that triggers new Lambda functions - -During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths. - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source Create a new Lambda function -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda -? Provide the AWS Lambda function name: itemsLambda -? Choose the function template that you want to use: - CRUD function for Amazon DynamoDB -❯ Serverless ExpressJS function -``` - -## REST endpoint that triggers existing Lambda functions - -During the CLI setup, you'll be guided through to use your own Lambda functions which you've initialized as a part of your CLI project using the `amplify add function` command. This would allow you to have custom logic in your Lambda function and not use the predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) templates generated by the CLI as in the examples above. - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source - Create a new Lambda function -❯ Use a Lambda function already added in the current Amplify project -``` - -## Set up a REST API with Amazon DynamoDB - -During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths with support for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command). - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source Create a new Lambda function -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda -? Provide the AWS Lambda function name: itemsLambda -? Choose the function template that you want to use: -❯ CRUD function for Amazon DynamoDB - Serverless ExpressJS function -``` - -In the example above with `/items` path, the following API will be created for you: - -1. GET /items/[ID] will return a list containing the item at the [ID]. If the item does not exist then an empty array is returned. -2. GET /items/object/[ID] will return a single item at [ID]. If the item does not exist then an empty object is returned. -3. PUT /items with your item in the request body will create or update the item. -4. POST /items with your item in the request body will create or update the item. -5. DELETE /items/object/[ID] will delete the item. - -When you have a sort key, you can append it to the end of the path, for example: `GET /items/object/[ID]/[SORT_KEY_ID]` diff --git a/src/pages/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx b/src/pages/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx deleted file mode 100644 index 2c41556c8c1..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx +++ /dev/null @@ -1,318 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Override Amplify-generated API Gateway resources', - description: "The 'amplify override api' command generates a developer-configurable 'overrides' TypeScript file which provides Amplify-generated API Gateway resources as CDK constructs. For example, developers can configure a custom description or the minimum compression size of their REST API.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/restapi/override-api-gateway/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -```bash -amplify override api -``` - -Run the command above to override Amplify-generated Amazon API Gateway resources. - -The command creates a new `overrides.ts` file under `amplify/backend/api//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). - -Apply all the overrides in the `override(...)` function. For example: - -```ts -// This file is used to override the REST API resources configuration -import { AmplifyApiRestResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiRestResourceStackTemplate) { - resources.restApi.description = "Custom description"; - resources.restApi.minimumCompressionSize = 1024; -} -``` - -To change a field on a particular path, use `resources.restApi.body.paths[\]`: - -```ts -export function override(resources: AmplifyApiRestResourceStackTemplate) { - // Change the default CORS response header Access-Control-Allow-Origin from "'*'" to the API's domain - resources.restApi.body.paths['/items'].options['x-amazon-apigateway-integration'].responses.default.responseParameters['method.response.header.Access-Control-Allow-Origin'] = { 'Fn::Sub': "'https://${ApiId}.execute-api.${AWS::Region}.amazonaws.com'" }; -} -``` - -You can override the following REST API resources that Amplify generates: - -
- -|Amplify-generated resource|Description| -|-|-| -|[restApi](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html)|The Amazon API Gateway REST API created by `amplify add api`| -|[deploymentResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-deployment.html)|The deployment resource that deploys the REST API above to a stage.| -|[policies](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|User pool group-related IAM policy. Example: `resources.policies["/items"].groups["Admin"]` - - -
- -## Authorize API requests with Cognito User Pools - -Amazon Cognito User Pools is a common service to use alongside API Gateway when -adding user Sign-Up and Sign-In to your application. If your application needs to -interact with other AWS services such as S3 on behalf of the user who invoked -an endpoint, you will need to use IAM credentials with Cognito Identity Pools. - -Amplify CLI does not support Cognito User Pool authorizers out-of-the-box. To -implement this functionality, you must override your REST API and add a Cognito -User Pool authorizer yourself by adding the following code into the -`override(...)` function, in order. - -First, assuming the Cognito User Pool you would like to use as an authorizer is -the Auth resource configured with your Amplify Project, create a parameter that resolves -to its User Pool ARN: - -```ts -// Replace the following with your Auth resource name -const authResourceName = ""; -const userPoolArnParameter = "AuthCognitoUserPoolArn"; - -// Add a parameter to your Cloud Formation Template for the User Pool's ID -resources.addCfnParameter({ - type: "String", - description: "The ARN of an existing Cognito User Pool to authorize requests", - default: "NONE", - }, - userPoolArnParameter, - { "Fn::GetAtt": [`auth${authResourceName}`, "Outputs.UserPoolArn"], } -); -``` - - - -Make sure to replace `` with the name of your auth resource. -This is the name of the folder in `amplify/backend/auth` that was created when -you added an Auth resource to your Amplify project. - - - -Now, define a REST API authorizer with Cognito User Pools using the OpenAPI extension, [`x-amazon-apigateway-authorizer`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html). This change will be applied by modifying the security definition of your REST API: - -```ts -// Create the authorizer using the AuthCognitoUserPoolArn parameter defined above -resources.restApi.addPropertyOverride("Body.securityDefinitions", { - Cognito: { - type: "apiKey", - name: "Authorization", - in: "header", - "x-amazon-apigateway-authtype": "cognito_user_pools", - "x-amazon-apigateway-authorizer": { - type: "cognito_user_pools", - providerARNs: [ - { - 'Fn::Join': ['', [{ Ref: userPoolArnParameter }]], - }, - ], - }, - }, -}); -``` - -Finally, update the security methods for all of the paths in your REST API to -use this new Cognito User Pool authorizer. You also add the `Authorization` header -as a parameter on incoming requests for these paths as a place for users to provide -their Cognito User ID Tokens. - -```ts -// For every path in your REST API -for (const path in resources.restApi.body.paths) { - // Add the Authorization header as a parameter to requests - resources.restApi.addPropertyOverride( - `Body.paths.${path}.x-amazon-apigateway-any-method.parameters`, - [ - ...resources.restApi.body.paths[path]["x-amazon-apigateway-any-method"] - .parameters, - { - name: "Authorization", - in: "header", - required: false, - type: "string", - }, - ] - ); - // Use your new Cognito User Pool authorizer for security - resources.restApi.addPropertyOverride( - `Body.paths.${path}.x-amazon-apigateway-any-method.security`, - [ { Cognito: [], }, ] - ); -} -``` - - - -Note that you can add more advanced logic to only use the Cognito User Pool authorizer -with some paths or methods. - - - -When performing requests to your REST API, make sure to add the `Authorization` -header with an ID Token provided by Cognito. - -Requests to endpoints are now populated with information from Cognito about the -user who is invoking the -endpoint, and you can reuse the verified ID Token in your endpoint resolvers to assume -the identity of the user for accessing other services like AWS AppSync or S3. - -## Authorize API requests with Lambda authorizer - -While Amplify CLI does not support Lambda authorizers natively out-of-box, you can implement this functionality by overriding your REST API resources. The following steps will walk you through how to create token-based Lambda authorizer. - -First, you need to have a Lambda authorizer function with required [authorization logic](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html#api-gateway-lambda-authorizer-lambda-function-create) in your Amplify project to use it as an authorizer. Refer to the steps to [set up a function](/[platform]/build-a-backend/functions/set-up-function/) using `amplify add function` - -After running `amplify override api`, add the following code to `override(...)` function. -Initially, create a parameter that resolves to Lambda Function ARN - -```ts -// Replace the following with your Function resource name -const functionResourcename = ""; -const functionArnParameter = "FunctionArn"; - -// Adding parameter to your Cloud Formation Template for Authorizer function arn -resources.addCfnParameter( - { - type: "String", - description: "The ARN of an existing Lambda Function to authorize requests", - default: "NONE", - }, - functionArnParameter, - { "Fn::GetAtt": [`function${functionResourcename}`, "Outputs.Arn"], } -); -``` - - - - -Make sure to replace `` with the name of your function resource. This is the name of the folder in `amplify/backend/function` that was created when you added an function resource to your Amplify project. - - - -Next, define the Lambda authorizer using the OpenAPI extension, [`x-amazon-apigateway-authorizer`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html). This change will be applied by modifying the security definition of your REST API: - -```ts -// Create the authorizer using the functionArnParameter parameter defined above -resources.restApi.addPropertyOverride("Body.securityDefinitions", { - Lambda: { - type: "apiKey", - name: "Authorization", - in: "header", - "x-amazon-apigateway-authtype": "oauth2", - "x-amazon-apigateway-authorizer": { - type: "token", - authorizerUri: - { - 'Fn::Join': [ - '', - [ - "arn:aws:apigateway:", - { Ref: 'AWS::Region' }, - ":lambda:path/2015-03-31/functions/", - { Ref: functionArnParameter }, - "/invocations" - ] - ], - }, - authorizerResultTtlInSeconds: 0 - }, - }, -}); -``` - -As API Gateway needs permission to invoke the Authorizer lambda function, add resource based policy to the function using following code: - -```ts -// Adding Resource Based policy to Lambda authorizer function -resources.addCfnResource( - { - type: "AWS::Lambda::Permission", - properties: { - Action: "lambda:InvokeFunction", - FunctionName: {Ref: functionArnParameter}, - Principal: "apigateway.amazonaws.com", - SourceArn:{ - "Fn::Join": [ - "", - [ - "arn:aws:execute-api:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "" - }, - "/*/*" - ] - ] - } - } - }, - "LambdaAuthorizerResourceBasedPolicy" -); - -``` - - -Make sure to replace `` with the name of your Rest API resource. This is the name of the folder in `amplify/backend/api` that was created when you added an Rest API resource to your Amplify project. - - - -Finally, update the security methods for all of the paths in your REST API to use this new Lambda authorizer. You can also add the Authorization header as a parameter on incoming requests for these paths as a place for users to provide their Auth token. - -```ts -for (const path in resources.restApi.body.paths) { - // Add the Authorization header as a parameter to requests - resources.restApi.addPropertyOverride( - `Body.paths.${path}.x-amazon-apigateway-any-method.parameters`, - [ - ...resources.restApi.body.paths[path]["x-amazon-apigateway-any-method"] - .parameters, - { - name: "Authorization", - in: "header", - required: false, - type: "string", - }, - ] - ); - // Use your new Lambda authorizer for security - resources.restApi.addPropertyOverride( - `Body.paths.${path}.x-amazon-apigateway-any-method.security`, - [ { Lambda: [], }, ] - ); -} -``` - -Note that you can add more advanced logic to only use the Lambda authorizer with some paths or methods. - - -When performing requests to your REST API, make sure to add the Authorization header with an token required by Lambda authorizer function. diff --git a/src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx b/src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx deleted file mode 100644 index 6effbae55fd..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/restapi/test-api/index.mdx +++ /dev/null @@ -1,178 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Test the REST API', - description: 'Learn how you can test the REST API from the terminal, with Amplify Mock, or with the API Gateway console.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/restapi/test-api/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -## Test the API from the terminal - -If Guest users have access to your REST API you can test it from the terminal using Curl. - -[Curl](https://github.com/curl/curl) is a command-line tool that lets you transfer data to and from a server using various protocols. - -> Curl is available in many distributions including Mac, Windows and Linux. Follow the install instructions in the [docs](https://curl.haxx.se/docs/install.html). - - - - -### GET method example - -```bash -curl https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos -``` - -### POST method example - -```bash -curl -H "Content-Type: application/json" -d '{"name":"todo-1"}' https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos -``` - - - - -### GET method example - -```bash -curl https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos -``` - -### POST method example - -```bash -curl -H "Content-Type: application/json" -d {\"name\":\"todo-1\"} https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos -``` - - - - -> Important! Testing methods using production endpoints may result in changes to resources that cannot be undone. - -## Test the API with Amplify Mock - -Amplify CLI allows you to quickly test your REST APIs by using the `amplify mock function` command. - -Let's test your new REST API using the route below with HTTP Method `GET` and path `/todos?limit=10` which includes a `limit` query string parameter. - -```console -GET /todos?limit=10 -``` - -> Important! Testing methods using production endpoints may result in changes to resources that cannot be undone. - -Before you continue, edit the file at `{project}/amplify/backend/function/todosLambda/src/event.json` and replace its content for the purpose of the test. - -```json -{ - "httpMethod": "GET", - "path": "/todos", - "queryStringParameters": { - "limit": "10" - } -} -``` - -Make sure you have saved the changes and run - -```bash -amplify mock function todosLambda -``` - -Select the following options: - -- Provide the path to the event JSON object relative to `{project}/amplify/backend/function/todosLambda` __src/event.json__ - -```console -Starting execution... -EVENT: {"httpMethod":"GET","path":"/todos","queryStringParameters":{"limit":"10"}} -App started - -Result: -{"statusCode":200,"body":"{\"success\":\"get call succeed!\",\"url\":\"/todos?limit=10\"}","headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","content-type":"application/json; charset=utf-8","content-length":"55", "date":"Tue, 18 Aug 2020 16:50:53 GMT","connection":"close"},"isBase64Encoded":false} -Finished execution. -``` - -## Test the API with API Gateway console - -Let's test your new REST API using the route below with HTTP Method `GET` and path `/todos?limit=10` which includes a `limit` query string parameter. - -```console -GET /todos?limit=10 -``` - -> Important! Testing methods with the API Gateway console may result in changes to resources that cannot be undone. - -- Sign in to the API Gateway console at [https://console.aws.amazon.com/apigateway](https://console.aws.amazon.com/apigateway). -- Choose the `todosApi` REST API. -- In the Resources pane, choose the method you want to test. Pick `ANY` right under `/todos`. - -```console -/ - |_ /todos Main resource. Eg: /todos - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser - |_ /{proxy+} Proxy resource. Eg: /todos/, /todos/id, todos/object/{id} - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser -``` - -- In the Method Execution pane, in the Client box, choose TEST. Choose the `GET` method. Add `limit=10` to the Query String `{todos}` field. - -- Choose Test to run the test for `GET /todos?limit=10`. The following information will be displayed: request, status, latency, response body, response headers and logs. - -```bash -Request: /todos?limit=10 -Status: 200 -Latency: 139 ms -Response Body -{ - "success": "get call succeed!", - "url": "/todos?limit=10" -} -Response Headers -{"access-control-allow-origin":"*","date":"Tue, 18 Aug 2020 17:36:14 GMT","content-length":"55","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","x-powered-by":"Express","content-type":"application/json; charset=utf-8","connection":"close"} -Logs -Execution log for request 4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f -Tue Aug 18 17:36:14 UTC 2020 : Starting execution for request: 4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f -Tue Aug 18 17:36:14 UTC 2020 : HTTP Method: GET, Resource Path: /todos -Tue Aug 18 17:36:14 UTC 2020 : Method request path: {} -Tue Aug 18 17:36:14 UTC 2020 : Method request query string: {limit=10} -Tue Aug 18 17:36:14 UTC 2020 : Method request headers: {} -Tue Aug 18 17:36:14 UTC 2020 : Method request body before transformations: -Tue Aug 18 17:36:14 UTC 2020 : Endpoint request URI: https://lambda.eu-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-west-2:664371068953:function:expressLambda-dev/invocations -Tue Aug 18 17:36:14 UTC 2020 : Endpoint request headers: { X-Amz-Date=20200818T173614Z, X-Amz-Source-Arn=arn:aws:execute-api:eu-west-2:664371068953:s3zmw6fqy5/test-invoke-stage/GET/todos, Accept=application/json, User-Agent=AmazonAPIGateway_s3zmw6fqy5, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDEaCWV1LXdlc3QtMiJGMEQCIC3KIeR66WhaCBw+eJ+GPhF7y4hz9xC2nN+ARb7T3psyAiBdsoaD9yMfiw2dHWjQM5x7vM11XmToNSGu64mckUQdzSq0AwgaEAEaDDU0NDM4ODgxNjY2MyIMIzObNbCd6QtYwb0IKpEDpHXEzkM2OYq7JfL0U/WbF09KNamodfnifRYwZd/GNOwykykc/zHiU9X0XZPRd+QTnQe/9eoy8DaxBkDgRzQQjTThQWJWadtcfjryTLRKpVeo1UueL+f6DTUDf+URjb0P9CN1gPm+ntZD3LSyAXGwACKG7YMA5/HyeEk [TRUNCATED] -Tue Aug 18 17:36:14 UTC 2020 : Endpoint request body after transformations: {"resource":"/todos","path":"/todos","httpMethod":"GET","headers":null,"multiValueHeaders":null,"queryStringParameters":{"limit":"10"},"multiValueQueryStringParameters":{"limit":["10"]},"pathParameters":null,"stageVariables":null,"requestContext":{"resourcePath":"/todos","httpMethod":"GET","requestTime":"18/Aug/2020:17:36:14 +0000","path":"/todos","accountId":"EXAMPLE_ID","protocol":"HTTP/1.1","stage":"test-invoke-stage","domainPrefix":"testPrefix","requestTimeEpoch":1597772174890,"requestId":"4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f","identity":{"cognitoIdentityPoolId":null,"cognitoIdentityId":null,"apiKey":"test-invoke-api-key","principalOrgId":null,"cognitoAuthenticationType":null,"userArn":"arn:aws:iam::664371068953:root","apiKeyId":"test-invoke-api-key-id","userAgent":"aws-internal/3 aws-sdk-java/1.11.820 Linux/4.9.217-0.1.ac.205.84.332.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.252-b09 java/1.8.0_252 v [TRUNCATED] -Tue Aug 18 17:36:14 UTC 2020 : Sending request to https://lambda.eu-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-west-2:664371068953:function:expressLambda-dev/invocations -Tue Aug 18 17:36:15 UTC 2020 : Received response. Status: 200, Integration latency: 137 ms -Tue Aug 18 17:36:15 UTC 2020 : Endpoint response headers: {Date=Tue, 18 Aug 2020 17:36:15 GMT, Content-Type=application/json, Content-Length=443, Connection=keep-alive, sampled=0} -Tue Aug 18 17:36:15 UTC 2020 : Endpoint response body before transformations: {"statusCode":200,"body":"{\"success\":\"get call succeed!\",\"url\":\"/todos?limit=10\"}","headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","content-type":"application/json; charset=utf-8","content-length":"55","date":"Tue, 18 Aug 2020 17:36:14 GMT","connection":"close"},"isBase64Encoded":false} -Tue Aug 18 17:36:15 UTC 2020 : Method response body after transformations: {"success":"get call succeed!","url":"/todos?limit=10"} -Tue Aug 18 17:36:15 UTC 2020 : Method response headers: {x-powered-by=Express, access-control-allow-origin=*, access-control-allow-headers=Origin, X-Requested-With, Content-Type, Accept, content-type=application/json; charset=utf-8, content-length=55, date=Tue, 18 Aug 2020 17:36:14 GMT, connection=close, Sampled=0} -Tue Aug 18 17:36:15 UTC 2020 : Successfully completed execution -Tue Aug 18 17:36:15 UTC 2020 : Method completed with status: 200 -``` diff --git a/src/pages/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx deleted file mode 100644 index f3f8404bb43..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx +++ /dev/null @@ -1,186 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure Storage', - description: 'Use Amplify CLI to create and manage cloud-connected file and data storage for your app.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/storage/configure-storage/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -Amplify CLI's `storage` category enables you to create and manage cloud-connected file & data storage. Use the `storage` category when you need to store: - -1. app content (images, audio, video etc.) in an public, protected or private storage bucket or -2. app data in a NoSQL database and access it with a REST API + Lambda - -## Setup a new storage resource - -You can setup a new storage resource by running the following command: - -```bash -amplify add storage -``` - -Amplify allows you to either setup a app content storage (images, audio, video etc.) backed by Amazon S3 or a NoSQL database backed by Amazon DynamoDB. - -### Adding S3 storage - -```console -? Please select from one of the below mentioned services: -> Content (Images, audio, video, etc.) - NoSQL Database -? Please provide a friendly name for your resource that will be used to label this category in the project: -> mystorage -? Please provide bucket name: -> mybucket -``` - -Follow the prompts to provide your content storage's resource name. - - - -The storage resource created by Amplify CLI has retention enabled which prevents accidental deletion or loss of data. Hence, running `amplify remove storage` will not delete the storage resource and will need to be manually deleted on the AWS console. - - - -### S3 Access permissions - -Next, configure the access permissions for your Amazon S3 bucket. If you haven't set up the `auth` category already, the Amplify CLI will guide you through a workflow to enable the auth category. - -```console -? Restrict access by? -> Auth/Guest Users - Individual Groups - Both - Learn more -``` - - - -**NOTE:** Run `amplify update storage` to change the access permissions for your Amazon S3 bucket - - - -#### Auth/Guest Users access - -Select `Auth/Guest Users`, to scope permissions based on an individual user's authentication status. On the next question you'll be able to select if only authenticated users can access resources, or authenticated and guest users: - -``` -? Who should have access: -❯ Auth users only - Auth and guest users -``` - -Then you'll be prompted to set the access scopes for your authenticated and (if selected prior) unauthenticated users. - -```console -? What kind of access do you want for Authenticated users? -> ◉ create/update - ◯ read - ◯ delete -? What kind of access do you want for Guest users? - ◯ create/update -> ◉ read - ◯ delete -``` - -Granting access to authenticated users will allow the specified CRUD operations on objects in the bucket starting with the prefix `/public/`, `/protected/{cognito:sub}/`, and `/private/{cognito:sub}/`. `{cognito:sub}` is the sub of the Cognito identity of the authenticated user. - -Granting access to guest users will allow the specified CRUD operations on objects in the bucket starting with the prefix `/public/`. - -#### Individual Group access - -Select `Individual Groups` to scope access permissions based on [Cognito User Groups](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/) - -```console -? Select groups: - ◉ EMPLOYEE -> ◉ MANAGER -``` - -Then select the CRUD operations you want to permit for each selected Cognito user group - -```console -? What kind of access do you want for EMPLOYEE users? - ◯ create/update -> ◉ read - ◯ delete -? What kind of access do you want for MANAGER users? - ◉ create/update - ◯ read -> ◉ delete -``` - -> Note: CRUD operations selected here will apply to ALL objects in the bucket, not just objects under a particular prefix. - -> Note: If you combine `Auth/Guest user access` and `Individual Group access`, users who are members of a group will only be granted the permissions of the group, and not the authenticated user permissions. - -### S3 Lambda trigger - -Lastly, you have the option of configuring a Lambda function that can execute in response to S3 events. - -```console -? Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) -``` - -Learn more about this workflow [here](/[platform]/tools/cli/usage/lambda-triggers/#s3-lambda-triggers). - -That's it! Your content storage is set up! Head to the [library's storage docs](/[platform]/build-a-backend/storage/set-up-storage/) to integrate this newly created S3 bucket into your app. - -### Adding a NoSQL database - -```console -? Please select from one of the below mentioned services: -> Content (Images, audio, video, etc.) - NoSQL Database -? Please provide a friendly name for your resource that will be used to label this category in the project: -> dynamo2e1dc4eb -? Please provide table name: -> dynamo2e1dc4eb -``` - -Follow the prompts to provide your NoSQL Database's resource name. Next, you'll go through a table-creation wizard. First, you'll create the columns of your table: - -```console -You can now add columns to the table. - -? What would you like to name this column: id -? Please choose the data type: string -? Would you like to add another column? Yes -``` - -Then, you'll need to specify your indexes. The concept behind "indexes", "partition key", "sort key" and "global secondary indexes" are explained in-depth [here](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey). - -```console -? Please choose partition key for the table: id -? Do you want to add a sort key to your table? (y/N) -``` - -```console -? Do you want to add a Lambda Trigger for your Table? (y/N) -``` - -If you want to configure a Lambda trigger for your Table, you'll have the option. Learn more about this workflow [here](/[platform]/tools/cli/usage/lambda-triggers/#dynamodb-lambda-triggers). - -That's it! Your NoSQL Database is set up! diff --git a/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx deleted file mode 100644 index 8e3f7a1ed37..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/storage/import/index.mdx +++ /dev/null @@ -1,254 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Import an S3 bucket or DynamoDB table', - description: 'Learn how you can import existing S3 bucket or DynamoDB table resources as a storage resource for other Amplify categories (API, Function, and more) using the Amplify CLI.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/storage/import/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -Import an existing S3 bucket or DynamoDB tables into your Amplify project. Get started by running `amplify import storage` command to search for & import an S3 or DynamoDB resource from your account. - -```bash -amplify import storage -``` - -Make sure to run `amplify push` to complete the import process and deploy this backend change to the cloud. - -The `amplify import storage` command will: - -- automatically populate your Amplify Library configuration files (aws-exports.js, amplifyconfiguration.json) with your chosen S3 bucket information -- provide your designated S3 bucket or DynamoDB table as a storage mechanism for all storage-dependent categories (API, Function, Predictions, and more) -- enable Lambda functions to access the chosen S3 or DynamoDB resource if you permit it - -This feature is particularly useful if you're trying to: - -- enable Amplify categories (such as API and Function) to access your existing storage resources; -- incrementally adopt Amplify for your application stack; -- independently manage S3 and DynamoDB resources while working with Amplify. - -> Note: Amplify does not manage the lifecycle of an imported resource. - -## Import an existing S3 bucket - -Select the "S3 bucket - Content (Images, audio, video, etc.)" option when you've run `amplify import storage`. - -Run `amplify push` to complete the import procedure. - -> Amplify projects are limited to exactly one S3 bucket. - -### Connect to an imported S3 bucket with Amplify Libraries - -By default, Amplify Libraries assumes that S3 buckets are configured with the following access patterns: - -- `public/` - Accessible by all users of your app -- `protected/{user_identity_id}/` - Readable by all users, but writable only by the creating user -- `private/{user_identity_id}/` - Only accessible for the individual user - -It is highly recommended to review your S3 bucket's CORS settings. Review the [recommendation guide here](/[platform]/build-a-backend/storage/set-up-storage/#amazon-s3-bucket-cors-policy-setup). - -### Configuring IAM role to use Amplify-recommended policies - -If you're using an imported S3 bucket with an imported Cognito resource, then you'll need to update the policy of your Cognito Identity Pool's authenticated and unauthenticated role. Create new __managed policies__ (not *inline policies*) for these roles with the following statements: - -> Make sure to replace `{YOUR_S3_BUCKET_NAME}` with your S3 bucket's name. - -#### Unauthenticated role policies - -- IAM policy statement for `public/`: - -```json -{ - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/public/*" - ], - "Effect": "Allow" -} -``` - -- IAM policy statement for read access to `public/`, `protected/`, and `private/`: - -```json -{ - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/*" - ], - "Effect": "Allow" -}, -{ - "Condition": { - "StringLike": { - "s3:prefix": [ - "public/", - "public/*", - "protected/", - "protected/*" - ] - } - }, - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}" - ], - "Effect": "Allow" -} -``` - -#### Authenticated role policies - -- IAM policy statement for `public/`: - -```json -{ - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/public/*" - ], - "Effect": "Allow" -} -``` - -- IAM policy statement for `protected/`: - -```json -{ - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/${cognito-identity.amazonaws.com:sub}/*" - ], - "Effect": "Allow" -} -``` - -- IAM policy statement for `private/`: - -```json -{ - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/private/${cognito-identity.amazonaws.com:sub}/*" - ], - "Effect": "Allow" -} -``` - -- IAM policy statement for read access to `public/`, `protected/`, and `private/`: - -```json -{ - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/*" - ], - "Effect": "Allow" -}, -{ - "Condition": { - "StringLike": { - "s3:prefix": [ - "public/", - "public/*", - "protected/", - "protected/*", - "private/${cognito-identity.amazonaws.com:sub}/", - "private/${cognito-identity.amazonaws.com:sub}/*" - ] - } - }, - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}" - ], - "Effect": "Allow" -} -``` - -## Import an existing DynamoDB table - -Select the "DynamoDB table - NoSQL Database" option when you've run `amplify import storage`. In order to successfully import your DynamoDB table, your DynamoDB table needs to be located within the same region as your Amplify project. - -Run `amplify push` to complete the import procedure. - -> Amplify projects can contain multiple DynamoDB tables. - -## Multi-environment support - -When you create a new environment through `amplify env add`, Amplify CLI will assume by default that you're managing your app's storage resources outside of an Amplify project. You'll be asked to either import a different S3 bucket or DynamoDB tables or maintain the same imported storage resource. - -If you want to have Amplify manage your storage resources in a new environment, run `amplify remove storage` to unlink the imported storage resources and `amplify add storage` to create new Amplify-managed S3 buckets and DynamoDB tables in the new environment. - -## Unlink an existing S3 bucket or DynamoDB table - -In order to unlink your existing Storage resource run `amplify remove storage`. This will only unlink the S3 bucket or DynamoDB table referenced from the Amplify project. It will not delete the S3 bucket or DynamoDB table itself. - -Run `amplify push` to complete the unlink procedure. - -## Configure environment variables for Amplify Hosting builds - -In order to successfully build your application with Amplify Hosting add the following environment variables to your build environment: - -|Environment Variable|Description|Imported Resource|Required -|-|-|-|-| -|AMPLIFY_STORAGE_BUCKET_NAME|The name of the S3 bucket being imported for storage|S3 bucket|Yes -|AMPLIFY_STORAGE_REGION|The AWS region in which the S3 bucket or the DynamoDB table is located (for example: us-east-1, us-west-2, etc.)|S3 bucket or DynamoDB table|Yes -|AMPLIFY_STORAGE_TABLES|The name of the storage resource and DynamoDB table being imported for storage|DynamoDB table|Yes - - -The value of the AMPLIFY_STORAGE_TABLES environment variable needs to be in a json format such as: - -``` -{ - "STORAGE_RESOURCE_NAME_1":"DDB_TABLE_NAME_1", - "STORAGE_RESOURCE_NAME_2":"DDB_TABLE_NAME_2" // If you are importing more than a single DynamoDB table -} -``` -The values for the `STORAGE_RESOURCE_NAME` and `DDB_TABLE_NAME` fields can be retrieved from the amplify/team-provider-info.json file. - diff --git a/src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx b/src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx deleted file mode 100644 index 8de05ae2a74..00000000000 --- a/src/pages/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx +++ /dev/null @@ -1,110 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Modify Amplify-generated resources', - description: "The 'amplify override storage' command generates a developer-configurable 'overrides' TypeScript file which provides Amplify-generated S3 and DynamoDB resources as CDK constructs. For example, developers can run the 'amplify override storage' command to enable Transfer Acceleration for Amplify-generated S3 buckets.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/storage/modify-amplify-generated-resources/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -Amplify Storage uses underlying AWS services and resources such as S3 and DynamoDB. You can customize these underlying resources for your specific use case. - -```bash -amplify override storage -``` - -Run the command above to override Amplify-generated storage resources including the S3 bucket, DynamoDB tables, and more. - -The command creates a new `overrides.ts` file under `amplify/backend/storage//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). - -## Customize Amplify-generated S3 resources - -Apply all the overrides in the `override(...)` function. For example to enable versioning on your S3 bucket: - -```ts -import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyS3ResourceTemplate) { - resources.s3Bucket.versioningConfiguration = { - status: 'Enabled' - }; -} -``` - -You can override the following S3-related resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [s3Bucket](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html) | The S3 bucket that Amplify generates for file storage upon `amplify add storage` | -| [s3AuthPublicPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `public/*` prefix | -| [s3AuthProtectedPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `protected/*` prefix | -| [s3AuthPrivatePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `private/*` prefix | -| [s3AuthUploadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `uploads/*` prefix | -| [s3AuthReadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' read access | -| [s3GuestPublicPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' write access to `public/*` prefix | -| [s3GuestUploadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' write access to `uploads/*` prefix | -| [s3GuestReadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' read access | - -
- -For example, you can use `amplify override storage` to add additional PUT and GET access IAM policy statements to the S3 bucket's default public Auth policy: - -```ts -import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyS3ResourceTemplate) { - resources.s3AuthPublicPolicy.policyDocument.Statement = [ - ...resources.s3AuthPublicPolicy.policyDocument.Statement, - { - Effect: 'Allow', - Action: ['s3:PutObject', 's3:PutObjectAcl', 's3:GetObject'], - Resource: '' - } - ]; -} -``` - -Please refer to the [IAM documentation](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html) for more information on actions, resources, and condition keys for Amazon S3. - -## Customize Amplify-generated DynamoDB tables - -Apply all the overrides in the `override(...)` function. For example to enable time-to-live specification on your DynamoDB table: - -```ts -import { AmplifyDDBResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyDDBResourceTemplate) { - resources.dynamoDBTable.timeToLiveSpecification = { - attributeName: 'ttl', - enabled: true - }; -} -``` - -You can override the following DynamoDB resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [dynamoDBTable](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The DynamoDB table that Amplify creates upon `amplify add storage` | diff --git a/src/pages/gen2/start/mobile-support/index.mdx b/src/pages/gen2/start/mobile-support/index.mdx deleted file mode 100644 index c6e49d3786c..00000000000 --- a/src/pages/gen2/start/mobile-support/index.mdx +++ /dev/null @@ -1,1267 +0,0 @@ -export const meta = { - title: 'Mobile support', - description: - 'Learn more about working with Android, iOS, Flutter, and React Native apps.' -}; - -export function getStaticProps(context) { - return { - props: { - meta - } - }; -} - -Amplify Gen 2 enables backend code sharing between web and mobile apps. The initial focus is on a TypeScript-first experience optimized for web developers. Mobile developers can also leverage the Gen 2 capabilities to build a unified backend for Android, iOS, Flutter and React Native apps. Expect ongoing improvements to streamline mobile workflows as we gather feedback from developers. - -## Prerequisites - -Before you get started, make sure you have the following installed: - -- [Node.js](https://nodejs.org/) v18.17 or later -- [npm](https://www.npmjs.com/) v9 or later -- [git](https://git-scm.com/) v2.14.1 or later -- You will also need to [create an AWS Account](https://portal.aws.amazon.com/billing/signup). Note that AWS Amplify is part of the [AWS Free Tier](https://aws.amazon.com/amplify/pricing/). -- Configure your AWS account to use with Amplify [instructions](/gen2/start/account-setup/). - -## Build a mobile app - -Here is how you can build a To Do application on each platform with CRUD operations: - - - - - - -You need to have [Android Studio and SDK](https://developer.android.com/studio) installed on your machine. - - -**Open Android Studio.** Select **+ Create New Project.** - -![Shows the Android studio welcome window](/images/lib/getting-started/android/set-up-android-studio-welcome.png) - -In **Select a Project Template**, select **Empty Activity** or **Empty Compose Activity**. Press **Next**. - -![Shows Android studio new project window](/images/lib/getting-started/android/set-up-android-studio-select-project-template.png) - -- Enter _MyAmplifyApp_ in the **Name** field -- Select either _Java_ or _Kotlin_ from the **Language** dropdown menu -- Select _API 24: Android 7.0 (Nougat)_ from the **Minimum SDK** dropdown menu -- Press **Finish** - -![Shows Android studio configure project window](/images/lib/getting-started/android/set-up-android-studio-configure-your-project.png) - - - This guide will expect you to use Kotlin DSL for Gradle. If you are using - Groovy DSL, you will need to make some changes to the Gradle files. - - -### Create Amplify Project - -The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. - -```bash -npm create amplify@latest -? Where should we create your project? (.) # press enter -``` - -Running this command will scaffold a lightweight Amplify project in your current project with the following files added: - -```text -├── amplify/ -│ ├── auth/ -│ │ └── resource.ts -│ ├── data/ -│ │ └── resource.ts -│ ├── backend.ts -│ └── package.json -├── node_modules/ -├── .gitignore -├── package-lock.json -├── package.json -└── tsconfig.json -``` - -### Running Local Development Environment - -Amplify gen2 provides a new way to develop applications. Now you are able to run your application with a sandbox environment and generate the configuration files for your application. To run your application with a sandbox environment, you can run the following command: - - - Be sure to add a "raw" folder under app/src/main/res directory if it doesn't - exist. - - -```bash -npx amplify sandbox --config-format=json-mobile --config-out-dir=app/src/main/res/raw -``` - -### Adding Authentication - -After the Amplify creation process, you can see a resource.ts file in the amplify/auth folder. This file contains the configuration for the authentication resource. The base code will enable the authentication with the default configuration. You can change the configuration based on your needs. For more information about the configuration, you can check the [documentation](/gen2/build-a-backend/auth/enable-sign-up/). - -```typescript -import { defineAuth } from '@aws-amplify/backend'; - -export const auth = defineAuth({ - loginWith: { - email: true - } -}); -``` - -After you have configured the authentication resource, you can use the Amplify UI libraries to run your authentication flow. Amplify UI is a collection of accessible, themeable, performant ui components that can connect directly to the Amplify resources. - -To use the Amplify UI libraries, you need to add the following dependencies to your app/build.gradle file: - -Be sure to have compileSdk version as 34 or higher. - -```kotlin -dependencies { - implementation("androidx.compose.material3:material3:1.1.0") - implementation("com.amplifyframework.ui:authenticator:1.1.0") - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") -} -``` - -Afterwards create a `MyAmplifyApp` class that extends `Application` and add the following code: - -```kotlin -import android.app.Application -import android.util.Log -import com.amplifyframework.AmplifyException -import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin -import com.amplifyframework.core.Amplify - -class MyAmplifyApp: Application() { - override fun onCreate() { - super.onCreate() - - try { - Amplify.addPlugin(AWSCognitoAuthPlugin()) - Amplify.configure(applicationContext) - Log.i("MyAmplifyApp", "Initialized Amplify") - } catch (error: AmplifyException) { - Log.e("MyAmplifyApp", "Could not initialize Amplify", error) - } - } -} -``` - -Next call this class in your `AndroidManifest.xml` file: - -```xml - -``` - -Lastly update your MainActivity.kt file to use the Amplify UI components: - -```kotlin -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.amplifyframework.core.Amplify -import com.amplifyframework.ui.authenticator.ui.Authenticator -import .ui.theme.MyAmplifyAppTheme - -class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - MyAmplifyAppTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Authenticator { state -> - Column { - Text( - text = "Hello ${state.user.username}!", - ) - Button(onClick = { - Amplify.Auth.signOut { } - }) { - Text(text = "Sign Out") - } - } - } - } - } - } - } -} -``` - -Now if you run the application on the Android emulator, you should see the authentication flow working. - - - -### Adding GraphQL API - -After the Amplify creation process, you can see a resource.ts file in the amplify/data folder. This file contains the configuration for the GraphQL API resource. - -The default code will create a Todo model with content and isDone property. The authorization rules below specify that owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records. - -```typescript -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - isDone: a.boolean() - }) - .authorization(allow => [allow.owner()]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'userPool' - } -}); -``` - -To generate the model classes out of GraphQL schema, you can run the following command: - -```bash -npx amplify generate graphql-client-code --format=modelgen --model-target=java --out=app/src/main/java -``` - -Now you can see that the model classes are generated under app/src/main/java/com/amplifyframework/datastore/generated/model folder. - - - The generated models are in Java but do not worry Java and Kotlin are - interoperable. You can use the generated models in your Kotlin code. - - -For using GraphQL API, you need to add the following dependencies to your app/build.gradle file: - -```kotlin -dependencies { - implementation("com.amplifyframework:core:2.14.4") - implementation("com.amplifyframework:aws-api:2.14.4") -} -``` - -Afterwards open the `MyAmplifyApp` class and add the following line before the `configure` call: - -```kotlin -Amplify.addPlugin(AWSApiPlugin()) -``` - -Now it is time to update the UI code a bit. Update the `MainActivity` class with the following code: - -```kotlin - -class MainActivity : ComponentActivity() { - - private val todoList = mutableStateListOf() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - MyApplicationTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Authenticator { _ -> - Scaffold( - floatingActionButton = { - FloatingActionButton( - onClick = { - val date = Date() - val offsetMillis = TimeZone.getDefault().getOffset(date.time).toLong() - val offsetSeconds = TimeUnit.MILLISECONDS.toSeconds(offsetMillis).toInt() - val temporalDateTime = Temporal.DateTime(date, offsetSeconds) - val todo = Todo.builder() - .createdAt(temporalDateTime) - .updatedAt(temporalDateTime) - .content("My random todo ${System.currentTimeMillis()}") - .isDone(false) - .build() - - Amplify.API.mutate( - ModelMutation.create(todo), - { - Log.i("MyAmplifyApp", "Added Todo with id: ${it.data.id}") - todoList.add(todo) - }, - { Log.e("MyAmplifyApp", "Create failed", it) } - ) - }, - ) { - Icon(Icons.Filled.Add, "Add a random todo.") - } - } - ) { - Column(modifier = Modifier.padding(it)) { - Button(onClick = { - Amplify.Auth.signOut { } - }) { - Text(text = "Sign Out") - } - Text(text = "The list of items will come here.") - } - } - } - } - } - } - } -} -``` - -The `onClick` function of the `FloatingActionButton` will create a random Todo item. Now it is time to add a logic to see the added items. - -First let's add a `TodoScreen` composable function: - -```kotlin -@Composable -fun TodoScreen( - todoList: SnapshotStateList, - onItemUpdated: (Todo) -> Unit, - onItemDeleted: (Todo) -> Unit, -) { - LazyColumn { - todoList.forEach { todo -> - item { - Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox( - checked = todo.isDone, - onCheckedChange = { } - ) - Text(todo.content) - } - } - } - } -} -``` - -Next let's update the `MainActivity` class to use the `TodoScreen` composable function. Update the `Authenticator` usage with the following code: - -```kotlin -Authenticator(modifier = Modifier.padding(it)) { _ -> - Column { - Button(onClick = { - Amplify.Auth.signOut { } - }) { - Text(text = "Sign Out") - } - if (todoList.isEmpty()) - Text(text = "The list is empty.\nAdd some items by clicking the Floating Action Button.") - else - TodoScreen( - todoList, - onItemUpdated = { todo -> - val foundItem = - todoList.firstOrNull { it.id == todo.id } - if (foundItem != null) { - val index = todoList.indexOf(foundItem) - todoList.removeAt(index) - Log.i("updated", todo.toString()) - todoList.add(index, todo) - } - }, - ) { todo -> todoList.remove(todo) } - } -} -``` - -Now add a `todoList` variable to the `MainActivity` class before the `onCreate` call and call the `refreshItems` function before the `setContent` call: - -```kotlin -override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - refreshItems() -... -} -``` - -Lastly create a function to fetch the items. Add the following code to the `MainActivity` class: - -```kotlin -private fun refreshItems() { - Amplify.API.query( - ModelQuery.list(Todo::class.java), - { response -> - val items = response.data - items.forEach { todo -> - val foundItem = todoList.firstOrNull { it.id == todo.id } - if (foundItem != null) { - val index = todoList.indexOf(foundItem) - todoList.removeAt(index) - todoList.add(index, todo) - } else { - todoList.add(todo) - } - } - Log.i("MyAmplifyApp", "Queried items: $items") - }, - { Log.e("MyAmplifyApp", "Query failure", it) } - ) -} -``` - -Now let's update and delete the items. For update, add the following code to the `onCheckedChange` method of the `Checkbox` widget: - -```kotlin -val newTodo = todo.copyOfBuilder().isDone(it).build() -Amplify.API.mutate( - ModelMutation.update(newTodo), - { - Log.i("MyAmplifyApp", "Updated Todo with id: ${todo.id}") - onItemUpdated(newTodo) - }, - { Log.e("MyAmplifyApp", "Update failed") } -) -``` - -For deleting add a long click behavior with the `Modifier.combinedClickable` modifier. To add it, update the Row composable call in the `TodoScreen` composable function with the following code: - -```kotlin -Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.combinedClickable( - onClick = { - val newTodo = todo.copyOfBuilder().isDone(!todo.isDone).build() - Amplify.API.mutate( - ModelMutation.update(newTodo), - { - Log.i("MyAmplifyApp", "Updated Todo with id: ${todo.id}") - onItemUpdated(newTodo) - }, - { Log.e("MyAmplifyApp", "Update failed") } - ) - }, - onLongClick = { - Amplify.API.mutate( - ModelMutation.delete(todo), - { - Log.i("MyAmplifyApp", "Deleted Todo with id: ${todo.id}") - onItemDeleted(todo) - }, - { Log.e("MyAmplifyApp", "Delete failed") } - ) - }, - ) -) -``` - -With the click, we update the checkbox but with the long click we remove it. Now if you run the application you should see the following flow. - - - -You can terminate the sandbox environment now to clean up the project. - -### Publishing changes to cloud - -For publishing the changes to cloud, you need to create a remote git repository. For a detailed guide, you can follow the link [here](/gen2/start/quickstart/#create-remote-git-repository). - - - - - - - You can follow the [official documentation](https://flutter.dev/docs/get-started/install) to install Flutter on your machine and check the [editor documentation](https://docs.flutter.dev/get-started/editor) for setting up your editor. - - - Once you have installed Flutter, you can create a new Flutter project using the following command: - - ```bash - flutter create my_amplify_app - ``` - -### Create Amplify Project - -The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. - -```bash -npm create amplify@latest -? Where should we create your project? (.) # press enter -``` - -Running this command will scaffold a lightweight Amplify project in your current project with the following files added: - -```text -├── amplify/ -│ ├── auth/ -│ │ └── resource.ts -│ ├── data/ -│ │ └── resource.ts -│ ├── backend.ts -│ └── package.json -├── node_modules/ -├── .gitignore -├── package-lock.json -├── package.json -└── tsconfig.json -``` - -### Running Local Development Environment - -Amplify gen2 provides a new way to develop applications. Now you are able to run your application with a sandbox environment and generate the configuration files for your application. To run your application with a sandbox environment, you can run the following command: - -```bash -npx amplify sandbox --config-format dart --config-out-dir lib -``` - -### Adding Authentication - -After the Amplify creation process, you can see a resource.ts file in the amplify/auth folder. This file contains the configuration for the authentication resource. The base code will enable the authentication with the default configuration. You can change the configuration based on your needs. For more information about the configuration, you can check the [documentation](/gen2/build-a-backend/auth/enable-sign-up/). - -```typescript -import { defineAuth } from '@aws-amplify/backend'; - -export const auth = defineAuth({ - loginWith: { - email: true - } -}); -``` - -After you have configured the authentication resource, you can use the Amplify UI libraries to run your authentication flow. Amplify UI is a collection of accessible, themeable, performant ui components that can connect directly to the Amplify resources. - -To use the Amplify UI libraries, you need to add the following dependencies to your pubspec.yaml file: - -```yaml -dependencies: - amplify_flutter: ^2.0.0 - amplify_auth_cognito: ^2.0.0 - amplify_authenticator: ^2.0.0 -``` - -You will add: - -- `amplify_flutter` to connect your application with the Amplify resources. -- `amplify_auth_cognito` to connect your application with the Amplify Cognito resources. -- `amplify_authenticator` to use the Amplify UI components. - -After adding the dependencies, you need to run the following command to install the dependencies: - -```bash -flutter pub get -``` - -Lastly update your main.dart file to use the Amplify UI components: - -```dart title="main.dart" -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:amplify_authenticator/amplify_authenticator.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; - -import 'amplifyconfiguration.dart'; - -Future main() async { - try { - WidgetsFlutterBinding.ensureInitialized(); - await _configureAmplify(); - runApp(const MyApp()); - } on AmplifyException catch (e) { - runApp(Text("Error configuring Amplify: ${e.message}")); - } -} - -Future _configureAmplify() async { - try { - await Amplify.addPlugin(AmplifyAuthCognito()); - await Amplify.configure(amplifyConfig); - safePrint('Successfully configured'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - @override - Widget build(BuildContext context) { - return Authenticator( - child: MaterialApp( - builder: Authenticator.builder(), - home: const Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SignOutButton(), - Text('TODO Application'), - ], - ), - ), - ), - ), - ); - } -} -``` - -The **Authenticator** widget provides an authentication flow according to the resources that has been configured. If you run the application now (on Flutter you can run your applications on Web, Desktop and Mobile), you can see the authentication flow working. - - - -### Adding Data - -After the Amplify creation process, you can see a resource.ts file in the amplify/data folder. This file contains the configuration for the GraphQL API resource. - -The default code will create a Todo model with content and isDone property. The authorization rules below specify that owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records. - -```typescript -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - isDone: a.boolean(), - }) - .authorization(allow => [allow.owner()]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "userPool", - }, -}); -``` - -To generate the model classes out of GraphQL schema, you can run the following command: - -```bash -npx amplify generate graphql-client-code --format modelgen --model-target dart --out lib/models -``` - -This will generate dart models under lib/models folder. - -For using GraphQL API, you need to add the following dependencies to your pubspec.yaml file: - -```yaml -dependencies: - amplify_api: ^2.0.0 -``` - -You will add `amplify_api` to connect your application with the Amplify API. - -After adding the dependencies, update the `_configureAmplify` method in your main.dart file to use the Amplify API: - -```dart title="main.dart" -Future _configureAmplify() async { - try { - await Amplify.addPlugins( - [ - AmplifyAuthCognito(), - AmplifyAPI(modelProvider: ModelProvider.instance), - ], - ); - await Amplify.configure(amplifyConfig); - safePrint('Successfully configured'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } -} -``` - -Next create a new widget called `TodoScreen` and add the following code: - -```dart title="main.dart" - -class TodoScreen extends StatefulWidget { - const TodoScreen({super.key}); - - @override - State createState() => _TodoScreenState(); -} - -class _TodoScreenState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton.extended( - label: const Text('Add Random Todo'), - onPressed: () async { - final newTodo = Todo( - id: uuid(), - content: "Random Todo ${DateTime.now().toIso8601String()}", - isDone: false, - createdAt: TemporalDateTime(DateTime.now()), - updatedAt: TemporalDateTime(DateTime.now()), - ); - final request = ModelMutations.create(newTodo); - final response = await Amplify.API.mutate(request: request).response; - if (response.hasErrors) { - safePrint('Creating Todo failed.'); - } else { - safePrint('Creating Todo successful.'); - } - _refreshTodos(); - }, - ), - body: const Placeholder(), - ); - } -} -``` - -This will create a random Todo every time a user clicks on the floating action button. You can see the `ModelMutations.create` method is used to create a new Todo. - -And update the `MyApp` widget in your **main.dart** file like the following: - -```dart title="main.dart" -class MyApp extends StatelessWidget { - const MyApp({super.key}); - @override - Widget build(BuildContext context) { - return Authenticator( - child: MaterialApp( - builder: Authenticator.builder(), - home: const SafeArea( - child: Scaffold( - body: Column( - children: [ - SignOutButton(), - Expanded(child: TodoScreen()), - ], - ), - ), - ), - ), - ); - } -} -``` - -Next add a `_todos` list in `_TodoScreenState` to add the results from the API and call the refresh function: - -```dart title="main.dart" -List _todos = []; - -@override -void initState() { - super.initState(); - _refreshTodos(); -} -``` - - -and create a new function called `_refreshTodos`: - -```dart title="main.dart" -Future _refreshTodos() async { - try { - final request = ModelQueries.list(Todo.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items; - if (response.hasErrors) { - safePrint('errors: ${response.errors}'); - return; - } - setState(() { - _todos = todos!.whereType().toList(); - }); - } on ApiException catch (e) { - safePrint('Query failed: $e'); - } -} -``` - -and update the body with the following code: - -```dart title="main.dart" -body: _todos.isEmpty == true - ? const Center( - child: Text( - "The list is empty.\nAdd some items by clicking the floating action button.", - textAlign: TextAlign.center, - ), - ) - : ListView.builder( - itemCount: _todos.length, - itemBuilder: (context, index) { - final todo = _todos[index]; - return Dismissible( - key: UniqueKey(), - confirmDismiss: (direction) async { - return false; - }, - child: CheckboxListTile.adaptive( - value: todo.isDone, - title: Text(todo.content!), - onChanged: (isChecked) async { }, - ), - ); - }, - ), -``` - -Now let's add a update and delete functionality. - -For update, add the following code to the `onChanged` method of the `CheckboxListTile.adaptive` widget: - -```dart title="main.dart" -final request = ModelMutations.update( - todo.copyWith(isDone: isChecked!), -); -final response = - await Amplify.API.mutate(request: request).response; -if (response.hasErrors) { - safePrint('Updating Todo failed. ${response.errors}'); -} else { - safePrint('Updating Todo successful.'); - await _refreshTodos(); -} -``` - -This will call the `ModelMutations.update` method to update the Todo with a copied/updated version of the todo item. So now the checkbox will get an update as well. - -For delete functionality, add the following code to the `confirmDismiss` method of the `Dismissible` widget: - -```dart title="main.dart" -if (direction == DismissDirection.endToStart) { - final request = ModelMutations.delete(todo); - final response = - await Amplify.API.mutate(request: request).response; - if (response.hasErrors) { - safePrint('Updating Todo failed. ${response.errors}'); - } else { - safePrint('Updating Todo successful.'); - await _refreshTodos(); - return true; - } -} -return false; -``` - -This will delete the Todo item when the user swipes the item from right to left. Now if you run the application you should see the following flow. - - - -You can terminate the sandbox environment now to clean up the project. - -### Publishing changes to cloud - -For publishing the changes to cloud, you need to create a remote git repository. For a detailed guide, you can follow the link [here](/gen2/start/quickstart/#create-remote-git-repository). - - - - - - - You need to have [Xcode and Developer - Tooling](https://developer.apple.com/xcode/) installed on your machine. - - -Open Xcode and select **Create New Project...** - -![Shows the Xcode starter video to start project](/images/lib/getting-started/ios/set-up-swift-1.png) - -In the next step select the **App** template under **iOS**. Click on next. - -![Shows the template of apps for iOS](/images/lib/getting-started/ios/set-up-swift-2.png) - -Next steps are: - -- Adding a _Product Name_ (e.g. MyAmplifyApp) -- Select a _Team_ (e.g. None) -- Select a _Organization Identifier_ (e.g. com.example) -- Select **SwiftUI** an _Interface_. -- Press **Next** - -![Shows the project details dialog](/images/lib/getting-started/ios/set-up-swift-3.png) - -Now you should have your project created. - -![Shows the base project for SwiftUI](/images/lib/getting-started/ios/set-up-swift-4.png) - -### Create Amplify Project - -The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. - -```bash -npm create amplify@latest -? Where should we create your project? (.) # press enter -``` - -Running this command will scaffold a lightweight Amplify project in your current project with the following files added: - -```text -├── amplify/ -│ ├── auth/ -│ │ └── resource.ts -│ ├── data/ -│ │ └── resource.ts -│ ├── backend.ts -│ └── package.json -├── node_modules/ -├── .gitignore -├── package-lock.json -├── package.json -└── tsconfig.json -``` - -### Running Local Development Environment - -Amplify gen2 provides a new way to develop applications. Now you are able to run your application with a sandbox environment and generate the configuration files for your application. To run your application with a sandbox environment, you can run the following command: - -```bash -npx amplify sandbox --config-format=json-mobile -``` - -Once the sandbox environment is running, you would also generate the configuration files for your application. However, Xcode won't be able to recognize them. For recognizing the files, you need to drag and drop the generated files to your project. - - - -### Adding Authentication - -After the Amplify creation process, you can see a resource.ts file in the amplify/auth folder. This file contains the configuration for the authentication resource. The base code will enable the authentication with the default configuration. You can change the configuration based on your needs. For more information about the configuration, you can check the [documentation](/gen2/build-a-backend/auth/enable-sign-up/). - -```typescript -import { defineAuth } from '@aws-amplify/backend'; - -export const auth = defineAuth({ - loginWith: { - email: true - } -}); -``` - -After you have configured the authentication resource, you can use the Amplify UI libraries to run your authentication flow. Amplify UI is a collection of accessible, themeable, performant ui components that can connect directly to the Amplify resources. - -Open your project in Xcode and select **File > Add Packages...** and add the following dependencies: - -![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-5.png) - -- Amplify Library for Swift: Enter its GitHub URL (https://github.com/aws-amplify/amplify-swift), select **Up to Next Major Version** and click **Add Package Dependencies...** and select the following libraries: - - - Amplify - - AWSCognitoAuthPlugin - -![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-6.png) - -- Amplify UI Swift - Authenticator: Enter its GitHub URL (https://github.com/aws-amplify/amplify-ui-swift-authenticator), select **Up to Next Major Version** and click **Add Package Dependencies...** and select the following libraries: - - Authenticator - -![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-7.png) - -Now update the `MyAmplifyAppApp` class with the following code: - -```swift -import Amplify -import Authenticator -import AWSCognitoAuthPlugin -import SwiftUI - -@main -struct MyApp: App { - init() { - do { - try Amplify.add(plugin: AWSCognitoAuthPlugin()) - try Amplify.configure() - } catch { - print("Unable to configure Amplify \(error)") - } - } - - var body: some Scene { - WindowGroup { - Authenticator { state in - VStack { - Button("Sign out") { - Task { - await state.signOut() - } - } - Spacer() - Button(action: { - Task { - await createTodo() - await listTodos() - } - }) { - HStack { - Text("Add a New Todo") - Image(systemName: "plus") - } - } - .accessibilityLabel("New Todo") - } - } - } - } -} -``` - -This will add the authentication flow by using the Authenticator component and add a sign out button with a create todo button. - -If you run the application now, you can see that the authentication flow is working. - - - -### Adding Data - -After the Amplify creation process, you can see a resource.ts file in the amplify/data folder. This file contains the configuration for the GraphQL API resource. - -The default code will create a Todo model with content and isDone property. The authorization rules below specify that owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records. - -```typescript -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - isDone: a.boolean() - }) - .authorization(allow => [allow.owner()]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'userPool' - } -}); -``` - -To generate the model classes out of GraphQL schema, you can run the following command: - -```bash -npx amplify generate graphql-client-code --format=modelgen -``` - -Move the generated files to your project. You can do this by dragging and dropping the files to your project. - -![Shows the drag and drop phase](/images/lib/getting-started/ios/set-up-swift-8.png) - -Once you are done, add the API dependencies to your project. Select **File > Add Package Dependencies...** and add the `AWSAPIPlugin`. - -![Shows the Amplify API library for Swift selected](/images/lib/getting-started/ios/set-up-swift-9.png) - -Next, update the `init` part of your `MyAmplifyAppApp.swift` file with the following code: - -```swift -init() { - do { - try Amplify.add(plugin: AWSCognitoAuthPlugin()) - try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) - try Amplify.configure() - } catch { - print("Unable to configure Amplify \(error)") - } -} -``` - -Now it is time to update the UI code a bit. Create a `createTodo` function in the `MyAmplifyAppApp.swift` file with the following code: - -```swift -func createTodo() async { - let creationTime = Temporal.DateTime.now() - let todo = Todo( - content: "Random Todo \(creationTime)", - isDone: false, - createdAt: creationTime, - updatedAt: creationTime - ) - do { - let result = try await Amplify.API.mutate(request: .create(todo)) - switch result { - case .success(let todo): - print("Successfully created todo: \(todo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to create todo: ", error) - } catch { - print("Unexpected error: \(error)") - } -} -``` - -The code above will create a random todo with the current time. - -Next create a `listTodos` function in the `MyAmplifyAppApp.swift` file with the following code to have the logic of listing the items: - -```swift -func listTodos() async { - let request = GraphQLRequest.list(Todo.self) - do { - let result = try await Amplify.API.query(request: request) - switch result { - case .success(let todos): - self.todos = todos.elements - print("Successfully retrieved list of todos: \(todos)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to query list of todos: ", error) - } catch { - print("Unexpected error: \(error)") - } -} -``` - -This will assign the value of the fetched todos into a State object. Be sure to create it before the `body` property: - -```swift -@State var todos: [Todo] = [] -``` - -Now let's update the UI code to show the todos. Update the `VStack` in the `MyAmplifyAppApp.swift` file with the following code: - -```swift -VStack { - Button("Sign out") { - Task { - await state.signOut() - } - } - List(todos, id: \.id) { todo in - Text(todo.content!) - } - Button(action: { - Task { - await createTodo() - await listTodos() - } - }) { - HStack { - Text("Add a New Todo") - Image(systemName: "plus") - } - } - .accessibilityLabel("New Todo") -}.task { - await listTodos() -} -``` - - - Throughout the Swift implementation, the async/await pattern has been used and - for using it easily, we take advantage of the Task structure. For more - information about the Task structure, you can check the - [documentation](https://developer.apple.com/documentation/swift/task). - - -The code above will fetch the todos once the VStack is shown. It will also create a todo and update the todo list each time a todo is created. - -Next step is to update and delete the todos. For that, create `updateTodo` and `deleteTodo` functions in the `MyAmplifyAppApp.swift` file with the following code: - -```swift -func deleteTodo(todo: Todo) async { - do { - let result = try await Amplify.API.mutate(request: .delete(todo)) - switch result { - case .success(let todo): - print("Successfully deleted todo: \(todo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to deleted todo: ", error) - } catch { - print("Unexpected error: \(error)") - } -} - -func updateTodo(todo: Todo) async { - do { - let result = try await Amplify.API.mutate(request: .update(todo)) - switch result { - case .success(let todo): - print("Successfully updated todo: \(todo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to updated todo: ", error) - } catch { - print("Unexpected error: \(error)") - } -} -``` - -Lastly, update the `List` in the `MyAmplifyAppApp.swift` file with the following code: - -```swift -List(todos, id: \.id) { todo in - @State var isToggled = todo.isDone! - Toggle(isOn: $isToggled - ) { - Text(todo.content!) - }.onTapGesture { - var updatedTodo = todos.first {$0.id == todo.id}! - updatedTodo.isDone = !todo.isDone! - Task { - await updateTodo(todo: updatedTodo) - await listTodos() - } - } - .onChange(of: isToggled) { oldValue, newValue in - var updatedTodo = todos.first {$0.id == todo.id}! - updatedTodo.isDone = newValue - Task { - await updateTodo(todo: updatedTodo) - await listTodos() - } - } - .toggleStyle(.switch) - .onLongPressGesture { - Task { - await deleteTodo(todo: todo) - await listTodos() - } - } -} -``` - -This will update the UI to show a toggle to update the todo and a long press gesture to delete the todo. Now if you run the application you should see the following flow. - - - -You can terminate the sandbox environment now to clean up the project. - -### Publishing changes to cloud - -For publishing the changes to cloud, you need to create a remote git repository. For a detailed guide, you can follow the link [here](/gen2/start/quickstart/#create-remote-git-repository). - - - - From ec5321c36be2c7b8f76d624261f28973295a803c Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 13 May 2024 14:20:13 -0700 Subject: [PATCH 53/88] Flutter v2 gen2 remove pages (#7579) * chore: remove pages ported from gen1 current to gen1 prev --- src/directory/directory.mjs | 124 +- .../storage/storagepath/index.mdx | 125 -- .../auth/admin-actions/index.mdx | 365 ----- .../auth/existing-resources-no-cli/index.mdx | 37 - .../auth/import-existing-resources/index.mdx | 115 -- .../auth/override-cognito/index.mdx | 189 --- .../auth/user-group-management/index.mdx | 117 -- .../existing-resources/cdk/index.mdx | 836 ----------- .../existing-resources/cli/index.mdx | 1014 ------------- .../existing-resources/index.mdx | 33 - .../functions/build-options/index.mdx | 141 -- .../functions/configure-options/index.mdx | 114 -- .../functions/environment-variables/index.mdx | 86 -- .../functions/graphql-from-lambda/index.mdx | 504 ------- .../prev/build-a-backend/functions/index.mdx | 40 - .../functions/layers/index.mdx | 229 --- .../functions/secrets/index.mdx | 105 -- .../functions/set-up-function/index.mdx | 161 --- .../batch-put-custom-resolver/index.mdx | 155 -- .../graphqlapi/best-practice/index.mdx | 40 - .../query-with-sorting/index.mdx | 136 -- .../warehouse-management/index.mdx | 556 -------- .../client-code-generation/index.mdx | 245 ---- .../index.mdx | 1018 ------------- .../index.mdx | 266 ---- .../custom-business-logic/index.mdx | 958 ------------- .../customize-authorization-rules/index.mdx | 937 ------------ .../graphqlapi/data-modeling/index.mdx | 1266 ----------------- .../index.mdx | 504 ------- .../graphqlapi/schema-evolution/index.mdx | 145 -- .../search-and-result-aggregations/index.mdx | 483 ------- .../graphqlapi/troubleshooting/index.mdx | 96 -- .../restapi/configure-rest-api/index.mdx | 290 ---- .../restapi/override-api-gateway/index.mdx | 325 ----- .../restapi/test-api/index.mdx | 185 --- .../storage/configure-storage/index.mdx | 190 --- .../build-a-backend/storage/import/index.mdx | 260 ---- .../index.mdx | 114 -- 38 files changed, 2 insertions(+), 12502 deletions(-) delete mode 100644 src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index f7874f4a12f..1cba4a93edf 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -295,9 +295,6 @@ export const directory = { { path: 'src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx' }, - { - path: 'src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx' - }, { path: 'src/pages/[platform]/build-a-backend/storage/authorization/index.mdx' }, @@ -1688,9 +1685,6 @@ export const directory = { { path: 'src/pages/gen1/[platform]/build-a-backend/troubleshooting/migrate-from-javascript-v5-to-v6/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx' - }, { path: 'src/pages/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/index.mdx' } @@ -2172,10 +2166,10 @@ export const directory = { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/sign-in-with-web-ui/index.mdx' }, { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/multi-step-sign-in/index.mdx' + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/enable-guest-access/index.mdx' }, { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/enable-guest-access/index.mdx' + path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/multi-step-sign-in/index.mdx' }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/add-social-provider/index.mdx' @@ -2207,24 +2201,9 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/delete-user-account/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/auth/auth-events/index.mdx' }, @@ -2248,15 +2227,6 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/index.mdx' }, @@ -2278,12 +2248,6 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/working-with-files/index.mdx' }, @@ -2296,52 +2260,20 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/offline/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx' }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/existing-resources/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/upgrade-guide/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx', - children: [ - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx' - } - ] } ] }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/index.mdx', children: [ - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/set-up-rest-api/index.mdx' }, @@ -2360,23 +2292,14 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/customize-authz/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/existing-resources/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx' } ] }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/index.mdx', children: [ - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/set-up-storage/index.mdx' }, @@ -2413,18 +2336,12 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/lambda-triggers/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/custom-plugin/index.mdx' }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/existing-resources/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/sdk/index.mdx' }, @@ -2433,32 +2350,6 @@ export const directory = { } ] }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx', - children: [ - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx' - } - ] - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/index.mdx', children: [ @@ -2494,17 +2385,6 @@ export const directory = { } ] }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx', - children: [ - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx' - }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx' - } - ] - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/server-side-rendering/index.mdx' }, diff --git a/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx b/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx deleted file mode 100644 index fb46c6884c2..00000000000 --- a/src/pages/[platform]/build-a-backend/storage/storagepath/index.mdx +++ /dev/null @@ -1,125 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Using StoragePath', - description: "Learn more about constructing a StoragePath to use on Amplify Storage APIs", - platforms: [ - 'swift', - 'android', - 'flutter' - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -You can use `StoragePath` to access, upload to, or download from to any path in your S3 bucket. The Amplify Gen 1 CLI automatically creates the following directories: -- `public/`: Accessible by all users of your application -- `protected//`: Readable by all users (you need to specify the identityID of the user who uploaded the file). Writable only by the creating user -- `private//`: Readable and writable only by the creating user - -If you are using Amplify Gen2 or an S3 bucket not created by Amplify, you can use StoragePath to access, upload to, or download from any directory in your bucket. - -You can specify the path to your S3 resource by creating a `StoragePath` directly from a String, or by constructing the path with the user's Cognito IdentityId. - -A `StoragePath` must: - -1. Not start with a '/' (leading slash) -2. Not be empty - -## Create a StoragePath from String -When constructing a StoragePath from a String, the provided string will be the path. - - - -```swift -// Resolves to "public/exampleFile.txt" -StoragePath.fromString("public/exampleFile.txt") -``` - - - - - - ```java - // Resolves to "public/exampleFile.txt" - StoragePath.fromString("public/exampleFile.txt") - ``` - - - - ```kotlin - // Resolves to "public/exampleFile.txt" - StoragePath.fromString("public/exampleFile.txt") - ``` - - - - - -```dart -// Resolves to "public/exampleFile.txt" -const StoragePath.fromString('public/exampleFile.txt'); -``` - - - - -## Create a StoragePath with user’s IdentityId -You may want to construct a StoragePath that contains the Amplify Auth user’s IdentityId. We’ve created a helper function that injects the user’s IdentityId when a Storage API is called, since fetching an IdentityId from the Auth plugin is not synchronous. - - - -```swift -// If the user's identityId was "123", -// the StoragePath would resolve to "private/123/exampleFile.txt" -StoragePath.fromIdentityID { identityId in - return "private/\(identityId)/exampleFile.txt" -} -``` - - - - - - ```java - // If the user's identityId was "123", - // the StoragePath would resolve to "private/123/exampleFile.txt" - StoragePath.fromIdentityId(identityId -> - "private/" + identityId + "/exampleFile.txt" - ); - ``` - - - - ```kotlin - // If the user's identityId was "123", - // the StoragePath would resolve to "private/123/exampleFile.txt" - StoragePath.fromIdentityId { identityId -> - "private/${identityId}/exampleFile.txt" - } - ``` - - - - - -```dart -// If the user's identityId was "123", -// the StoragePath would resolve to "private/123/exampleFile.txt" -StoragePath.fromIdentityId( - (String identityId) => 'private/$identityId/exampleFile.txt', -}; -``` - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx deleted file mode 100644 index e6124862644..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/admin-actions/index.mdx +++ /dev/null @@ -1,365 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Set up admin actions', - description: 'Learn how to expose administrative actions for your Cognito User Pool to your end user applications.', - platforms: [ - 'flutter', - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -Admin Actions allow you to execute queries and operations against users and groups in your Cognito user pool. - -For example, the ability to list all users in a Cognito User Pool may provide useful for the administrative panel of an app if the logged-in user is a member of a specific Group called "Admins". - -> This is an advanced feature that is not recommended without an understanding of the underlying architecture. The associated infrastructure which is created is a base designed for you to customize for your specific business needs. We recommend removing any functionality which your app does not require. - -The Amplify CLI can setup a REST endpoint with secure access to a Lambda function running with limited permissions to the User Pool if you wish to have these capabilities in your application, and you can choose to expose the actions to all users with a valid account or restrict to a specific User Pool Group. - -## Enable Admin Queries - - - - -```bash -amplify add auth -``` - -Select the option to go through Manual configuration. - -```console - Do you want to use the default authentication and security configuration? (Use arrow keys) - Default configuration - Default configuration with Social Provider (Federation) -❯ Manual configuration - I want to learn more. -``` - -Go through the rest of the configuration steps until you reach the following prompts: - -```console -? Do you want to add User Pool Groups? Yes -? Provide a name for your user pool group: Admins -? Do you want to add another User Pool Group No -✔ Sort the user pool groups in order of preference · Admins -? Do you want to add an admin queries API? Yes -? Do you want to restrict access to the admin queries API to a specific Group? Yes -? Select the group to restrict access with: (Use arrow keys) -❯ Admins - Enter a custom group -``` - -Continue with the rest of the prompts to finish the configuration. - - - - - -```bash -amplify update auth -``` - -Select the option to Create or update Admin queries API. - -```console -What do you want to do? Create or update Admin queries API -? Do you want to restrict access to the admin queries API to a specific Group Yes -? Select the group to restrict access with: (Use arrow keys) -❯ Admins - Enter a custom group -``` - - - - - - - -If you don't have any User Pool Groups, you will need to select `Enter a custom group`. - - - -When ready, run `amplify push` to deploy the changes. - -This will configure an API Gateway endpoint with a Cognito Authorizer that accepts an Access Token, which is used by a Lambda function to perform actions against the User Pool. The function is example code which you can use to remove, add, or alter functionality based on your business case by editing it in the `amplify/backend/function/AdminQueriesXXX/src` directory and running an `amplify push` to deploy your changes. If you choose to restrict actions to a specific Group, custom middleware in the function will prevent any actions unless the user is a member of that Group. - -## Admin Queries API - -The default routes and their functions, HTTP methods, and expected parameters are below - -- `addUserToGroup`: Adds a user to a specific Group. Expects `username` and `groupname` in the POST body. -- `removeUserFromGroup`: Removes a user from a specific Group. Expects `username` and `groupname` in the POST body. -- `confirmUserSignUp`: Confirms a users signup. Expects `username` in the POST body. -- `disableUser`: Disables a user. Expects `username` in the POST body. -- `enableUser`: Enables a user. Expects `username` in the POST body. -- `getUser`: Gets specific user details. Expects `username` as a GET query string. -- `listUsers`: Lists all users in the current Cognito User Pool. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. -- `listGroups`: Lists all groups in the current Cognito User Pool. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. -- `listGroupsForUser`: Lists groups to which current user belongs to. Expects `username` as a GET query string. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. -- `listUsersInGroup`: Lists users that belong to a specific group. Expects `groupname` as a GET query string. You can provide an OPTIONAL `limit` (between 0 and 60) as a GET query string, which returns a `NextToken` that can be provided as a `token` query string for pagination. -- `signUserOut`: Signs a user out from User Pools, but only if the call is originating from that user. Expects `username` in the POST body. - -## Example - -To leverage this functionality in your app you would call the appropriate route from `Amplify.API` after signing in. The following example adds the user "richard" to the Editors Group and then list all members of the Editors Group with a pagination limit of 10: - - - - - -```js -import React from 'react' -import { Amplify } from 'aws-amplify'; -import { fetchAuthSession } from 'aws-amplify/auth'; -import { post } from 'aws-amplify/api' -import { withAuthenticator } from '@aws-amplify/ui-react'; -import '@aws-amplify/ui-react/styles.css'; - -import amplifyconfig from './amplifyconfiguration.json'; -Amplify.configure(amplifyconfig); - -const client = generateClient() - -async function addToGroup() { - let apiName = 'AdminQueries'; - let path = '/addUserToGroup'; - let options = { - body: { - "username" : "richard", - "groupname": "Editors" - }, - headers: { - 'Content-Type' : 'application/json', - Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` - } - } - return post({apiName, path, options}); -} - -async function listEditors(limit){ - let apiName = 'AdminQueries'; - let path = '/listUsersInGroup'; - let options = { - queryStringParameters: { - "groupname": "Editors", - "limit": limit, - }, - headers: { - 'Content-Type' : 'application/json', - Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` - } - } - const response = await get({apiName, path, options}); - return response; -} - -function App() { - return ( -
- - -
- ); -} - -export default withAuthenticator(App); -``` - -
- - - -1. Initialize Amplify API. Refer to [Set up Amplify REST API](/gen1/[platform]/build-a-backend/restapi/set-up-rest-api/) for more details. - -You should have the initialization code including the imports: - -```swift -import Amplify -import AWSCognitoAuthPlugin -import AWSAPIPlugin -``` - -and code that adds `AWSCognitoAuthPlugin` and `AWSAPIPlugin` before configuring Amplify. - -```swift -try Amplify.add(plugin: AWSCognitoAuthPlugin()) -try Amplify.add(plugin: AWSAPIPlugin()) -try Amplify.configure() -``` - -2. Sign in using `Amplify.Auth`. See [Amplify.Auth](/gen1/[platform]/build-a-backend/auth/set-up-auth/) to learn more about signing up and signing in a user. - -3. Use the following in your app to add a user to the Group. - -```swift -func addToGroup(username: String, groupName: String) async { - let path = "/addUserToGroup" - let body = "{\"username\":\"\(username)\",\"groupname\":\"\(groupName)\"}".data(using: .utf8) - let request = RESTRequest(path: path, body: body) - do { - let data = try await Amplify.API.post(request: request) - print("Response Body: \(String(decoding: data, as: UTF8.self))") - } catch { - if case let .httpStatusError(statusCode, response) = error as? APIError, - let awsResponse = response as? AWSHTTPURLResponse, - let responseBody = awsResponse.body { - print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") - } - } -} - -await addToGroup(username: "richard", groupName: "Editors") -``` - -4. Use the following to list the users in the Group. - -```swift -func listEditors(groupName: String, limit: Int, nextToken: String? = nil) async { - let path = "/listUsersInGroup" - var query = [ - "groupname": groupName, - "limit": String(limit) - ] - if let nextToken = nextToken { - query["token"] = nextToken - } - - let request = RESTRequest(path: path, queryParameters: query, body: nil) - do { - let data = try await Amplify.API.get(request: request) - print("Response Body: \(String(decoding: data, as: UTF8.self))") - } catch { - if case let .httpStatusError(statusCode, response) = error as? APIError, - let awsResponse = response as? AWSHTTPURLResponse, - let responseBody = awsResponse.body { - print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") - } - } -} - -await listEditors(groupName: "Editors", limit: 10) -``` - -**Note: Cognito User Pool with HostedUI** - -The Admin Queries API configuration in **amplifyconfiguration.json** will have the endpoint's authorization type set to `AMAZON_COGNITO_USER_POOLS`. With this authorization type, `Amplify.API` will perform the request with the access token. However, when using HostedUI, the app may get unauthorized responses despite being signed in, and will require using the ID Token. Set the authorizationType to "NONE" and add a custom interceptor to return the ID Token. - -```json -{ - "awsAPIPlugin": { - "[YOUR-RESTENDPOINT-NAME]": { - "endpointType": "REST", - "endpoint": "[YOUR-REST-ENDPOINT]", - "region": "[REGION]", - "authorizationType": "NONE" - } - } -} -``` - - -If you perform additional updates to your resources using Amplify CLI, the authorizationType will be reverted back to `AMAZON_COGNITO_USER_POOLS`. Make sure to update this back to `NONE`. - - - -Add a custom interceptor to the API -```swift -try Amplify.configure() -try Amplify.API.add(interceptor: MyCustomInterceptor(), for: "[YOUR-RESTENDPOINT-NAME]") -``` - -Set up the custom interceptor to return the ID token for the request. - -```swift -import Amplify -import AWSPluginsCore - -class MyCustomInterceptor: URLRequestInterceptor { - func latestAuthToken() async throws -> String { - guard let session = try await Amplify.Auth.fetchAuthSession() as? AuthCognitoTokensProvider else { - throw AuthError.unknown("Could not retrieve Cognito token") - } - - let tokens = try session.getCognitoTokens().get() - return tokens.idToken - } - - func intercept(_ request: URLRequest) async throws -> URLRequest { - var request = request - do { - let token = try await latestAuthToken() - request.setValue(token, forHTTPHeaderField: "authorization") - } catch { - throw APIError.operationError("Failed to retrieve Cognito UserPool token.", "", error) - } - return request - } -} -``` - -
- -## Adding Admin Actions - -To add additional admin actions that are not included by default but are enabled by Amazon Cognito, you will need to update the Lambda function code that is generated for you. The change will include adding a route handler for the action and creating a route for it. You will then associate the route handler to the route within the [Express](https://expressjs.com/) app. - -Below is an example of how to add an admin action that will allow you to [update a user's attributes](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminUpdateUserAttributes.html). -```js -async function updateUserAttributes(username, attributes) { - - const params = { - Username: username, - UserAttributes: attributes, - UserPoolId: 'STRING_VALUE', - }; - - console.log(`Attempting to update ${username} attributes`); - - try { - await cognitoIdentityServiceProvider.adminUpdateUserAttributes(params).promise(); - console.log(`Success updating ${username} attributes`); - return { - message: `Success updating ${username} attributes`, - }; - } catch (err) { - console.log(err); - throw err; - } -} -``` -Once the route handler is defined, you will then add a route with the correct HTTP method to the Express app and associate the route handler to the route. Be sure to make the route unique. - -Below is an example of how you can add a `POST` route named `/updateUserAttributes` and associate the above route handler to it. -```js -app.post('/updateUserAttributes', async (req, res, next) => { - if (!req.body.username || !req.body.attributes) { - const err = new Error('username and attributes are required'); - err.statusCode = 400; - return next(err); - } - - try { - const response = await updateUserAttributes(req.body.username, req.body.attributes); - res.status(200).json(response); - } catch (err) { - next(err); - } -}); -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx deleted file mode 100644 index 9d633f1e56d..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/index.mdx +++ /dev/null @@ -1,37 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Use existing resources without the CLI', - description: - 'Configure the Amplify Libraries to use existing Amazon Cognito resources by referencing them in your configuration.', - platforms: ['flutter'] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -import android0 from '/src/fragments/lib/auth/existing-resources.mdx'; - - - -import ios1 from '/src/fragments/lib/auth/existing-resources.mdx'; - - - -import flutter2 from '/src/fragments/lib/auth/existing-resources.mdx'; - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx deleted file mode 100644 index 5f816c4516d..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/import-existing-resources/index.mdx +++ /dev/null @@ -1,115 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Use an existing Cognito User Pool and Identity Pool', - description: 'Configure the Amplify CLI to use existing Amazon Cognito User Pool and Identity Pool resources as an authentication and authorization mechanism for other Amplify categories (API, Storage, and more).', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/auth/import-existing-resources/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - -Import existing Amazon Cognito resources into your Amplify project. Get started by running `amplify import auth` command to search for & import an existing Cognito User Pool & Identity Pool in your account. - -```bash -amplify import auth -``` - -The `amplify import auth` command will: - -- automatically populate your Amplify Library configuration files (aws-exports.js, amplifyconfiguration.json) with your chosen Amazon Cognito resource information -- provide your designated existing Cognito resource as the authentication & authorization mechanism for all auth-dependent categories (API, Storage and more) -- enable Lambda functions to access the chosen Cognito resource if you permit it - -Make sure to run `amplify push` to complete the import process and deploy this backend change to the cloud. - -This feature is particularly useful if you're trying to: - -- enable Amplify categories (such as API, Storage, and function) for your existing user base; -- incrementally adopt Amplify for your application stack; -- independently manage Cognito resources while working with Amplify. - -> Note: Amplify does not manage the lifecycle of an imported resource. - -## Import an existing Cognito User Pool - -Select the "Cognito User Pool only" option when you've run `amplify import auth`. In order to successfully import your User Pool, your User Pools require at least one app client with the following conditions: - -- *A "Web app client"*: an app client **without** a client secret - -Run `amplify push` to complete the import procedure. - -import attributesCallout from "/src/fragments/common/writable-vs-mutable-attributes.mdx"; - - - - - -Ensure that the hosted UI for an app client has a sign-out URL defined as omitting this may cause the Amplify CLI to not generate the OAuth `scopes`, `redirectSignIn`, `redirectSignOut` and `responseType` in the `aws-exports.js` file. - -If the Cognito user pool has native and web client defined ensure the clients have matching OAuth properties. - - - -## Import an existing Identity Pool - -Select the "Cognito User Pool and Identity Pool" option when you've run `amplify import auth`. In order to successfully import your Identity Pool, it must have both of the User Pool app clients fulfilling [these requirements](#import-an-existing-cognito-user-pool) associated as an authentication provider. - -Your Identity Pool needs: - -- an Authenticated Role with a trust relationship to your Identity Pool -- an Unauthenticated Role with a trust relationship to your Identity Pool - -These roles are usually automatically configured when you create a new Identity Pool enabling "Unauthenticated" access and have a Cognito User Pool as an authentication provider. - -Amplify CLI will update the policies attached to the roles to ensure Amplify categories function correctly. For example, enabling Storage for authenticated & guest users will add private, protected, public, read and upload permissions for the S3 bucket to the unauthenticated & authenticated role. - -Run `amplify push` to complete the import procedure. - -## Multi-environment support - -When you create a new environment through `amplify env add`, Amplify CLI will assume by default that you're managing your app's Cognito resources outside of an Amplify project. You'll be asked to either import a different Cognito resource or maintain the same Cognito resource for your app's auth category. - -If you want to have Amplify manage your auth resources in a new environment, run `amplify remove auth` to unlink the imported Cognito resource and `amplify add auth` to create new Amplify-managed auth resources in the new environment. - -## Unlink an existing Cognito User Pool or Identity Pool - -In order to unlink your existing Cognito resource run `amplify remove auth`. This will only unlink the Cognito resource referenced from the Amplify project. It will not delete the Cognito resource itself. - -Run `amplify push` to complete the unlink procedure. - -## Add Environmental Variables to Amplify Console Build - -In order to successfully build your application with Amplify Console add the following environmental variables to your build environment: - -|Environment Variable|Description| -|-|-| -|AMPLIFY_USERPOOL_ID|The ID for the Amazon Cognito user pool imported for auth| -|AMPLIFY_WEBCLIENT_ID|The ID for the app client to be used by web applications. The app client must be configured with access to the Amazon Cognito user pool specified by the AMPLIFY_USERPOOL_ID environment variable.| -|AMPLIFY_NATIVECLIENT_ID|The ID for the app client to be used by native applications. The app client must be configured with access to the Amazon Cognito user pool specified by the AMPLIFY_USERPOOL_ID environment variable.| -|AMPLIFY_IDENTITYPOOL_ID|The ID for the Amazon Cognito identity pool| diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx deleted file mode 100644 index 9903a214370..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/override-cognito/index.mdx +++ /dev/null @@ -1,189 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Override Amplify-generated Cognito resources', - description: "The 'amplify override auth' command generates a developer-configurable 'overrides' TypeScript file that provides Amplify-generated Cognito resources as CDK constructs. For example, developers can set auth settings that are not directly available in the Amplify CLI workflow, such as the number of valid days for a temporary password.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter' - ], - canonicalPath: '/javascript/build-a-backend/auth/override-cognito/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - -```bash -amplify override auth -``` - -Run the command above to override Amplify-generated auth resources including Amazon Cognito user pool, identity pool, user pool groups, and more. - -The command creates a new `overrides.ts` file under `amplify/backend/auth//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). - -## Customize Amplify-generated Cognito auth resources - -Apply all the overrides in the `override(...)` function. For example, to update the temporary password validity days for your Cognito user pool: - -```ts -import { AmplifyAuthCognitoStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyAuthCognitoStackTemplate) { - resources.userPool.policies = { // Set the user pool policies - passwordPolicy: { - ...resources.userPool.policies["passwordPolicy"], // Carry over existing settings - temporaryPasswordValidityDays: 3 // Add new setting not provided Amplify's default - } - } -} -``` - -Or add a custom attribute to your Cognito user pool: - - - -Removing or adding an attribute on a Cognito userpool schema including default attributes (e.g. `email`) will cause errors such as -`Invalid AttributeDataType input, consider using the provided AttributeDataType enum` as CloudFormation interprets this as schema change. - - - - - -Custom attributes can not be renamed or deleted after you create them. - - - -```ts -import { AmplifyAuthCognitoStackTemplate } from '@aws-amplify/cli-extensibility-helper' - -export function override(resources: AmplifyAuthCognitoStackTemplate) { - const myCustomAttribute = { - attributeDataType: 'String', - developerOnlyAttribute: false, - mutable: true, - name: 'my_custom_attribute', - required: false, - } - resources.userPool.schema = [ - ...(resources.userPool.schema as any[]), // Carry over existing attributes (example: email) - myCustomAttribute, - ] -} -``` - -You can override the following auth resources that Amplify generates: - -|Amplify-generated resource|Description| -|-|-| -|[customMessageConfirmationBucket](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html)|S3 bucket used for custom message triggers| -|[snsRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|SNS role for sending authentication-related messages| -|[userPool](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html)|The Cognito user pool that enables user sign-up and sign-in| -|[userPoolClientWeb](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)|A Cognito user pool client for web apps| -|[userPoolClient](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)|A Cognito user pool client for mobile apps| -|[identityPool](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypool.html)|A Cognito identity pool to federate identities| -|[identityPoolRoleMap](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypoolroleattachment.html)|Role mapping for authenticated and unauthenticated user roles| -|[lambdaConfigPermissions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html)|Permissions for Lambda function to access Cognito user pool and identity pool | -|[lambdaTriggerPermissions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM policy attached to Cognito Lambda triggers| -|[userPoolClientLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to fetch app client secret from user pool client| -|[userPoolClientRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|IAM Role for Lambda function to fetch app client secret from user pool client| -|[userPoolClientLambdaPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to fetch app client secret from user pool client| -|[userPoolClientLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to fetch app client secret from user pool client| -|[userPoolClientInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to fetch app client secret from user pool client| -|[hostedUICustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable Cognito user pool Hosted UI login| -|[hostedUICustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to enable Cognito user pool Hosted UI login| -|[hostedUICustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to enable Cognito user pool Hosted UI login| -|[hostedUICustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable Cognito user pool Hosted UI login| -|[hostedUIProvidersCustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to configure Hosted UI with 3rd party identity providers| -|[hostedUIProvidersCustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for Lambda function to configure Hosted UI with 3rd party identity provider| -|[hostedUIProvidersCustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for Lambda function to configure Hosted UI with 3rd party identity provider| -|[hostedUIProvidersCustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to configure Hosted UI with 3rd party identity provider| -|[oAuthCustomResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable OAuth| -|[oAuthCustomResourcePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for OAuth custom CloudFormation resource| -|[oAuthCustomResourceLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for OAuth Lambda function| -|[oAuthCustomResourceInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable OAuth| -|[mfaLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable multi-factor authentication function| -|[mfaLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for multi-factor authentication Lambda function| -|[mfaLambdaPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy for multi-factor authentication Lambda function| -|[mfaLambdaInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable multi-factor authentication| -|[mfaLambdaRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|IAM Execution Role for multi-factor authentication Lambda function| -|[openIdLambda](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|Lambda function to enable OpenID Connect| -|[openIdLogPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable CloudWatch logging for OpenID Connect Lambda function| -|[openIdLambdaIAMPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|IAM Policy to enable OpenID Connect Lambda function| -|[openIdLambdaInputs](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|Custom CloudFormation resource to enable OpenID Connect| -|[openIdLambdaRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|Lambda Execution Role for OpenID Connect Lambda function| - -## Customize Amplify-generated Cognito user group resources - -Apply all the overrides in the `override(...)` function. For example to add a path to the lambda execution role that facilitates the user pool group to role mapping: -```ts -import { AmplifyUserPoolGroupStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyUserPoolGroupStackTemplate) { - resources.lambdaExecutionRole.path = "//" // Note: CFN does not allow you to modify the path after creation -} -``` - -You can override the following user pool group resources that Amplify generates: - -|Amplify-generated resource|Description| -|-|-| -|[userPoolGroup](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolgroup.html)|The map of user pool groups| -|[userPoolGroupRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)|The map of user pool group roles| -|[roleMapCustomResource](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html)|A custom CloudFormation resource to map user pool groups to their roles| -|[lambdaExecutionRole](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html)|Lambda execution role for the "user pool group"-to-role mapping function| -|[roleMapLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)|The Lambda function that facilitates the user pool group to role mapping| - - -## Customize Amplify-generated Cognito auth resources with social providers - -Apply all the overrides in the `override(...)` function. For example to add social providers to your Cognito user pool: - -```ts -import { AmplifyAuthCognitoStackTemplate } from "@aws-amplify/cli-extensibility-helper"; - -export function override(resources: AmplifyAuthCognitoStackTemplate) { - resources.addCfnResource( - { - type: "AWS::Cognito::UserPoolIdentityProvider", - properties: { - AttributeMapping: { - preferred_username: "email", - email: "email" - }, - ProviderDetails: { - client_id: "test", - client_secret: "test", - authorize_scopes: "test", - }, - ProviderName: "LoginWithAmazon", - ProviderType: "LoginWithAmazon", - UserPoolId: { - Ref: "UserPool", - }, - }, - }, - "amazon-social-provider" - ); -} -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx deleted file mode 100644 index a8cfbbb90d4..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/user-group-management/index.mdx +++ /dev/null @@ -1,117 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Set up user group management', - description: 'Create logical groups in Cognito User Pools and assign permissions to access resources in Amplify categories with the Amplify CLI.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/auth/user-group-management/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -You can create logical groups in Cognito User Pools and assign permissions to access resources in Amplify categories with the CLI, as well as define the relative precedence of one group to another. This can be useful for defining which users should be part of "Admins" vs "Editors", and if the users in a Group should be able to just write or write & read to a resource (AppSync, API Gateway, S3 bucket, etc). [You can also use these with `@auth` Static Groups in the GraphQL Transformer](/[platform]/build-a-backend/graphqlapi/customize-authorization-rules#user-group-based-data-access). Precedence helps remove any ambiguity on permissions if a user is in multiple Groups. - -## Create user groups - -```bash -amplify add auth -``` - -```console -❯ Manual configuration - -Do you want to add User Pool Groups? (Use arrow keys) -❯ Yes - -? Provide a name for your user pool group: Admins -? Do you want to add another User Pool Group Yes -? Provide a name for your user pool group: Editors -? Do you want to add another User Pool Group No -? Sort the user pool groups in order of preference … (Use + to change the order) - Admins - Editors -``` - -When asked as in the example above, you can press `Shift` on your keyboard along with the **LEFT** and **RIGHT** arrows to move a Group higher or lower in precedence. Once complete you can open `amplify/backend/auth/userPoolGroups/user-pool-group-precedence.json` to manually set the precedence. - -## Group access controls - -For certain Amplify categories you can restrict access with CRUD (Create, Read, Update, and Delete) permissions, setting different access controls for authenticated users vs Guests (e.g. Authenticated users can read & write to S3 buckets while Guests can only read). You can further restrict this to apply different permissions conditionally depending on if a logged-in user is part of a specific User Pool Group. - -```bash -amplify add storage # Select content -``` - -```console -? Restrict access by? (Use arrow keys) - Auth/Guest Users - Individual Groups -❯ Both - Learn more - -Who should have access? -❯ Auth and guest users - -What kind of access do you want for Authenticated users? -❯ create/update, read - -What kind of access do you want for Guest users? -❯ read - -Select groups: -❯ Admins - -What kind of access do you want for Admins users? -❯ create/update, read, delete -``` - -The above example uses a combination of permissions where users in the "Admins" Group have full access, "Guest" users can only read, and "Authenticated" users who are not a part of any group have create, update, and read access. Amplify will configure the corresponding IAM policy on your behalf. Advanced users can additionally set permissions by adding a `customPolicies` key to `amplify/backend/auth/userPoolGroups/user-pool-group-precedence.json` with custom IAM policy for a Group. This will attach an inline policy on the IAM role associated to this Group during deployment. **Note** this is an advanced feature and only suitable if you have an understanding of AWS resources. For instance perhaps you wanted users in the "Admins" group to have the ability to Create an S3 bucket: - -```json -[ - { - "groupName": "Admins", - "precedence": 1, - "customPolicies": [ - { - "PolicyName": "admin-group-policy", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "statement1", - "Effect": "Allow", - "Action": ["s3:CreateBucket"], - "Resource": ["arn:aws:s3:::*"] - } - ] - } - } - ] - }, - { - "groupName": "Editors", - "precedence": 2 - } -] -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx deleted file mode 100644 index ac6c2c6c6ac..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cdk/index.mdx +++ /dev/null @@ -1,836 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Connect to existing AWS resources built with the CDK', - description: "Connect a new app to AWS resources built with the CDK.", - platforms: [ - 'flutter', - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -This guide shows you how to connect a new app to AWS resources you've already created using the [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/). The AWS CDK is an open-source software development framework for defining cloud infrastructure as code with modern programming languages. This infrastructure is then deployed through [AWS CloudFormation](https://aws.amazon.com/cloudformation/). - - - -In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then create and connect a React web app to the GraphQL API. - - - - - -In this guide, you will use the Amplify Data CDK to create a GraphQL API backend with AWS AppSync. This creates the core backend. You will then build and integrate a Flutter app with the GraphQL API. - - - -Before you begin, you will need: - - - -* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](/[platform]/tools/cli/start/set-up-cli/) and configured. -* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. - - - - - -* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](/gen1/[platform]/tools/cli/start/set-up-cli/) and configured. -* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. -* Flutter and its command-line tools [installed](https://docs.flutter.dev/get-started/install) and configured. - - - -## Build a GraphQL API using the Amplify Data CDK construct - -The CDK provides a simple way to define cloud infrastructure in code. In this section, we will use the CDK to build out the backend resources for our application. - -**Step 1:** Create a folder for the CDK app by running the following command in your terminal. - -```bash title="Terminal" showLineNumbers={false} -mkdir cdk-backend -``` - -**Step 2:** Navigate to the `cdk-backend` folder and create a new CDK project by running the `cdk init` command and specifying your preferred language. - -```bash title="Terminal" -cd cdk-backend -cdk init --language typescript -``` - -**Step 3:** Open the newly created CDK project using VS Code, or your preferred IDE. - -**Step 4:** In your terminal, navigate to the `cdk_backend` root folder, and install the AWS Amplify Data package by running the following command. - -```bash title="Terminal" showLineNumbers={false} -npm install @aws-amplify/data-construct -``` - -**Step 5:** Update the `cdk_backend/lib/cdk-backend-stack.ts` file as shown in the following code to use the `AmplifyData` construct to create an AWS AppSync API. - -```ts title="lib/cdk-backend-stack.ts" -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { - AmplifyData, - AmplifyDataDefinition -} from '@aws-amplify/data-construct'; - -export class CdkBackendStack extends cdk.Stack { - constructor(scope: Construct, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - // highlight-start - new AmplifyData(this, 'AmplifyCdkData', { - definition: AmplifyDataDefinition.fromString(/* GraphQL */ ` - type Todo @model @auth(rules: [{ allow: public }]) { - id: ID! - name: String! - description: String - complete: Boolean - } - `), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } - }); - // highlight-end - } -} -``` - -**Step 6:** Deploy the CDK stacks by running the following command. - -```bash showLineNumbers={false} -cdk deploy -``` - -**Step 7:** The CDK will prepare the resources for deployment and will display the following prompt. Enter **Y** and press **Enter**. - -The CDK preparing for deployment. - -The CDK will deploy the stacks and display the following confirmation. Note the details of the deployed API; we’re going to use them in the next section. - -CDK deploying the stacks. - -Now that you have built the backend API with the CDK, you can connect a frontend. - - - -## Build a React app and connect to the GraphQL API - -In this section, we will connect a React web app to our existing GraphQL API. First, we will create a new React project and install the necessary Amplify packages. Next, we will use the Amplify CLI to generate GraphQL code matching our API structure. Then, we will add React components to perform queries and mutations to manage to-do items in our API. After that, we will configure the Amplify library with details of our backend API. Finally, we will run the application to demonstrate full CRUD functionality with our existing API. - -**Step 1:** Create a React app by running the following command in your terminal. - -```bash title="Terminal" showLineNumbers={false} -npx create-react-app react-amplify-connect -``` - -**Step 2:** Open the newly created React app using VS Code, or your preferred IDE. - -**Step 3:** Install the `aws-amplify`, `@aws-amplify/ui-react`, and `@aws-amplify/cli` packages by running the following commands. - -```bash title="Terminal" showLineNumbers={false} -npm install aws-amplify @aws-amplify/ui-react @aws-amplify/cli -``` - -**Step 4:** Use the awsAppsyncApiId and awsAppsyncRegion values of the CDK stack you created previously to generate the GraphQL client helper code by running the following command. - -awsAppsyncApiId and awsAppsyncRegion values highlighted within the outputs of the CDK stack. - -```react showLineNumbers={false} -npx @aws-amplify/cli codegen add --apiId --region -``` - -**Step 5:** Accept the default values for the prompts. - -```console title="Terminal" showLineNumbers={false} -? Choose the type of app that you're building javascript -? What javascript framework are you using react -✔ Getting API details -? Choose the code generation language target javascript -? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js -? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes -? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 -✔ Downloaded the schema -✔ Generated GraphQL operations successfully and saved at src/graphql -``` - -The Amplify CLI will create the GraphQL client helper code inside the `src/graphql` folder. - -mutations.js, queries.js, and subscriptions.js within the graphql folder. - -**Step 6:** Update the `App.js` file with the following code to create a form with a button to create to-dos, as well as a way to fetch and render the to-do list. - -```jsx title="src/App.js" -import { Amplify} from 'aws-amplify' -import '@aws-amplify/ui-react/styles.css'; -import { useEffect, useState } from 'react'; -import { generateClient } from 'aws-amplify/api'; -import { createTodo } from './graphql/mutations'; -import { listTodos } from './graphql/queries'; - -Amplify.configure({ - API: { - GraphQL: { - // highlight-start - endpoint: '', - region: '', - defaultAuthMode: 'apiKey', - apiKey: '' - // highlight-end - } - } -}); - -const initialState = { name: '', description: '' }; -const client = generateClient(); - -const App = () => { - const [formState, setFormState] = useState(initialState); - const [todos, setTodos] = useState([]); - - useEffect(() => { - fetchTodos(); - }, []); - - function setInput(key, value) { - setFormState({ ...formState, [key]: value }); - } - - async function fetchTodos() { - try { - const todoData = await client.graphql({ - query: listTodos - }); - const todos = todoData.data.listTodos.items; - setTodos(todos); - } catch (err) { - console.log('error fetching todos'); - } - } - - async function addTodo() { - try { - if (!formState.name || !formState.description) return; - const todo = { ...formState }; - setTodos([...todos, todo]); - setFormState(initialState); - await client.graphql({ - query: createTodo, - variables: { - input: todo - } - }); - } catch (err) { - console.log('error creating todo:', err); - } - } - - return ( -
-

Amplify Todos

- setInput('name', event.target.value)} - style={styles.input} - value={formState.name} - placeholder="Name" - /> - setInput('description', event.target.value)} - style={styles.input} - value={formState.description} - placeholder="Description" - /> - - {todos.map((todo, index) => ( -
-

{todo.name}

-

{todo.description}

-
- ))} -
- ); -}; - -const styles = { - container: { - width: 400, - margin: '0 auto', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - padding: 20 - }, - todo: { marginBottom: 15 }, - input: { - border: 'none', - backgroundColor: '#ddd', - marginBottom: 10, - padding: 8, - fontSize: 18 - }, - todoName: { fontSize: 20, fontWeight: 'bold' }, - todoDescription: { marginBottom: 0 }, - button: { - backgroundColor: 'black', - color: 'white', - outline: 'none', - fontSize: 18, - padding: '12px 0px' - } -}; - -export default App; -``` - - -**Step 7:** Run the app using the following command. - -```bash title="Terminal" showLineNumbers={false} -npm start -``` - -**Step 8:** Use the form to create a few to-do items. - - - -In this section, we generated GraphQL code, created React components, configured Amplify, and connected the app to the API. This enabled full CRUD functionality with our backend through queries and mutations. - -
- - - -## Build a Flutter app and connect to the GraphQL API - -In this section, we will connect a Flutter mobile app to the GraphQL API we created with the CDK. First, we will initialize a Flutter project, define models matching our schema, and use Amplify to integrate CRUD operations. Then we will add UI pages to manage to-do items with queries and mutations. Finally, we will configure Amplify with our backend details and run the app to demonstrate full functionality with our existing API. - -**Step 1:** Create a Flutter app by running the following command in your terminal. - -```bash title="Terminal" showLineNumbers={false} -flutter create flutter_todo_app --platforms=web -``` - -**Step 2:** Open the newly created Flutter app by running the following commands in your terminal. - -```bash title="Terminal" -cd flutter_todo_app -code . -r -``` - -**Step 3**: Update the `pubspec.yaml` file in the app’s root directory to add the required dependencies, as shown in the following code. - -{/* cSpell:disable */} -```yaml title="pubspec.yaml" -name: flutter_todo_app -description: "A new Flutter project." -publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.0+1 - -// highlight-start -dependencies: - flutter: - sdk: flutter - amplify_flutter: ^1.0.0 - amplify_api: ^1.0.0 - go_router: ^6.5.5 - cupertino_icons: ^1.0.2 -// highlight-end - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^2.0.0 - -flutter: - uses-material-design: true -``` -{/* cSpell:enable */} - -**Step 4:** Run the following command in your terminal to install the dependencies you added to the `pubspec.yaml` file. - -```bash title="Terminal" showLineNumbers={false} -flutter pub get -``` - -**Step 5:** Install the @aws-amplify/cli packages by running the following command. - -```bash title="Terminal" showLineNumbers={false} -npm install @aws-amplify/cli -``` - -**Step 6:** Create a new folder and name it `graphql`. Inside it, create the file `schema.graphql`. - -The schema.graphql file inside the graphql folder. - -**Step 7:** Update the file `schema.graphql`, as shown in the following example, to define the Todo data model, similar to what you used for the CDK app. - -```graphql title="schema.graphql" -type Todo @model @auth(rules: [{ allow: public }]) { - id: ID! - name: String! - description: String - complete: Boolean -} -``` - -**Step 8:** Run the following command to generate the GraphQL client helper models inside the `lib/models` folder. - -```bash showLineNumbers={false} -npx @aws-amplify/cli codegen models --model-schema ./graphql --target flutter --output-dir ./lib/models -``` - -The ModelProvider.dart and Todo.dart files within the models folder. - -**Step 9:** Create the file `todo_item_page.dart` inside the `lib` folder and update it with the following code to present a form to the user for creating a to-do item. Once submitted, the form will initiate a GraphQL mutation to add or modify the item in the database. - -```dart title="todo_item_page.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import '../models/ModelProvider.dart'; - -class ToDoItemPage extends StatefulWidget { - const ToDoItemPage({ - required this.todoItem, - super.key, - }); - - final Todo? todoItem; - - @override - State createState() => _ToDoItemPageState(); -} - -class _ToDoItemPageState extends State { - final _formKey = GlobalKey(); - final TextEditingController _nameController = TextEditingController(); - final TextEditingController _descriptionController = TextEditingController(); - - late final String _nameText; - late bool _isDone; - - bool get _isCreate => _todoItem == null; - Todo? get _todoItem => widget.todoItem; - - @override - void initState() { - super.initState(); - - final todoItem = _todoItem; - if (todoItem != null) { - _nameController.text = todoItem.name; - _descriptionController.text = todoItem.description ?? ''; - - _nameText = 'Update to-do Item'; - _isDone = todoItem.complete ?? false; - } else { - _nameText = 'Create to-do Item'; - _isDone = false; - } - } - - @override - void dispose() { - _nameController.dispose(); - _descriptionController.dispose(); - - super.dispose(); - } - - Future submitForm() async { - if (!_formKey.currentState!.validate()) { - return; - } - - // If the form is valid, submit the data - final name = _nameController.text; - final description = _descriptionController.text; - final complete = _isDone; - - // highlight-start - if (_isCreate) { - // Create a new todo item - final newEntry = Todo( - name: name, - description: description.isNotEmpty ? description : null, - complete: complete, - ); - final request = ModelMutations.create(newEntry); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Create result: $response'); - } else { - // Update todoItem instead - final updateToDoItem = _todoItem!.copyWith( - name: name, - description: description.isNotEmpty ? description : null, - complete: complete, - ); - final request = ModelMutations.update(updateToDoItem); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Update result: $response'); - } - // highlight-end - - // Navigate back to homepage after create/update executes - if (mounted) { - context.pop(); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(_nameText), - ), - body: Align( - alignment: Alignment.topCenter, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 800), - child: Padding( - padding: const EdgeInsets.all(16), - child: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _nameController, - decoration: const InputDecoration( - labelText: 'Name (required)', - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a name'; - } - return null; - }, - ), - TextFormField( - controller: _descriptionController, - decoration: const InputDecoration( - labelText: 'Description', - ), - ), - SwitchListTile( - title: const Text('Done'), - value: _isDone, - onChanged: (bool value) { - setState(() { - _isDone = value; - }); - }, - secondary: const Icon(Icons.done_all_outlined), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: submitForm, - child: Text(_nameText), - ), - ], - ), - ), - ), - ), - ), - ), - ); - } -} -``` - -**Step 10:** Create the file `home_page.dart.dart` inside the `lib` folder and update it with the following code. This page will use a GraphQL query to retrieve the list of to-do items and display them in a ListView widget. The page will also allow the user to delete a to-do item by using a GraphQL mutation. - -```dart title="home_page.dart.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import '../models/ModelProvider.dart'; - -class HomePage extends StatefulWidget { - const HomePage({super.key}); - - @override - State createState() => _HomePageState(); -} - -class _HomePageState extends State { - var _todoItems = []; - - @override - void initState() { - super.initState(); - _refreshTodoItems(); - } - - // highlight-start - Future _refreshTodoItems() async { - try { - final request = ModelQueries.list(Todo.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items; - if (response.hasErrors) { - safePrint('errors: ${response.errors}'); - return; - } - setState(() { - _todoItems = todos!.whereType().toList(); - }); - } on ApiException catch (e) { - safePrint('Query failed: $e'); - } - } - - Future _deleteToDoItem(Todo todoItem) async { - final request = ModelMutations.delete(todoItem); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Delete response: $response'); - await _refreshTodoItems(); - } - // highlight-end - - Future _openToDoItem({Todo? todoItem}) async { - await context.pushNamed('manage', extra: todoItem); - // Refresh the entries when returning from the - // todo item screen. - await _refreshTodoItems(); - } - - Widget _buildRow({ - required String name, - required String description, - required bool isDone, - TextStyle? style, - }) { - return Row( - children: [ - Expanded( - child: Text( - name, - textAlign: TextAlign.center, - style: style, - ), - ), - Expanded( - child: Text( - description, - textAlign: TextAlign.center, - style: style, - ), - ), - Expanded( - child: isDone ? const Icon(Icons.done) : const SizedBox(), - ), - ], - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton( - // Navigate to the page to create new todo item - onPressed: _openToDoItem, - child: const Icon(Icons.add), - ), - appBar: AppBar( - title: const Text('To-Do List'), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.only(top: 25), - child: RefreshIndicator( - onRefresh: _refreshTodoItems, - child: Column( - children: [ - if (_todoItems.isEmpty) - const Text('Use the \u002b sign to add new to-do items') - else - const SizedBox(height: 30), - _buildRow( - name: 'Name', - description: 'Description', - isDone: false, - style: Theme.of(context).textTheme.titleMedium, - ), - const Divider(), - Expanded( - child: ListView.builder( - itemCount: _todoItems.length, - itemBuilder: (context, index) { - final todoItem = _todoItems[index]; - return Dismissible( - key: ValueKey(todoItem), - background: const ColoredBox( - color: Colors.red, - child: Padding( - padding: EdgeInsets.only(right: 10), - child: Align( - alignment: Alignment.centerRight, - child: Icon(Icons.delete, color: Colors.white), - ), - ), - ), - onDismissed: (_) => _deleteToDoItem(todoItem), - child: ListTile( - onTap: () => _openToDoItem( - todoItem: todoItem, - ), - title: _buildRow( - name: todoItem.name, - description: todoItem.description ?? '', - isDone: todoItem.complete ?? false, - ), - ), - ); - }, - ), - ), - ], - ), - ), - ), - ), - ); - } -} -``` - -**Step 11:** Update `main.dart` to configure Amplify using the details of the GraphQL API you created using the CDK app in the previous section. - -```dart title="main.dart" -import 'package:amplify_api/amplify_api.dart'; - -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import 'models/ModelProvider.dart'; -import 'home_page.dart'; -import 'todo_item_page.dart'; - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await _configureAmplify(); - runApp(const MyApp()); -} - -Future _configureAmplify() async { - try { - final api = AmplifyAPI(modelProvider: ModelProvider.instance); - - await Amplify.addPlugins([api]); - const amplifyconfig = '''{ - "api": { - "plugins": { - "awsAPIPlugin": { - "flutter_todo_app": { - "endpointType": "GraphQL", - // highlight-start - "endpoint": "", - "region": "", - "authorizationType": "API_KEY", - "apiKey": "" - // highlight-end - } - } - } - } -}'''; - - await Amplify.configure(amplifyconfig); - - safePrint('Successfully configured'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // GoRouter configuration - static final _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, state) => const HomePage(), - ), - GoRoute( - path: '/manage-todo-item', - name: 'manage', - builder: (context, state) => ToDoItemPage( - todoItem: state.extra as Todo?, - ), - ), - ], - ); - - @override - Widget build(BuildContext context) { - return MaterialApp.router( - routerConfig: _router, - debugShowCheckedModeBanner: false, - builder: (context, child) { - return child!; - }, - ); - } -} -``` - -**Step 12:** Run the app in the Chrome browser using the following command. - -```bash title="Terminal" showLineNumbers={false} -flutter run -d chrome -``` - - - - - -## Conclusion - -Congratulations! You used the AWS Amplify Data CDK construct to create a GraphQL API backend using AWS AppSync. You then connected your app to that API using the Amplify libraries. If you have any feedback, leave a [GitHub issue](https://github.com/aws-amplify/docs/issues) or join our [Discord Community](https://discord.gg/amplify)! - -## Clean up resources - -Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the CDK app created above. - -```bash title="Terminal" showLineNumbers={false} - cdk destroy -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx deleted file mode 100644 index d78b609c6ba..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/cli/index.mdx +++ /dev/null @@ -1,1014 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Connect to existing AWS resources with Amplify CLI', - description: "Use the Amplify CLI to connect existing AWS resources to a new app.", - platforms: [ - 'flutter', - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -The AWS Amplify CLI (Command Line Interface) CLI provides a simple workflow for provisioning cloud resources like authentication, databases, and storage for apps through the command line. - - -In this guide, you will learn how to connect a new Flutter mobile app to backend resources you've already created using the Amplify CLI. - - - -Connecting a web app? We also offer a version of this guide for integrating existing backends with React using the Amplify CLI. Check out the [React guide](/gen1/react/build-a-backend/existing-resources/cli/). - - - - - -In this guide, you will learn how to connect a new React web app to backend resources you've already created using the Amplify CLI. - - - -Connecting a mobile app? We also offer a version of this guide for integrating existing backends with Flutter using the Amplify CLI. Check out the [Flutter guide](/flutter/build-a-backend/existing-resources/cli/). - - - - - -## Connect mobile app to existing AWS resources - -This guide will walk you through connecting a new Flutter app to AWS resources created with Amplify for an existing Flutter app. If you don't already have an existing app, you can follow this [Flutter tutorial](https://docs.amplify.aws/start/getting-started/setup/q/integration/flutter/) to create a budget tracker app that uses Amplify Auth and API resources. - -Before you begin, you will need: - -* An existing Flutter app -* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](/gen1/[platform]/tools/cli/start/set-up-cli/) and configured. -* Flutter [installed](https://docs.flutter.dev/get-started/install) and configured. -* A text editor combined with Flutter’s command-line tools. For this guide, we will use VS Code, but you can use your preferred IDE. - -### Find the AWS backend details -Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app. - -**Step 1:** In your existing app, open the file `/amplify/team-provider-info.json`. - -The team-provider-info.json file within the file directory of the Amplify app. - -**Step 2:** In the `team-provider-info.json` file, note the following: - -1. The environment you want to use -2. The `AmplifyAppId` for the required environment - -{/* cSpell:disable */} -The environment and AmplifyAppId in team-provider-info.json file. -{/* cSpell:enable */} - -### Create the Flutter app -Now that we have gathered the necessary backend details, we can start building out the new Flutter app. - -**Step 1:** Create a Flutter app by running the following command in your terminal. - -```bash showLineNumbers={false} - -flutter create amplify_connect_resources - -``` - - - -**Step 2:** Open the newly created Flutter app using VS Code by running the following commands in your terminal. - -``` -cd amplify_connect_resources -code . -r -``` - -![Open the created app using VS Code.](/images/existing-resources/app-vscode-mobile-cli.png) - - -**Step 3:** Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal. - -```bash showLineNumbers={false} -amplify pull --appId --envName -``` - - -**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. - -Accept the default values for the prompts about the default editor, type of app, and storage location of the configuration file. Then answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. - -{/* cSpell:disable */} -``` -? Select the authentication method you want to use: AWS profile - -For more information on AWS Profiles, see: -https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html - -? Please choose the profile you want to use AwsWest1 -Amplify AppID found: dorapq24trw9r. Amplify App name is:amplifyBudget -Backend environment dev found in Amplify Console app: amplifyBudget -? Choose your default editor: Visual Studio Code -✔ Choose the type of app that you're building · flutter -Please tell us about your project -? Where do you want to store your configuration file? ./lib/ -? Do you plan on modifying this backend? Yes -⠦ Fetching updates to backend environment: dev from the cloud.⠋ Building resource api/amplifyBudget -⠹ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully. - -Edit your schema at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema -✔ Successfully pulled backend environment dev from the cloud. -✅ - -✅ Successfully pulled backend environment dev from the cloud. -Run 'amplify pull' to sync future upstream changes. -``` -{/* cSpell:enable */} - -The Amplify CLI will add a new folder named `amplify` to the app's root folder, which contains the Amplify project and backend details. It will also add a new Dart file, `amplifyconfiguration.dart`, to the `lib/` folder. The app will use this file at runtime to locate and connect to the backend resources you have provisioned. - -The Amplify folder and amplifyconfiguration.dart file within the file directory of the Amplify app. - -**Step 5:** Update the file `pubspec.yaml` in the app root directory to add the required packages. In this example, we will use the same packages as the app created in this guide. To do this, update the `pubspec.yaml` as shown in the following. - -{/* cSpell:disable */} -```yaml title="pubspec.yaml" -name: budget_tracker -description: A new Flutter project. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.0+1 - -dependencies: -// highlight-start - amplify_api: ^1.0.0 - amplify_auth_cognito: ^1.0.0 - amplify_authenticator: ^1.0.0 - amplify_flutter: ^1.0.0 -// highlight-end - flutter: - sdk: flutter -// highlight-next-line - go_router: ^6.5.5 - cupertino_icons: ^1.0.2 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^2.0.0 - -flutter: - uses-material-design: true - -``` -{/* cSpell:enable */} - - -**Step 6**: To enable type-safe interaction with the GraphQL schema, use this command to generate the required Dart files. - -```bash showLineNumbers={false} - -amplify codegen models - -``` -The Amplify CLI will generate the Dart files in the `lib/models` folder. - -The models folder within the file directory of the Amplify app. - -**Step 7:** Update the `main.dart` file with the following code to introduce the Amplify Authenticator and integrate Amplify API with your app to create, update, query, and delete `BudgetEntry` items. Typically, you would break this file up into smaller modules but we've kept it as a single file for this guide. - -```bash title="main.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:amplify_authenticator/amplify_authenticator.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'amplifyconfiguration.dart'; -import 'models/ModelProvider.dart'; - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await _configureAmplify(); - runApp(const MyApp()); -} - -// highlight-start -Future _configureAmplify() async { - - try { - - final api = AmplifyAPI(modelProvider: ModelProvider.instance); - final auth = AmplifyAuthCognito(); - await Amplify.addPlugins([api, auth]); - await Amplify.configure(amplifyconfig); - - safePrint('Successfully configured'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } -} -// highlight-end - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // GoRouter configuration - static final _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, state) => const HomeScreen(), - ), - GoRoute( - path: '/manage-budget-entry', - name: 'manage', - builder: (context, state) => ManageBudgetEntryScreen( - budgetEntry: state.extra as BudgetEntry?, - ), - ), - ], - ); - - @override - Widget build(BuildContext context) { - return Authenticator( - child: MaterialApp.router( - routerConfig: _router, - debugShowCheckedModeBanner: false, - // highlight-next-line - builder: Authenticator.builder(), - ), - ); - } -} - -class LoadingScreen extends StatelessWidget { - const LoadingScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ); - } -} - -class HomeScreen extends StatefulWidget { - const HomeScreen({super.key}); - - @override - State createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { - var _budgetEntries = []; - - @override - void initState() { - super.initState(); - _refreshBudgetEntries(); - } - - Future _refreshBudgetEntries() async { - try { - final request = ModelQueries.list(BudgetEntry.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items; - if (response.hasErrors) { - safePrint('errors: ${response.errors}'); - return; - } - setState(() { - _budgetEntries = todos!.whereType().toList(); - }); - } on ApiException catch (e) { - safePrint('Query failed: $e'); - } - } - - Future _deleteBudgetEntry(BudgetEntry budgetEntry) async { - final request = ModelMutations.delete(budgetEntry); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Delete response: $response'); - await _refreshBudgetEntries(); - } - - Future _navigateToBudgetEntry({BudgetEntry? budgetEntry}) async { - await context.pushNamed('manage', extra: budgetEntry); - // Refresh the entries when returning from the - // budget entry screen. - await _refreshBudgetEntries(); - } - - double _calculateTotalBudget(List items) { - var totalAmount = 0.0; - for (final item in items) { - totalAmount += item?.amount ?? 0; - } - return totalAmount; - } - - Widget _buildRow({ - required String title, - required String description, - required String amount, - TextStyle? style, - }) { - return Row( - children: [ - Expanded( - child: Text( - title, - textAlign: TextAlign.center, - style: style, - ), - ), - Expanded( - child: Text( - description, - textAlign: TextAlign.center, - style: style, - ), - ), - Expanded( - child: Text( - amount, - textAlign: TextAlign.center, - style: style, - ), - ), - ], - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton( - // Navigate to the page to create new budget entries - onPressed: _navigateToBudgetEntry, - child: const Icon(Icons.add), - ), - appBar: AppBar( - title: const Text('Budget Tracker'), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.only(top: 25), - child: RefreshIndicator( - onRefresh: _refreshBudgetEntries, - child: Column( - children: [ - if (_budgetEntries.isEmpty) - const Text('Use the \u002b sign to add new budget entries') - else - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Show total budget from the list of all BudgetEntries - Text( - 'Total Budget: \$ ${_calculateTotalBudget(_budgetEntries).toStringAsFixed(2)}', - style: const TextStyle(fontSize: 24), - ) - ], - ), - const SizedBox(height: 30), - _buildRow( - title: 'Title', - description: 'Description', - amount: 'Amount', - style: Theme.of(context).textTheme.titleMedium, - ), - const Divider(), - Expanded( - child: ListView.builder( - itemCount: _budgetEntries.length, - itemBuilder: (context, index) { - final budgetEntry = _budgetEntries[index]; - return Dismissible( - key: ValueKey(budgetEntry), - background: const ColoredBox( - color: Colors.red, - child: Padding( - padding: EdgeInsets.only(right: 10), - child: Align( - alignment: Alignment.centerRight, - child: Icon(Icons.delete, color: Colors.white), - ), - ), - ), - onDismissed: (_) => _deleteBudgetEntry(budgetEntry), - child: ListTile( - onTap: () => _navigateToBudgetEntry( - budgetEntry: budgetEntry, - ), - title: _buildRow( - title: budgetEntry.title, - description: budgetEntry.description ?? '', - amount: - '\$ ${budgetEntry.amount.toStringAsFixed(2)}', - ), - ), - ); - }, - ), - ), - ], - ), - ), - ), - ), - ); - } -} - -class ManageBudgetEntryScreen extends StatefulWidget { - const ManageBudgetEntryScreen({ - required this.budgetEntry, - super.key, - }); - - final BudgetEntry? budgetEntry; - - @override - State createState() => - _ManageBudgetEntryScreenState(); -} - -class _ManageBudgetEntryScreenState extends State { - final _formKey = GlobalKey(); - final TextEditingController _titleController = TextEditingController(); - final TextEditingController _descriptionController = TextEditingController(); - final TextEditingController _amountController = TextEditingController(); - - late final String _titleText; - - bool get _isCreate => _budgetEntry == null; - BudgetEntry? get _budgetEntry => widget.budgetEntry; - - @override - void initState() { - super.initState(); - - final budgetEntry = _budgetEntry; - if (budgetEntry != null) { - _titleController.text = budgetEntry.title; - _descriptionController.text = budgetEntry.description ?? ''; - _amountController.text = budgetEntry.amount.toStringAsFixed(2); - _titleText = 'Update budget entry'; - } else { - _titleText = 'Create budget entry'; - } - } - - @override - void dispose() { - _titleController.dispose(); - _descriptionController.dispose(); - _amountController.dispose(); - super.dispose(); - } - - Future submitForm() async { - if (!_formKey.currentState!.validate()) { - return; - } - - // If the form is valid, submit the data - final title = _titleController.text; - final description = _descriptionController.text; - final amount = double.parse(_amountController.text); - - if (_isCreate) { - // Create a new budget entry - final newEntry = BudgetEntry( - title: title, - description: description.isNotEmpty ? description : null, - amount: amount, - ); - final request = ModelMutations.create(newEntry); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Create result: $response'); - } else { - // Update budgetEntry instead - final updateBudgetEntry = _budgetEntry!.copyWith( - title: title, - description: description.isNotEmpty ? description : null, - amount: amount, - ); - final request = ModelMutations.update(updateBudgetEntry); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Update result: $response'); - } - - // Navigate back to homepage after create/update executes - if (mounted) { - context.pop(); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(_titleText), - ), - body: Align( - alignment: Alignment.topCenter, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 800), - child: Padding( - padding: const EdgeInsets.all(16), - child: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _titleController, - decoration: const InputDecoration( - labelText: 'Title (required)', - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a title'; - } - return null; - }, - ), - TextFormField( - controller: _descriptionController, - decoration: const InputDecoration( - labelText: 'Description', - ), - ), - TextFormField( - controller: _amountController, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - decoration: const InputDecoration( - labelText: 'Amount (required)', - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter an amount'; - } - final amount = double.tryParse(value); - if (amount == null || amount <= 0) { - return 'Please enter a valid amount'; - } - return null; - }, - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: submitForm, - child: Text(_titleText), - ), - ], - ), - ), - ), - ), - ), - ), - ); - } -} - -``` - - -**Step 8:** Run the app using the following command, and use the authentication flow to create a user. Then create a few budget items. - - -```bash showLineNumbers={false} - -flutter run - - -``` - - - - - -### Conclusion - -Congratulations! Your new Flutter app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management and a scalable GraphQL API backed by Amazon DynamoDB. - - -### Clean up resources - -Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app. - -```bash showLineNumbers={false} -amplify delete - -``` - -If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the [Build & connect backend section](/[platform]/build-a-backend/) for guides on how to add and connect other backend resources. - - - -## Connect web app to existing AWS resources - -This guide will walk you through connecting a new React web app to the AWS resources created with Amplify for an existing React app. If you don't already have an existing app, you can follow this [React tutorial](https://docs.amplify.aws/react/start/getting-started/introduction/) to create a to-do app that uses Amplify Auth, API, and Hosting resources. - -Before you begin, you will need: - -* An existing React app -* An AWS account: If you don't already have an account, follow the [Setting Up Your AWS Environment](https://aws.amazon.com/getting-started/guides/setup-environment/) tutorial for a quick overview. -* The Amplify CLI [installed](https://docs.amplify.aws/cli/start/install/) and configured. -* A text editor. For this guide, we will use VS Code, but you can use your preferred IDE. - -### Find the AWS backend details -Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app. - -**Step 1:** In your existing app, open the file `/amplify/team-provider-info.json` . - -The team-provider-info.json file within the file directory of the Amplify app. - -**Step 2:** In the `team-provider-info.json` file, note the following: - -1. The environment you want to use -2. The `AmplifyAppId` for the required environment - -{/* cSpell:disable */} -The environment and AmplifyAppId in team-provider-info.json file. -{/* cSpell:enable */} - -### Create the React app -Now that we have gathered the necessary backend details, we can start building out the new React app. - -**Step 1:** Create a React app by running the following command in your terminal. - -```bash showLineNumbers={false} -npx create-react-app react-amplify-connect -``` - -**Step 2:** Open the newly created React app using VS Code by running the following commands in your terminal. - -``` -cd react-amplify-connect -code . -r -``` - -![Open the created app using VS Code.](/images/existing-resources/app-vscode-web-cli.png) - -**Step 3:** Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal. - -```bash showLineNumbers={false} -amplify pull --appId --envName -``` - -**Step 4:** Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See [Configure the Amplify CLI](/[platform]/tools/cli/start/set-up-cli/#configure-the-amplify-cli) for more information on setting up your AWS profile. - -Accept the default values for the prompts and make sure to answer **Yes** to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud. - -{/* cSpell:disable */} -``` -? Select the authentication method you want to use: AWS profile - -For more information on AWS Profiles, see: -https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html - -? Please choose the profile you want to use AwsWest1 -Amplify AppID found: dfn3u8j1nvzjc. Amplify App name is: reactamplified -Backend environment dev found in Amplify Console app: reactamplified -? Choose your default editor: Visual Studio Code -✔ Choose the type of app that you're building · javascript -Please tell us about your project -? What javascript framework are you using react -? Source Directory Path: src -? Distribution Directory Path: build -? Build Command: npm run-script build -? Start Command: npm run-script start -? Do you plan on modifying this backend? Yes -⠋ Fetching updates to backend environment: dev from the cloud. -⚠️ WARNING: your GraphQL API currently allows public create, read, update, and delete access to all models via an API Key. To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql/authorization-rules - -⠇ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully. - -Edit your schema at /Users/malakamm/development/react-amplify-connect/amplify/backend/api/reactamplified/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/react-amplify-connect/amplify/backend/api/reactamplified/schema -✔ Successfully pulled backend environment dev from the cloud. -Browserslist: caniuse-lite is outdated. Please run: - npx update-browserslist-db@latest - Why you should do it regularly: https://github.com/browserslist/update-db#readme -✅ - -✅ Successfully pulled backend environment dev from the cloud. -Run 'amplify pull' to sync future upstream changes. -``` -{/* cSpell:enable */} - -The Amplify CLI will add a new folder named `amplify` to the app's root folder, which contains the Amplify project and backend details. - -The amplify folder within the file directory of the Amplify app. - -**Step 5**: Use the following command to generate the GraphQL statements. - -```bash showLineNumbers={false} -amplify codegen add -``` - -**Step 6:** Accept the default values of the prompts. - -``` - -? Choose the code generation language target javascript -? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js -? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes -? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 -✔ Generated GraphQL operations successfully and saved at src/graphql - -``` - -The CLI will add a new folder named `graphql` to the app's root folder, which contains the GraphQL statements. - -The graphql folder within the file directory of the Amplify app. - -**Step 7:** Install the aws-amplify and @aws-amplify/ui-react packages by running the following commands. - -```bash showLineNumbers={false} -npm install aws-amplify @aws-amplify/ui-react -``` - -**Step 8:** Update the `App.js` file with the following code to create a login flow using Amplify UI and a form with a button to create to-dos, as well as a way to fetch and render the list of to-dos. - -```bash title="src/App.js" -import React, { useEffect, useState } from 'react' -import { Amplify, API, graphqlOperation } from 'aws-amplify' -import { createTodo } from './graphql/mutations' -import { listTodos } from './graphql/queries' -import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react'; -import '@aws-amplify/ui-react/styles.css'; - -// highlight-start -import awsExports from "./aws-exports"; -Amplify.configure(awsExports); -// highlight-end - -const initialState = { name: '', description: '' } - -const App = ({ signOut, user }) => { - const [formState, setFormState] = useState(initialState) - const [todos, setTodos] = useState([]) - - useEffect(() => { - fetchTodos() - }, []) - - function setInput(key, value) { - setFormState({ ...formState, [key]: value }) - } - - // highlight-start - async function fetchTodos() { - try { - const todoData = await API.graphql(graphqlOperation(listTodos)) - const todos = todoData.data.listTodos.items - setTodos(todos) - } catch (err) { console.log('error fetching todos') } - } - - async function addTodo() { - try { - if (!formState.name || !formState.description) return - const todo = { ...formState } - setTodos([...todos, todo]) - setFormState(initialState) - await API.graphql(graphqlOperation(createTodo, {input: todo})) - } catch (err) { - console.log('error creating todo:', err) - } - } - // highlight-end - - return ( -
- Hello {user.username} - -

Amplify Todos

- setInput('name', event.target.value)} - style={styles.input} - value={formState.name} - placeholder="Name" - /> - setInput('description', event.target.value)} - style={styles.input} - value={formState.description} - placeholder="Description" - /> - - { - todos.map((todo, index) => ( -
-

{todo.name}

-

{todo.description}

-
- )) - } -
- ) -} - -const styles = { - container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 }, - todo: { marginBottom: 15 }, - input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 }, - todoName: { fontSize: 20, fontWeight: 'bold' }, - todoDescription: { marginBottom: 0 }, - button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' } -} - -export default withAuthenticator(App); - - -``` - - -**Step 9:** Run the app using the following command. - -```bash showLineNumbers={false} -npm start -``` - -**Step 10:** Change the UI of the app as shown in the following to update the placeholder text of the to-do form and to use the user’s email for the hello message. - -```bash title="src/App.js" -... - return ( -
- // highlight-next-line - Hello {*user*.attributes.email} - -

Amplify Todos

- setInput('name', event.target.value)} - style={styles.input} - value={formState.name} - // highlight-next-line - placeholder="ToDo Name" - /> - setInput('description', event.target.value)} - style={styles.input} - value={formState.description} - // highlight-next-line - placeholder="ToDo Description" - /> - - { - todos.map((todo, index) => ( -
-

{todo.name}

-

{todo.description}

-
- )) - } -
- ) -.... -``` - -The `App.js` file should now look like the following code snippet. - -```bash title="src/App.js" -import React, { useEffect, useState } from 'react' -import { Amplify, API, graphqlOperation } from 'aws-amplify' -import { createTodo } from './graphql/mutations' -import { listTodos } from './graphql/queries' -import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react'; -import '@aws-amplify/ui-react/styles.css'; - -import awsExports from "./aws-exports"; -Amplify.configure(awsExports); - -const initialState = { name: '', description: '' } - -const App = ({ signOut, user }) => { - const [formState, setFormState] = useState(initialState) - const [todos, setTodos] = useState([]) - - useEffect(() => { - fetchTodos() - }, []) - - function setInput(key, value) { - setFormState({ ...formState, [key]: value }) - } - - async function fetchTodos() { - try { - const todoData = await API.graphql(graphqlOperation(listTodos)) - const todos = todoData.data.listTodos.items - setTodos(todos) - } catch (err) { console.log('error fetching todos') } - } - - async function addTodo() { - try { - if (!formState.name || !formState.description) return - const todo = { ...formState } - setTodos([...todos, todo]) - setFormState(initialState) - await API.graphql(graphqlOperation(createTodo, {input: todo})) - } catch (err) { - console.log('error creating todo:', err) - } - } - - return ( -
- Hello {user.attributes.email} - -

Amplify Todos

- setInput('name', event.target.value)} - style={styles.input} - value={formState.name} - placeholder="ToDo Name" - /> - setInput('description', event.target.value)} - style={styles.input} - value={formState.description} - placeholder="ToDo Description" - /> - - { - todos.map((todo, index) => ( -
-

{todo.name}

-

{todo.description}

-
- )) - } -
- ) -} - -const styles = { - container: { width: 400, margin: '0 auto', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 20 }, - todo: { marginBottom: 15 }, - input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 }, - todoName: { fontSize: 20, fontWeight: 'bold' }, - todoDescription: { marginBottom: 0 }, - button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' } -} - -export default withAuthenticator(App); - -``` - -**Step 11:** Use the following command to publish your changes. Amplify CLI will publish the changes and display the app URL. - -```bash showLineNumbers={false} -amplify publish -``` - -Amplify CLI publish the app and display a URL. - -**Step 12:** Use the URL to run the app in the browser. - - - -### Conclusion - -Congratulations! Your new React app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management, a scalable GraphQL API backed by Amazon DynamoDB, and a hosting service for publishing your app to the cloud. - -### Clean up resources - -Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app. - -```bash showLineNumbers={false} -amplify delete -``` - -If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the [Build & connect backend section](/[platform]/build-a-backend/) for guides on how to add and connect other backend resources. -
diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx deleted file mode 100644 index 161ca3b71b9..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/existing-resources/index.mdx +++ /dev/null @@ -1,33 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; -import { getChildPageNodes } from '@/utils/getChildPageNodes'; - -export const meta = { - title: 'Existing AWS resources', - description: - 'Use the Amplify CLI or AWS CDK to connect to existing AWS resources.', - platforms: [ - 'flutter', - ], - route: '/gen1/[platform]/prev/build-a-backend/existing-resources' -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - const childPageNodes = getChildPageNodes(meta.route); - return { - props: { - platform: context.params.platform, - meta, - childPageNodes - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx deleted file mode 100644 index 0b4019f0afd..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/build-options/index.mdx +++ /dev/null @@ -1,141 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Build options', - description: "Use build options for the function category in Amplify to execute a script before a function is deployed, for example, to transpile Typescript or ES6 with Babel into a format that is supported by the AWS Lambda's node runtime.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/build-options/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - - - - - -In some cases, it might be necessary to execute a script before a function is deployed, e.g. to transpile Typescript or ES6 with Babel or with `tsc` into a format that is supported by the AWS Lambda's node runtime. `amplify push` will look for a `script` definition in the project root's `package.json` with the name `amplify:` and run it right after `npm install` is called in the function resource's `src` directory. - -**Example: Transpiling Typescript code with TSC** - -Make sure you have the `tsc` command installed globally by running `npm install -g typescript` or locally by running `npm install --save-dev typescript` - -Let's say, a function resource has been created with `amplify function add` and it is called `generateReport`. The ES6 source code for this function is located in `amplify/backend/function/generateReport/lib` and the resource's `src` directory only contains the auto-generated `package.json` for this function. In order to compile TypeScript, you have to add the following script definition to your project root's `package.json`: - -```json -{ - "scripts": { - "amplify:generateReport": "cd amplify/backend/function/generateReport && tsc -p ./tsconfig.json && cd -" - }, -} -``` - -Navigate into `amplify/backend/function/generateReport` and create `tsconfig.json` then add the following to it: - -```json -{ - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "lib": ["dom", "esnext"], - "module": "commonjs", - "moduleResolution": "node", - "skipLibCheck": true, - "resolveJsonModule": true, - "outDir": "./src", - "baseUrl": "./", - "rootDir": "./lib", - "paths": { - "src": ["./lib"] - } - } -} -``` - -**Note:** It is important to note that if you are using `aws-sdk` in your TypeScript file, you will get a timeout if you attempt to import it with the following: - -```js -import AWS from 'aws-sdk'; -``` - -Change to this: - -```js -import * as AWS from 'aws-sdk'; -``` - -Once you run `amplify push`, the `amplify:generateReport` script will be executed, either by `yarn` or by `npm` depending on the existence of a `yarn.lock` file in the project root directory. - -**Example: Transpiling ES6 code with Babel** - -Let's say, a function resource has been created with `amplify function add` and it is called `generateReport`. The ES6 source code for this function is located in `amplify/backend/function/generateReport/lib` and the resource's `src` directory only contains the auto-generated `package.json` for this function. In order to run Babel, you have to add the following script definition and dev dependencies to your project root's `package.json`: - -```json -{ - "scripts": { - "amplify:generateReport": "cd amplify/backend/function/generateReport && babel lib -d src && cd -" - }, - "devDependencies": { - "@babel/cli": "^7.5.5", - "@babel/preset-env": "^7.5.5", - } -} -``` - -Babel needs to be configured properly so that the transpiled code can be run on AWS Lambda. This can be done by adding a `.babelrc` file to the resource folder (`amplify/backend/function/generateReport/.babelrc` in this case): - -```json -{ - "presets": [ - [ - "env", - { - "exclude": ["transform-regenerator"], - "targets": { - "node": "10.18" - } - } - ] - ], - "plugins": [ - "transform-async-to-generator", - "transform-exponentiation-operator", - "transform-object-rest-spread" - ] -} -``` - -Once you run `amplify push`, the `amplify:generateReport` script will be executed, either by `yarn` or by `npm` depending on the existence of a `yarn.lock` file in the project root directory. - - - - - -There are no existing build options for Python functions. The process of building and packaging Python functions is in line with Amazon's [existing documentation](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-venv) for manually creating a Lambda deployment package which depends on a virtual environment. - -Amplify will run `pipenv install` in your function's source directory during builds using either Pipenv's default virtual environment, or whichever virtual environment happens to be active. Then, during the packaging stage, the contents of the `site-packages` directory for that virtual environment will be zipped up along with the function-specific files. - -The contents of the Python build can include local development dependencies (e.g. for testing) in addition to those necessary for your function to run. Packages installed as "editable" (using the `-e` flag) will not be packaged, as they are represented as an `.egg-link` file pointing to the local, editable code of the dependency. - - - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx deleted file mode 100644 index 8d6b61eab10..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/configure-options/index.mdx +++ /dev/null @@ -1,114 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure Lambda function settings', - description: 'Learn how to configure custom settings for your Lambda function', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/configure-options/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -You may want to override the Amplify CLI default configurations for your Lambda function or configure changes not available within the `amplify add function` workflow. - -*Example*: When creating a `Node.js` function, the CLI will automatically configure a runtime version, a default memory size, and more. There are a few things you may want to override or configure: - -1. Runtime -2. Memory size -3. Environment variables - -Let's look at how to update all of these things. - -## Updating the Runtime - -You may want to tweak the runtime version to be either a newer or older version than the Amplify-generated default. - -Let's say we've deployed a Lambda function using a Node.js runtime and you want to modify the version of the runtime to be `14.x`. - -To do so, open __amplify/backend/function/function-name/function-name-cloudformation-template.json__ and set the `Runtime` property in the `LambdaFunction` resource to: - -```json -"Resources": { - "LambdaFunction": { - ... - "Properties": { - "Runtime": "nodejs14.x", // Runtime now set to 14.x - "Layers": [], - } - ... - } - }, -} -``` - -Next, deploy the updates using the Amplify CLI: - -```sh -amplify push -``` - -## Updating the default memory size - -When you deploy a function with Amplify, the default memory size will be set to a low setting (128MB). Often you will want to increase the default memory size in order to improve performance. A popular memory setting in Lambda is 1024MB as it speeds the function noticeably while usually keeping the cost the same or close to it. - -To update the memory size, open __amplify/backend/function/function-name/function-name-cloudformation-template.json__ and set the `MemorySize` property in the `LambdaFunction` resource: - -```json -"Resources": { - "LambdaFunction": { - ... - "Properties": { - "Runtime": "nodejs14.x", - "MemorySize": 1024, // Memory size now set to 1024 MB - "Layers": [], - } - ... - } - }, -} -``` - -Next, deploy the updates using the Amplify CLI: - -```sh -amplify push -``` - -_To learn more about optimizing resources allocation for Lambda functions, check out [this](https://dev.to/aws/deep-dive-finding-the-optimal-resources-allocation-for-your-lambda-functions-35a6) blog post._ - -## Setting an environment variable - -A very common scenario is the need to set and use an environment variable in your Lambda function. - -There are generally two types of environment variables: -- [Secret values (example: access keys, API keys etc.)](/[platform]/build-a-backend/functions/secrets/) -- [Non-secret values (example: endpoint information, locale information etc.)](/[platform]/build-a-backend/functions/environment-variables/) - - - -To view all configuration options available in AWS Lambda, check out the documentation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-environment.html) - -To learn more about extending the Amplify CLI with custom resources, check out the documentation [here](/[platform]/tools/cli/custom/cdk/) - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx deleted file mode 100644 index dea2617a370..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/environment-variables/index.mdx +++ /dev/null @@ -1,86 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Environment variables', - description: 'Configure environment variables for AWS Lambda functions', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/environment-variables/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -Amplify CLI allows you to configure environment variables for your Lambda functions. Each Amplify environment can have a different environment variable value. This enables use cases such as switching between dev and prod URLs depending on the environment. - -> Environment variables should NOT be used for storing sensitive configuration values such as database passwords, API keys, or access tokens. Use [function secrets configuration](/[platform]/build-a-backend/functions/secrets/) instead! - -## Configuring environment variables -To configure a new function with environment variables, run `amplify add function`, select `yes` to the advanced settings prompt and select `yes` to the environment variables configuration prompt. From there, you will be able to specify a key and value for the environment variable. - -```console -$ amplify add function -... -? Do you want to configure advanced settings? Yes -... -? Do you want to configure environment variables for this function? Yes -? Enter the environment variable name: API_URL -? Enter the environment variable value: https://example.com/test -? Select what you want to do with environment variables: (Use arrow keys) - Add new environment variable - Update existing environment variables - Remove existing environment variables -> I'm done -``` - -To configure environment variables for an existing function, run `amplify update function`, and select `Environment variables configuration`. You can then add, update, or remove environment variables. - -```console -$ amplify update function -... -? Which setting do you want to update? - Resource access permissions - Scheduled recurring invocation - Lambda layers configuration -> Environment variables configuration - Secret values configuration -? Select what you want to do with environment variables: -> Add new environment variable - Update existing environment variables - Remove existing environment variables - I'm done -``` - -## Multi-environment flows -When creating a new Amplify environment using `amplify env add`, Amplify CLI asks if you want to apply all environment variable values to the new environment or modify them. If you choose to apply the existing values, you can still make edits anytime by running `amplify update function`. - -When creating a new Amplify environment using `amplify env add --yes`, Amplify CLI will apply all environment variable values from the current environment to the new environment. - -In multi-environment workflows, you may have added a new environment variable in one Amplify environment and then checked out a different Amplify environment. In this case, on the next `amplify push`, Amplify CLI will detect that there is a new environment variable that does not have a value specified in the current environment and prompt for one. -Running `amplify push --yes` in this case will fail with a message explaining the missing environment variable values. - -In [**git-based** multi-environment workflows](/[platform]/tools/cli/teams/), you may run into errors during deployment. For example, this happens when you add an environment variable in `envA` (corresponding to a git branch `branchA`), then `amplify checkout envB` and `git checkout branchB` and `git merge` branchA into branchB. Upon pushing `envB`, Amplify CLI detects that a new environment variable has been added but can't infer a value for it. To resolve this issue, run the following commands in the terminal: - -1. `amplify env checkout ` -2. `amplify push` - when prompted, enter a new value for the environment variable(s) -3. `git commit` -4. `git push` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx deleted file mode 100644 index 51cbb4bed84..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/graphql-from-lambda/index.mdx +++ /dev/null @@ -1,504 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Call a GraphQL API from a Lambda function', - description: 'Interact with a GraphQL API from a Lambda function.', - platforms: [ - 'flutter', - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -You can call an AppSync GraphQL API from a Node.js app or a Lambda function. Take a basic `Todo` app as an example: - -```graphql -type Todo @model @auth(rules: [{ allow: public }]) { - name: String - description: String -} -``` - -This API will have operations available for `Query`, `Mutation`, and `Subscription`. Let's take a look at how to perform both a **query** as well as a **mutation** from a Lambda function using Node.js. - -## Utilizing Lambda function template (IAM authorization) - -First, create a Lambda function with `amplify add function` and choose the `AppSync - GraphQL API request (with IAM)` to get started. Be sure to grant access to your GraphQL API when prompted by the CLI to grant access to other resources in the project. Alternatively, you can create the [function from scratch](#create-from-scratch). - -```console -amplify add function -? Select which capability you want to add: Lambda function (serverless function) -? Provide an AWS Lambda function name: myfunction -? Choose the runtime that you want to use: NodeJS -? Choose the function template that you want to use: AppSync - GraphQL API request (with IAM) - -Available advanced settings: -- Resource access permissions -- Scheduled recurring invocation -- Lambda layers configuration -- Environment variables configuration -- Secret values configuration - -? Do you want to configure advanced settings? Yes -? Do you want to access other resources in this project from your Lambda function? Yes -? Select the categories you want this function to have access to. api -? Select the operations you want to permit on Query, Mutation, Subscription - -You can access the following resource attributes as environment variables from your Lambda function - API__GRAPHQLAPIENDPOINTOUTPUT - API__GRAPHQLAPIIDOUTPUT - API__GRAPHQLAPIKEYOUTPUT - ENV - REGION -``` - - - -The function can only be added when the GraphQL API with IAM authorization exists. - - - -### Create from scratch - -```console -amplify add function -? Select which capability you want to add: Lambda function (serverless function) -? Provide an AWS Lambda function name: myfunction -? Choose the runtime that you want to use: NodeJS -? Choose the function template that you want to use: Hello World - -Available advanced settings: -- Resource access permissions -- Scheduled recurring invocation -- Lambda layers configuration -- Environment variables configuration -- Secret values configuration - -? Do you want to configure advanced settings? Yes -? Do you want to access other resources in this project from your Lambda function? Yes -? Select the categories you want this function to have access to. api -? Select the operations you want to permit on Query, Mutation, Subscription - -You can access the following resource attributes as environment variables from your Lambda function - API__GRAPHQLAPIENDPOINTOUTPUT - API__GRAPHQLAPIIDOUTPUT - API__GRAPHQLAPIKEYOUTPUT - ENV - REGION -``` - -The examples on this page use [`node-fetch`](https://www.npmjs.com/package/node-fetch) to make a HTTP request to your GraphQL API. When the Node.js v18 runtime is released for Lambda this dependency can be removed in favor of [native `fetch`](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#fetch) To get started, add the `node-fetch` module as a dependency: - -**CommonJS**: - -For functions written using CommonJS, you will need to install version 2 of `node-fetch` - -```diff -{ - "name": "myfunction", - "version": "2.0.0", - "description": "Lambda function generated by Amplify", - "main": "index.js", - "license": "Apache-2.0", -+ "dependencies": { -+ "node-fetch": "2" -+ }, - "devDependencies": { - "@types/aws-lambda": "^8.10.92" - } -} -``` - -**ESM**: - -```diff -{ - "name": "myfunction", -+ "type": "module", - "version": "2.0.0", - "description": "Lambda function generated by Amplify", - "main": "index.js", - "license": "Apache-2.0", -+ "dependencies": { -+ "node-fetch": "^3.2.3" -+ }, - "devDependencies": { - "@types/aws-lambda": "^8.10.92" - } -} -``` - -## Query - -Using an API Key for authenticating your requests, you can query the GraphQL API to get a list of all `Todo`s. To paginate over the list queries, you need to pass in a `limit` and `nextToken` on the `listTodos` query. See more at [GraphQL pagination ](/[platform]/build-a-backend/graphqlapi/query-data/). - -{/* prettier-ignore */} -```js -import { default as fetch, Request } from 'node-fetch'; - -const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; -const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; - -const query = /* GraphQL */ ` - query LIST_TODOS { - listTodos { - items { - id - name - description - } - } - } -`; - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -export const handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - - /** @type {import('node-fetch').RequestInit} */ - const options = { - method: 'POST', - headers: { - 'x-api-key': GRAPHQL_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ query }) - }; - - const request = new Request(GRAPHQL_ENDPOINT, options); - - let statusCode = 200; - let body; - let response; - - try { - response = await fetch(request); - body = await response.json(); - if (body.errors) statusCode = 400; - } catch (error) { - statusCode = 400; - body = { - errors: [ - { - status: response.status, - message: error.message, - stack: error.stack - } - ] - }; - } - - return { - statusCode, - body: JSON.stringify(body) - }; -}; -``` - -## Mutation - -In this example you will create a mutation showing how to pass in variables as arguments to create a `Todo` record. - -{/* prettier-ignore */} -```js -import { default as fetch, Request } from 'node-fetch'; - -const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; -const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; - -const query = /* GraphQL */ ` - mutation CREATE_TODO($input: CreateTodoInput!) { - createTodo(input: $input) { - id - name - createdAt - } - } -`; - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -export const handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - - const variables = { - input: { - name: 'Hello, Todo!' - } - }; - - /** @type {import('node-fetch').RequestInit} */ - const options = { - method: 'POST', - headers: { - 'x-api-key': GRAPHQL_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ query, variables }) - }; - - const request = new Request(GRAPHQL_ENDPOINT, options); - - let statusCode = 200; - let body; - let response; - - try { - response = await fetch(request); - body = await response.json(); - if (body.errors) statusCode = 400; - } catch (error) { - statusCode = 400; - body = { - errors: [ - { - status: response.status, - message: error.message, - stack: error.stack - } - ] - }; - } - - return { - statusCode, - body: JSON.stringify(body) - }; -}; -``` - -## IAM Authorization - -Let's take a look at another example schema that uses `iam` authorization. - -```graphql -type Todo @model @auth(rules: [{ allow: private, provider: iam }]) { - name: String - description: String -} -``` - -The CLI will automatically configure the Lambda execution IAM role to call the GraphQL API. Before writing your Lambda function you will first need to install the appropriate AWS SDK v3 dependencies: - -```diff -{ - "name": "myfunction", -+ "type": "module", - "version": "2.0.0", - "description": "Lambda function generated by Amplify", - "main": "index.js", - "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-crypto/sha256-js": "^2.0.1", -+ "@aws-sdk/credential-provider-node": "^3.76.0", -+ "@aws-sdk/protocol-http": "^3.58.0", -+ "@aws-sdk/signature-v4": "^3.58.0", -+ "node-fetch": "^3.2.3" -+ }, - "devDependencies": { - "@types/aws-lambda": "^8.10.92" - } -} -``` - -Then, the following example will sign the request to call the GraphQL API using IAM authorization. - -{/* due to placeholder text */} - -{/* prettier-ignore */} -```js -import crypto from '@aws-crypto/sha256-js'; -import { defaultProvider } from '@aws-sdk/credential-provider-node'; -import { SignatureV4 } from '@aws-sdk/signature-v4'; -import { HttpRequest } from '@aws-sdk/protocol-http'; -import { default as fetch, Request } from 'node-fetch'; - -const { Sha256 } = crypto; -const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; -const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; - -const query = /* GraphQL */ ` - query LIST_TODOS { - listTodos { - items { - id - name - description - } - } - } -`; - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -export const handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - - const endpoint = new URL(GRAPHQL_ENDPOINT); - - const signer = new SignatureV4({ - credentials: defaultProvider(), - region: AWS_REGION, - service: 'appsync', - sha256: Sha256 - }); - - const requestToBeSigned = new HttpRequest({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - host: endpoint.host - }, - hostname: endpoint.host, - body: JSON.stringify({ query }), - path: endpoint.pathname - }); - - const signed = await signer.sign(requestToBeSigned); - const request = new Request(GRAPHQL_ENDPOINT, signed); - - let statusCode = 200; - let body; - let response; - - try { - response = await fetch(request); - body = await response.json(); - if (body.errors) statusCode = 400; - } catch (error) { - statusCode = 500; - body = { - errors: [ - { - message: error.message - } - ] - }; - } - - return { - statusCode, - body: JSON.stringify(body) - }; -}; -``` - -### CommonJS - -When writing functions with CommonJS, you will need to install version 2 of `node-fetch`: - -```diff -{ - "name": "myfunction", - "version": "2.0.0", - "description": "Lambda function generated by Amplify", - "main": "index.js", - "license": "Apache-2.0", -+ "dependencies": { -+ "@aws-crypto/sha256-js": "^2.0.1", -+ "@aws-sdk/credential-provider-node": "^3.76.0", -+ "@aws-sdk/protocol-http": "^3.58.0", -+ "@aws-sdk/signature-v4": "^3.58.0", -+ "node-fetch": "2" -+ }, - "devDependencies": { - "@types/aws-lambda": "^8.10.92" - } -} -``` - -Similar to the example above you can now write your handler. The difference here is the use of `require()` rather than `import ... from` - -```js -const { Sha256 } = require('@aws-crypto/sha256-js'); -const { defaultProvider } = require('@aws-sdk/credential-provider-node'); -const { SignatureV4 } = require('@aws-sdk/signature-v4'); -const { HttpRequest } = require('@aws-sdk/protocol-http'); -const { default: fetch, Request } = require('node-fetch'); - -const GRAPHQL_ENDPOINT = - process.env.API_ < YOUR_API_NAME > _GRAPHQLAPIENDPOINTOUTPUT; -const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; - -const query = /* GraphQL */ ` - query LIST_TODOS { - listTodos { - items { - id - name - description - } - } - } -`; - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -exports.handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - - const endpoint = new URL(GRAPHQL_ENDPOINT); - - const signer = new SignatureV4({ - credentials: defaultProvider(), - region: AWS_REGION, - service: 'appsync', - sha256: Sha256 - }); - - const requestToBeSigned = new HttpRequest({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - host: endpoint.host - }, - hostname: endpoint.host, - body: JSON.stringify({ query }), - path: endpoint.pathname - }); - - const signed = await signer.sign(requestToBeSigned); - const request = new Request(GRAPHQL_ENDPOINT, signed); - - let statusCode = 200; - let body; - let response; - - try { - response = await fetch(request); - body = await response.json(); - if (body.errors) statusCode = 400; - } catch (error) { - statusCode = 500; - body = { - errors: [ - { - message: error.message - } - ] - }; - } - - return { - statusCode, - body: JSON.stringify(body) - }; -}; -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx deleted file mode 100644 index d5d8b2ddc40..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/index.mdx +++ /dev/null @@ -1,40 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; -import { getChildPageNodes } from '@/utils/getChildPageNodes'; - -export const meta = { - title: 'Functions', - description: 'Build and deploy serverless functions to perform various tasks, such as handling API requests, executing backend logic, or integrating with other AWS services.', - platforms: [ - 'flutter', - ], - route: '/gen1/[platform]/prev/build-a-backend/functions', - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - const childPageNodes = getChildPageNodes(meta.route); - return { - props: { - platform: context.params.platform, - meta, - childPageNodes - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx deleted file mode 100644 index 6ebdbfaf42b..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/layers/index.mdx +++ /dev/null @@ -1,229 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Reuse code and assets using layers', - description: "Use Amplify CLI's Lambda layer capability to reuse code & assets across functions.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/layers/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -Lambda layers allow you to pull common code & assets for your Lambda function into a centralized location. With Lambda layers you can: - -1. _*Re-use your code & assets*_: Your Lambda functions can leverage these layers to reuse shared code & assets across functions -2. _*Speed up function deployments*_: Iterating on your Lambda function will be significantly faster because it can be independently updated from the layer which usually contains the bulk of large static content - -> **Known limitation**: Functions using a layer can't be mocked locally using `amplify mock`. We recommend you to create a dev environment and test the functions inside the AWS Lambda console. - -![Lambda layer architecture diagram](/images/layers-architecture.gif) - -The general workflow breaks down into the following steps: - -1. Create a Lambda layer -2. Add shared code & assets to the layer -3. Add the Lambda layer to a function -4. Deploy the layer & function - -## Create a Lambda Layer - -To create a layer, run the following command within your Amplify project: - -```bash -amplify add function -``` - -Select `Lambda layer` as the capability to add - -```console -? Select which capability you want to add: -> Lambda layer (shared code & resource used across functions) -``` - -```console -? Provide a name for your Lambda layer: (layer-name) -? Choose the runtime that you want to use: (Use arrow keys) -❯ NodeJS - Python -``` - -Next, you'll be guided through a workflow to provide a **layer name**, and select a **supported runtime**. Currently Amplify CLI provides NodeJS or Python runtime support for layers. - -```console -? The current AWS account will always have access to this layer. - Optionally, configure who else can access this layer. (Hit to skip) -◯ Specific AWS accounts -◯ Specific AWS organization -◯ Public (everyone on AWS can use this layer) -``` - -After that, you'll be asked to configure your **layer's permission**. - -The **current AWS account will always have access to this layer**. -In addition, the customer can configure access for: - -- **Specific AWS accounts**: provide a comma-separated list of AWS Account IDs to provide access to them. -- **Specific AWS organizations**: provide a comma-separated list of AWS Organization IDs to provide access to them. *AWS Organization IDs start with `o-`.* -- **Public**: make this layer available for everyone AWS. Anyone in AWS can reference this layer using its ARN. - -```console -Next steps: -Move your libraries to the following folder: -[NodeJS]: amplify/backend/function//lib/nodejs - -Include any files you want to share across runtimes in this folder: -amplify/backend/function//opt - -"amplify function update " - configure a function with this Lambda layer -"amplify push" - builds all of your local backend resources and provisions them in the cloud -``` - -Your Lambda layer is ready to use after the permissions are set up. - -## Add shared code & assets - -Now that your layer is set up, you'll see a new folder with the `layer-name` added to `amplify/backend/function/`. The respective runtime's folder structure is autogenerated. - -### Add shared code - - - - - -A `nodejs` folder is auto-generated for you. In there you'll find an empty `package.json` file and a `node_modules` folder. If you want to offload other node_modules you can either: - -1. `cd` into the `nodejs` folder and add the dependencies into the `package.json` file, or -2. move all your existing function's `node_modules` content into the layer's `node_modules` folder - -Any dependency listed within the layer's `package.json` file will be installed and packaged during `amplify push`. - -Any node module that is in the layer's `node_modules` folder can be accessed from the function as if the node module is in the function's `node_modules` folder. - -*In order to take advantage of Lambda layer's for your NodeJS function, you don't even need to update your function's code!* - - - - - -A `python` folder is auto-generated for you. In there you'll find an empty `Pipfile` file. Any packages listed within the layer's `Pipfile` file will be installed and packaged during `amplify push`. You can `import` these packages from within your Python function just like any other package within your Python function. - - - - - -### Add shared assets - -Any assets like large images or other files that you want to share across various functions can be placed in the `amplify/backend/function//opt/` folder. Your function's code can import any assets by looking for files in the `/opt/` path. - -### Lambda layer versions - -Every time `amplify push` or `amplify update function` is run, Amplify CLI checks if a layer's content has changed and automatically creates a new *layer version*. Layer versions are immutable and functions always use a specific layer version. - -In order to speed up deployments when vast amount of node_modules exist, Amplify CLI scans only for changes within each module's `package.json` file. If you don't see Amplify CLI detect your latest changes, verify that at least of your node module's `package.json` content has changed. - -## Add a layer to a function - -You can either create a new function and add Lambda layers by running `amplify add function` or add layers to an existing function using `amplify update function`. Select `Lambda function` when prompted and you'll be presented the following question during the guided flow: - -```console -... -? Do you want to enable Lambda layers for this function? Yes -? Provide existing layers or select layers in this project to access from this function (pick up to 5): - ◯ Provide existing Lambda layer ARNs -❯◉ myamplifylayer1 - ◯ myamplifylayer2 -``` - -You can either add an existing layer in AWS by referencing its ARN or select a layer from your Amplify project that's listed below. - -```console -? Select a version for myamplifylayer1: -❯ Always choose latest version - 2: Updated layer version 2021-06-08T05:33:42.651Z - 1: Updated layer version 2021-06-08T05:30:43.101Z -``` - -When adding a layer from your Amplify project, you'll also be able to select a specific layer version or always choose the latest layer version. The largest layer version number represents the most recent changes. - -```console -? Modify the layer order: -(Layers with conflicting files will overwrite contents of layers earlier in the list): -- layer2 -- layer3 -- layer6 -- -- -``` - -Given that layers can have overlapping contents, the order of the layer matters. You can adjust the layer's order if needed in the next step. - -Now, you've successfully added a layer to your function. - -## Deploy Lambda layers & functions with Lambda layers - -Once you're ready with your changes in your layer and functions, you can deploy them by running `amplify push`. - -If a layer’s content has been updated and it has permissions associated, Amplify CLI will prompt you whether you want to carry the permissions forward to a newer version. - -```console -Content changes in Lambda layers detected. -Suggested configuration for new layer versions: - -myamplifylayer1 - - Description: Updated layer version 2021-06-08T05:33:42.651Z - -? Accept the suggested layer version configurations? (Y/n) -``` - -During `amplify push`, you get to modify the layer version description. By default, Amplify CLI will populate the description as `Updated layer version `. - -## Update layer content - -Any file changes within a layer's folder are automatically tracked by Amplify CLI. If there are changes available, the Amplify CLI will create a new layer version with the changes. - -## Update layer settings - -You can update layer's permissions by running `amplify update function` and selecting `Lambda layer`. - -Next, you'll be prompted to select the layer for which you want to update the settings for. - -#### Note: Update Layer Permissions from Public to Specific - - To update a lambda layer from Public access to Specific (Account/Organization) access, please remember to remove Public access by **un-selecting** the option in the 'amplify update' CLI flow before selecting a specific AWS account/organization. - - If you have already selected 'Public' access, just adding additional 'specific' AWS accounts/organizations will not have any effect on the Lambda Layer configuration. It will not automatically remove Public access. - -## Remove a layer - -To remove a Lambda layer, run the `amplify function remove` command and select `Lambda layers`. Next, you'll be prompted to select which layer to remove. You can delete specific layer versions or all of them. - -> Warning: When you delete a layer, you can no longer configure functions to use it. However, any function that already uses the layer continues to have access to it. - -```console -? Choose the resource you would want to remove (layer) -When you delete a layer version, you can no longer configure functions to use it. -However, any function that already uses the layer version continues to have access to it. -? Choose the Layer versions you want to remove. -❯◯ 1: Updated layer version 2021-06-08T05:30:43.101Z - ◯ 2: Updated layer version 2021-06-08T05:33:42.651Z -? Are you sure you want to delete the resource? This action deletes all files related to this resource from the backend directory. (Y/n) -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx deleted file mode 100644 index fcf0f3862cc..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/secrets/index.mdx +++ /dev/null @@ -1,105 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Access secret values', - description: 'Configure Lambda functions to securely access secret values', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/functions/secrets/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - - -Amplify CLI allows you to configure secret values that can be securely accessed from a Lambda function. Each Amplify environment can have a different secret value. This enables use cases such as different API keys for a dev and prod environment. Secrets should be used for values such as database passwords, API keys, and access tokens. - -> To access non-sensitive configuration values in a Lambda function, use [environment variables](/[platform]/build-a-backend/functions/environment-variables/). - -## Configuring secret values -To configure a new function with secret values, run `amplify add function`, select `yes` to the advanced settings prompt and select `yes` to the secrets configuration prompt. From there, you can specify the name and value of the secret. - -```console -$ amplify add function -... -? Do you want to configure advanced settings? Yes -... -? Do you want to configure secret values this function can access? Yes -? Enter a secret name (this is the key used to look up the secret value): API_KEY -? Enter the value for API_KEY: [hidden] -? What do you want to do? (Use arrow keys) - Add a secret - Update a secret - Remove secrets -> I'm done -``` - -To configure secrets for an existing function, run `amplify update function`, and select `Secret values configuration`. You can then add, update and remove secret values. - -```console -$ amplify update function -... -? Which setting do you want to update? - Resource access permissions - Scheduled recurring invocation - Lambda layers configuration - Environment variables configuration -> Secret values configuration -? What do you want to do? -> Add a secret - Update a secret - Remove secrets - I'm done -``` - -> Note: Amplify CLI never stores secrets locally. All secret values are immediately stored in [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) using the SecureString parameter type. - -## Accessing the values in your function -To access the secret values in your Lambda function, use the [AWS SSM GetParameter API](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html). Amplify CLI will automatically supply the SSM parameter name of the secret as an environment variable to the function. This value can be passed into the API call as the "Name" to retrieve the value. Ensure that the API call has "WithDecryption" specified as `true`. - -If your Lambda function is using the Node.js runtime, a comment block will be placed at the top of your `index.js` file with example code to retrieve the secret values. - -```js -const aws = require('aws-sdk'); - -const { Parameters } = await (new aws.SSM()) - .getParameters({ - Names: ["EXAMPLE_SECRET_1", "EXAMPLE_SECRET_2"].map(secretName => process.env[secretName]), - WithDecryption: true, - }) - .promise(); - -// Parameters will be of the form { Name: 'secretName', Value: 'secretValue', ... }[] -``` - -## Multi-environment flows -When creating a new Amplify environment using `amplify env add`, Amplify CLI asks if you want to apply all secret values to the new environment or modify them. If you choose to apply the existing values, you can still make edits anytime using `amplify update function`. - -When creating a new Amplify environment using `amplify env add --envName --yes`, Amplify CLI will apply all secret values from the current environment to the new environment. - -In multi-environment workflows, you may have added a new secret in one Amplify environment and then checked out a different Amplify environment. In this case, on the next `amplify push`. Amplify CLI will detect that there is a new secret that does not have a value specified in the current environment and prompt for one. Running `amplify push --yes` in this case will fail with a message explaining the missing secret values. - -In [**git-based** multi-environment workflows](/[platform]/tools/cli/teams/), you may run into errors during deployment. For example, this happens when you add a secret in `envA` (corresponding to a git branch `branchA`), then `amplify env checkout envB` and `git checkout branchB` and `git merge` branchA into branchB. Upon pushing `envB`, Amplify CLI detects that a new secret has been added but can't infer a value for it. To resolve this issue, run the following commands in the terminal: - -1. `amplify env checkout ` -2. `amplify push` - when prompted, enter a new value for the secret(s) -3. `git commit` -4. `git push` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx deleted file mode 100644 index 98144a46542..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/functions/set-up-function/index.mdx +++ /dev/null @@ -1,161 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Set up a function', - description: 'Use Amplify CLI to add powerful Lambda functions to your cloud-based mobile and web app with a simple guided workflow.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter' - ], - canonicalPath: '/javascript/build-a-backend/functions/set-up-function/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -## Set up a function - -You can add a Lambda function to your project which you can use alongside a REST API or as a datasource in your GraphQL API using the [`@function` directive](/[platform]/build-a-backend/graphqlapi/custom-business-logic/#lambda-function-resolver). - -```bash -amplify add function -``` - -```console -? Select which capability you want to add: Lambda function (serverless function) -? Provide a friendly name for your resource to be used as a label for this category in the project: lambdafunction -? Provide the AWS Lambda function name: lambdafunction -? Choose the runtime that you want to use: NodeJS -? Choose the function template that you want to use: (Use arrow keys) -> Hello world function - CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB) - Serverless express function (Integration with Amazon API Gateway) -``` - -## Function templates - -- The `Hello World function` will create a basic hello world Lambda function -- The `CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB)` function will add a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) Lambda function template for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command) -- The `Serverless express function (Integration with Amazon API Gateway)` will add a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) Lambda function template with routing enabled for your REST API paths. - -You can update the Lambda execution role policies for your function to access other resources generated and maintained by the CLI using the CLI. - -```console -$ amplify update function -Please select the Lambda Function you would want to update: lambdafunction -? Which setting do you want to update? Resource access permissions -? Select the category (Press to select,
to toggle all, to invert selection) -> api - function - storage - auth -? Select the operations you want to permit on (Press to select, to toggle all, to invert selection) -> Query - Mutation - Subscription - -You can access the following resource attributes as environment variables from your Lambda function - API__GRAPHQLAPIENDPOINTOUTPUT - API__GRAPHQLAPIIDOUTPUT - API__GRAPHQLAPIKEYOUTPUT -``` - -Behind the scenes, the CLI automates populating of the resource identifiers for the selected resources as Lambda environment variables which you will see in your function code as well. This process additionally configures CRUD level IAM policies on the Lambda execution role to access these resources from the Lambda function. For instance, you might grant permissions to your Lambda function to read/write to a DynamoDB table in the Amplify project by using the above flow and the appropriate IAM policy would be set on that Lambda function's execution policy which is scoped to that table only. - -## Supported Lambda runtimes - -Amplify CLI enables you to create, test and deploy Lambda functions with the following runtimes: - -| Runtime | Default Version | Requirements | -| --- | --- | --- | -| NodeJS | 14.x | - Install [NodeJS](https://nodejs.org/en/) | -| Java | 11 | - Install [Java 11 JDK](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) and [Gradle 5+](https://docs.gradle.org/current/userguide/installation.html) | -| Go | 1.x | - Install [Go](https://golang.org/doc/install) | -| .NET Core | 3.1 | - Install [.NET Core SDK](https://docs.microsoft.com/en-us/dotnet/core/install/sdk) | -| Python | 3.8.x | - Install [python3](https://www.python.org/downloads/) and [pipenv](https://pypi.org/project/pipenv/)
- Ensure `python3` and `pipenv` commands are available in your `PATH` | - -In order to create and test Lambda functions locally, you need to have the runtime's requirements (table above) fulfilled. You'll be asked to `Choose the runtime you would like to use:` when running `amplify add function`. - -Once a runtime is selected, you can select a function template for the runtime to help bootstrap your Lambda function. - -## Access existing AWS resource from Lambda Function - -You can grant your Lambda function access to your existing resources. After running `amplify add function`, the CLI generates a `custom-policies.json` file under the folder `amplify/backend/function//`. The file is where you can specify the [resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html) and [actions](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html) that grant Lambda Function access to the specified AWS resources. - -### File Structure - -```json -[ - { - "Action": ["s3:CreateBucket"], - "Resource": ["arn:aws:s3:::*"] - } -] -``` - -**Action:** Specify the actions that are required to be granted to your AWS resource. Wild characters ‘\*’ is accepted. - -**Resource**: Specify resources that the AWS resource needs access. The resource accepts multiple Arns for a service and wild card character ‘\*’ is accepted. - -> Note: Specifying resource or action as ‘\*’ is not recommended as best practice. This gives the Amplify function resource Administrative privileges which should be avoided. - -If your Amplify resource requires access to multiple AWS services and resources, create another block to grant access to the additional services and resources. - -```json -[ - { - "Action": ["s3:CreateBucket"], - "Resource": ["arn:aws:s3:::*"] - }, - { - "Action": ["iam:GetPolicy"], - "Resource": ["arn:aws:iam:::policy/*"] - } -] -``` - -Optionally, the `Effect` field can be specified to use ‘Allow’ or ‘Deny’. If not specified the field defaults to ‘Allow’ - -```json -{ - "Action": ["s3:CreateBucket"], - "Resource": ["arn:aws:s3:::*"], - "Effect": "Allow" -} -``` - -### Multi-Environment Workflow - -To specify AWS ARN resources across environments, an optional `\${env}` parameter can be used for resources. The `\${env}` parameter in the AWS ARN resource will get populated with the current Amplify environment name at deployment. - -```json -"Resource": ["arn:aws:s3:::${env}my-bucket"] -``` - -### Next Step - -On running `amplify push` commands, the IAM policies specified in the `custom-policies.json` file will be appended to the existing IAM policy list tied to the Lambda Function's execution role. - -## Schedule recurring Lambda functions - -Amplify CLI allows you to schedule Lambda functions to be executed periodically (e.g every minute, hourly, daily, weekly, monthly or yearly). You can also formulate more complex schedules using AWS Cron Expressions such as: _“10:15 AM on the last Friday of every month”_. Review the [Schedule Expression for Rules documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions) for more details. - -To schedule your Lambda function, answer **Yes** to `Do you want to invoke this function on a recurring schedule?` in the `amplify add function` flow. Once you deploy a function, it'll create a CloudWatch Rule to periodically execute the selected Lambda function. - -For more information on files generated in the function resource folder, see [Function Category Files](/[platform]/tools/cli/reference/files/#function-category-files). diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx deleted file mode 100644 index 89692baeb1d..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/index.mdx +++ /dev/null @@ -1,155 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Batch put custom resolver', - description: 'Leverage GraphQL mutations to efficiently create multiple objects in one request rather than making sequential requests to create each object individually.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -Sometimes you need to create objects in bulk, rather than creating individual objects sequentially and waiting for all the requests to complete. - -1. Define your schema with a custom mutation. The custom mutation should not be deployed to AppSync beforehand if following these steps, the CLI will attach its own resolver preventing you from attaching a custom resource this way. -```graphql -type Todo @model { - id: ID! - name: String! - description: String -} - -type Mutation { - batchCreateTodo(todos: [BatchCreateTodo]): [Todo] -} - -input BatchCreateTodo { - id: ID - name: String! - description: String -} -``` - -2. [Create a custom resource for your resolver](/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) and use the following code snippets as a guide to get started - -2. Follow the steps for creating a custom resolver: -```bash -amplify add custom -``` -```console -? How do you want to define this custom resource? -❯ AWS CDK -? Provide a name for your custom resource -❯ MyCustomResolvers -``` - -Next, install the AppSync dependencies for your custom resource: -```bash -cd amplify/backend/custom/MyCustomResolvers -npm i @aws-cdk/aws-appsync@~1.124.0 -``` - -Use the following template as a starting point for your custom CDK stack, the resolvers must be templated with environment references - -```ts -import * as cdk from '@aws-cdk/core'; -import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; -import * as appsync from '@aws-cdk/aws-appsync'; -import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; - -export class cdkStack extends cdk.Stack { - constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) { - super(scope, id, props); - /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ - new cdk.CfnParameter(this, 'env', { - type: 'String', - description: 'Current Amplify CLI env name', - }); - - // Access other Amplify Resources - const retVal:AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this, - amplifyResourceProps.category, - amplifyResourceProps.resourceName, - [{ - category: "api", - resourceName: "" - }] - ); - - const requestVTL = ` - ## [Start] Initialization default values. ** - $util.qr($ctx.stash.put("defaultValues", $util.defaultIfNull($ctx.stash.defaultValues, {}))) - #set( $createdAt = $util.time.nowISO8601() ) - #set($todosArray = []) - #foreach($item in \${ctx.args.todos}) - $util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId()))) - $util.qr($item.put("createdAt", $util.defaultIfNull($item.createdAt, $createdAt))) - $util.qr($item.put("updatedAt", $util.defaultIfNull($item.updatedAt, $createdAt))) - $util.qr($item.put("__typename", "Todo")) - $util.qr($todosArray.add($util.dynamodb.toMapValues($item))) - #end - ## [End] Initialization default values. ** - $util.toJson( { - "version": "2018-05-29", - "operation": "BatchPutItem", - "tables": { - "-${apiIdRef}-${envRef}": $todosArray - } - } ) - ` - const responseVTL = ` - ## [Start] ResponseTemplate. ** - #if( $ctx.error ) - $util.error($ctx.error.message, $ctx.error.type) - #else - $util.toJson($ctx.result.data.-${apiIdRef}-${envRef}) - #end - ## [End] ResponseTemplate. ** - `; - - - const resolver = new appsync.CfnResolver(this, "custom-resolver", { - // apiId: retVal.api.new.GraphQLAPIIdOutput, - // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 - // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. - // Previously the ApiId is the variable Name which is wrong , it should be variable value as below - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - fieldName: "querySomething", - typeName: "Query", // Query | Mutation | Subscription - requestMappingTemplate: requestVTL, - responseMappingTemplate: responseVTL, - dataSourceName: "TodoTable" // DataSource name - }) - } -} -``` - -By using CloudFormation parameters, you contextualize your custom resolvers to the environment you're working with. - -3. Run `amplify push` and deploy your API - -The full documentation for custom resolvers [is available here](/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/#vtl-resolver) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx deleted file mode 100644 index ad7f704d76a..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/index.mdx +++ /dev/null @@ -1,40 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; -import { getChildPageNodes } from '@/utils/getChildPageNodes'; - -export const meta = { - title: 'Best practice', - description: 'Best practices and examples for working with GraphQL.', - platforms: [ - 'flutter', - ], - route: '/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice', - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - const childPageNodes = getChildPageNodes(meta.route); - return { - props: { - platform: context.params.platform, - meta, - childPageNodes - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx deleted file mode 100644 index d5d585c2781..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/query-with-sorting/index.mdx +++ /dev/null @@ -1,136 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'GraphQL query with sorting by date', - description: 'How to implement sorting in a GraphQL query', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/query-with-sorting/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -In this guide you will learn how to implement sorting in a GraphQL API. In our example, you will implement sorting results by date in either an ascending or descending order by implementing an additional data access pattern leveraging a DynamoDB Global Secondary Index using the `@index` GraphQL Transformer directive. - -### Overview - -To get started, let's start with a basic GraphQL schema for a Todo app: - -```graphql -type Todo @model { - id: ID! - title: String! -} -``` - -When the API is created with an `@model` directive, the following queries will automatically be created for you: - -```graphql -type Query { - getTodo(id: ID!): Todo - listTodos( - filter: ModelTodoFilterInput - limit: Int - nextToken: String - ): ModelTodoConnection -} -``` - -Next, take a look at the `ModelTodoConnection` type to get an idea of the data that will be returned when the `listTodos` query is run: - -```graphql -type ModelTodoConnection { - items: [Todo] - nextToken: String -} -``` - -By default, the `listTodos` query will return the `items` array **unordered**. Many times you will need these items to be ordered by title, by creation date, or in some other way. - -To enable this, you can use the [@index](/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/) directive. This directive will allow you to set a custom `sortKey` on any field in your API. - -### Implementation - -In this example, you will enable sorting by the `createdAt` field. By default, Amplify will populate this `createdAt` field with a timestamp if none is passed in. - -To enable this, update your schema with the following: - -```graphql -type Todo @model { - id: ID! - title: String! - type: String! - @index( - name: "todosByDate" - queryField: "todosByDate" - sortKeyFields: ["createdAt"] - ) - createdAt: String! -} -``` - - - -When created a Todo, you must now populate the `type` field for this to work properly. - - - -Next, create a few todos being sure to populate the `type` field: - -```graphql -mutation createTodo { - createTodo(input: { title: "Todo 1", type: "Todo" }) { - id - title - } -} -``` - -Now, you can query for todos by date in an ascending or descending order using the new `todosByDate` query: - -```graphql -query todosByDate { - todosByDate(type: "Todo", sortDirection: ASC) { - items { - id - title - createdAt - } - } -} - -query todosByDateDescending { - todosByDate(type: "Todo", sortDirection: DESC) { - items { - id - title - createdAt - } - } -} -``` - -To learn more about the `@index` directive, check out the documentation [here](/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx deleted file mode 100644 index d931dfe4b94..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/best-practice/warehouse-management/index.mdx +++ /dev/null @@ -1,556 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Warehouse Management System', - description: 'Configure common access patters for your app following a warehouse management system example.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/best-practice/warehouse-management/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -In this "Warehouse management system" example, you will learn how to configure common access patterns for your app. This example has the following types: - -- Warehouse -- Product -- Inventory -- Employee -- AccountRepresentative -- Customer - -These types have the following common access patterns: - -1. [Look up employee details by employee ID](#1-look-up-employee-details-by-employee-id) -2. [Query employee details by employee name](#2-query-employee-details-by-employee-name) -3. [Find an employee's phone number(s)](#3-find-an-employees-phone-number) -4. [Find a customer's phone number(s)](#4-find-a-customers-phone-number) -5. [Get orders for a given customer within a given date range](#5-get-orders-for-a-given-customer-within-a-given-date-range) -6. [Show all open orders within a given date range across all customers](#6-show-all-open-orders-within-a-given-date-range-across-all-customers) -7. [See all employees recently hired](#7-see-all-employees-hired-recently) -8. [Find all employees working in a given warehouse](#8-find-all-employees-working-in-a-given-warehouse) -9. [Get all items on order for a given product](#9-get-all-items-on-order-for-a-given-product) -10. [Get current inventories for a given product at all warehouses](#10-get-current-inventories-for-a-product-at-all-warehouses) -11. [Get customers by account representative](#11-get-customers-by-account-representative) -12. [Get orders by account representative and date](#12-get-orders-by-account-representative-and-date) -13. [Get all items on order for a given product](#13-get-all-items-on-order-for-a-given-product) -14. [Get all employees with a given job title](#14-get-all-employees-with-a-given-job-title) -15. [Get inventory by product and warehouse](#15-get-inventory-by-product-by-warehouse) -16. [Get total product inventory](#16-get-total-product-inventory) -17. [Get account representatives ranked by order total and sales period](#17-get-sales-representatives-ranked-by-order-total-and-sales-period) - -The following schema introduces the required indexes and relationships so that you can support these access patterns: - -```graphql -# This "input" configures a global authorization rule to enable public access to -# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/auth -input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY! - -type Order @model { - id: ID! - customerID: ID! @index(name: "byCustomerByStatusByDate", sortKeyFields: ["status", "date"]) @index(name: "byCustomerByDate", sortKeyFields: ["date"]) - accountRepresentativeID: ID! @index(name: "byRepresentativebyDate", sortKeyFields: ["date"]) - productID: ID! @index(name: "byProduct", sortKeyFields: ["id"]) - status: String! - amount: Int! - date: String! -} - -type Customer @model { - id: ID! - name: String! - phoneNumber: String - accountRepresentativeID: ID! @index(name: "byRepresentative", sortKeyFields: ["id"]) - ordersByDate: [Order] @hasMany(indexName: "byCustomerByDate", fields: ["id"]) - ordersByStatusDate: [Order] @hasMany(indexName: "byCustomerByStatusByDate", fields: ["id"]) -} - -type Employee @model { - id: ID! - name: String! @index(name: "byName", queryField: "employeeByName", sortKeyFields: ["id"]) - startDate: String! - phoneNumber: String! - warehouseID: ID! @index(name: "byWarehouse", sortKeyFields: ["id"]) - jobTitle: String! @index(name: "byTitle", queryField: "employeesByJobTitle", sortKeyFields: ["id"]) - newHire: String! @index(name: "newHire", queryField: "employeesNewHire", sortKeyFields: ["id"]) @index(name: "newHireByStartDate", queryField: "employeesNewHireByStartDate", sortKeyFields: ["startDate"]) -} - -type Warehouse @model { - id: ID! - employees: [Employee] @hasMany(indexName: "byWarehouse", fields: ["id"]) -} - -type AccountRepresentative @model { - id: ID! - customers: [Customer] @hasMany(indexName: "byRepresentative", fields: ["id"]) - orders: [Order] @hasMany(indexName: "byRepresentativebyDate", fields: ["id"]) - orderTotal: Int - salesPeriod: String @index(name: "bySalesPeriodByOrderTotal", queryField: "repsByPeriodAndTotal", sortKeyFields: ["orderTotal"]) -} - -type Inventory @model { - productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) - warehouseID: ID! @index(name: "byWarehouseID", queryField: "itemsByWarehouseID") - inventoryAmount: Int! -} - -type Product @model { - id: ID! - name: String! - orders: [Order] @hasMany(indexName: "byProduct", fields: ["id"]) - inventories: [Inventory] @hasMany(fields: ["id"]) -} -``` - -Now that you have the schema created, let's create the items in the database that you will be operating against: - -```graphql -# first -mutation createWarehouse { - createWarehouse(input: {id: "1"}) { - id - } -} - -# second -mutation createEmployee { - createEmployee(input: { - id: "amanda" - name: "Amanda", - startDate: "2018-05-22", - phoneNumber: "6015555555", - warehouseID: "1", - jobTitle: "Manager", - newHire: "true"} - ) { - id - jobTitle - name - newHire - phoneNumber - startDate - warehouseID - } -} - -# third -mutation createAccountRepresentative { - createAccountRepresentative(input: { - id: "dabit" - orderTotal: 400000 - salesPeriod: "January 2019" - }) { - id - orderTotal - salesPeriod - } -} - -# fourth -mutation createCustomer { - createCustomer(input: { - id: "jennifer_thomas" - accountRepresentativeID: "dabit" - name: "Jennifer Thomas" - phoneNumber: "+16015555555" - }) { - id - name - accountRepresentativeID - phoneNumber - } -} - -# fifth -mutation createProduct { - createProduct(input: { - id: "yeezyboost" - name: "Yeezy Boost" - }) { - id - name - } -} - -# sixth -mutation createInventory { - createInventory(input: { - productID: "yeezyboost" - warehouseID: "1" - inventoryAmount: 300 - }) { - productID - inventoryAmount - warehouseID - } -} - -# seventh -mutation createOrder { - createOrder(input: { - amount: 300 - date: "2018-07-12" - status: "pending" - accountRepresentativeID: "dabit" - customerID: "jennifer_thomas" - productID: "yeezyboost" - }) { - id - customerID - accountRepresentativeID - amount - date - customerID - productID - } -} -``` - -### 1. Look up employee details by employee ID - -This can simply be done by querying the employee model with an employee ID, no `@primaryKey` or `@index` need to be explicitly specified to make this work. - -```graphql -query getEmployee($id: ID!) { - getEmployee(id: $id) { - id - name - phoneNumber - startDate - jobTitle - } -} -``` - -### 2. Query employee details by employee name - -The `@index` `byName` on the `Employee` type makes this access-pattern feasible because under the hood an index is created and a query is used to match against the name field. You can use this query: - -```graphql -query employeeByName($name: String!) { - employeeByName(name: $name) { - items { - id - name - phoneNumber - startDate - jobTitle - } - } -} -``` - -### 3. Find an Employee’s phone number - -Either one of the previous queries would work to find an employee’s phone number as long as one has their ID or name. - -### 4. Find a customer’s phone number - -A similar query to those given above but on the Customer model would give you a customer’s phone number. - -```graphql -query getCustomer($customerID: ID!) { - getCustomer(id: $customerID) { - phoneNumber - } -} -``` - -### 5. Get orders for a given customer within a given date range - -There is a one-to-many relation that lets all the orders of a customer be queried. - -This relationship is created by having the `@index` name `byCustomerByDate` on the Order model that is queried by the `@hasMany` relationship on the orders field of the Customer model. - -A sort key with the date is used. What this means is that the GraphQL resolver can use predicates like `Between` to efficiently search the date range rather than scanning all records in the database and then filtering them out. - -The query one would need to get the orders to a customer within a date range would be: - -```graphql -query getCustomerWithOrdersByDate($customerID: ID!) { - getCustomer(id: $customerID) { - ordersByDate(date: { - between: [ "2018-01-22", "2020-10-11" ] - }) { - items { - id - amount - productID - } - } - } -} -``` - -### 6. Show all open orders within a given date range across all customers - -The `@index` `byCustomerByStatusByDate` enables you to run a query that would work for this access pattern. - -In this example, a composite sort key (combination of two or more keys) with the `status` and `date` is used. What this means is that the unique identifier of a record in the database is created by concatenating these two fields (status and date) together, and then the GraphQL resolver can use predicates like `between` or `contains` to efficiently search the unique identifier for matches rather than scanning all records in the database and then filtering them out. - -```graphql -query listCustomersWithOrdersByStatusDate { - listCustomers { - items { - ordersByStatusDate(statusDate: { - between: [ - { status: "pending", date: "2018-01-22" }, - { status: "pending", date: "2020-10-11" } - ]}) { - items { - id - amount - date - } - } - } - } -} -``` - -### 7. See all employees hired recently - -Having `@index(name: "newHire", fields: ["newHire", "id"])` on the `Employee` model allows one to query by whether an employee has been hired recently. - -```graphql -query employeesNewHire { - employeesNewHire(newHire: "true") { - items { - id - name - phoneNumber - startDate - jobTitle - } - } -} -``` - -You can also query and have the results returned by start date by using the `employeesNewHireByStartDate` query: - -```graphql -query employeesNewHireByDate { - employeesNewHireByStartDate(newHire: "true") { - items { - id - name - phoneNumber - startDate - jobTitle - } - } -} -``` - -### 8. Find all employees working in a given warehouse - -This needs a one to many relationship from warehouses to employees. As can be seen from the `@hasMany` relationship in the `Warehouse` model, this relationship uses the `byWarehouse` index on the `Employee` model. The relevant query would look like this: - -```graphql -query getWarehouse($warehouseID: ID!) { - getWarehouse(id: $warehouseID) { - id - employees{ - items { - id - name - startDate - phoneNumber - jobTitle - } - } - } -} -``` - -### 9. Get all items on order for a given product - -This access-pattern would use a one-to-many relation from products to orders. With this query you can get all orders of a given product: - -```graphql -query getProductOrders($productID: ID!) { - getProduct(id: $productID) { - id - orders { - items { - id - status - amount - date - } - } - } -} -``` - -### 10. Get current inventories for a product at all warehouses - -The query needed to get the inventories of a product in all warehouses would be: - -```graphql -query getProductInventoryInfo($productID: ID!) { - getProduct(id: $productID) { - id - inventories { - items { - warehouseID - inventoryAmount - } - } - } -} -``` - -### 11. Get customers by account representative - -This uses a has-many relationship between account representatives and customers: - -The query needed would look like this: - -```graphql -query getCustomersForAccountRepresentative($representativeId: ID!) { - getAccountRepresentative(id: $representativeId) { - customers { - items { - id - name - phoneNumber - } - } - } -} -``` - -### 12. Get orders by account representative and date - -As can be seen in the AccountRepresentative model this relationship uses the `byRepresentativebyDate` field on the `Order` model to create the connection needed. The query needed would look like this: - -```graphql -query getOrdersForAccountRepresentative($representativeId: ID!) { - getAccountRepresentative(id: $representativeId) { - id - orders(date: { - between: [ - "2010-01-22", "2020-10-11" - ] - }) { - items { - id - status - amount - date - } - } - } -} -``` - -### 13. Get all items on order for a given product - -This is the same as number 9. - -### 14. Get all employees with a given job title - -Using the `byTitle` `@index` makes this access pattern quite easy. - -```graphql -query employeesByJobTitle { - employeesByJobTitle(jobTitle: "Manager") { - items { - id - name - phoneNumber - jobTitle - } - } -} -``` - -### 15. Get inventory by product by warehouse - -Here having the inventories be held in a separate model is particularly useful since this model can have its own partition key and sort key such that the inventories themselves can be queried as is needed for this access-pattern. - -A query on this model would look like this: - -```graphql -query inventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { - getInventory(productID: $productID, warehouseID: $warehouseID) { - productID - warehouseID - inventoryAmount - } -} - -``` - -You can also get all inventory from an individual warehouse by using the `itemsByWarehouseID` query created by the `byWarehouseID` key: - -```graphql -query byWarehouseId($warehouseID: ID!) { - itemsByWarehouseID(warehouseID: $warehouseID) { - items { - inventoryAmount - productID - } - } -} -``` - -### 16. Get total product inventory - -How this would be done depends on the use case. If one just wants a list of all inventories in all warehouses, one could just run a list inventories on the Inventory model: - -```graphql -query listInventorys { - listInventorys { - items { - productID - warehouseID - inventoryAmount - } - } -} -``` - -### 17. Get sales representatives ranked by order total and sales period - -The sales period is either a date range or maybe even a month or week. Therefore you can set the sales period as a string and query using the combination of `salesPeriod` and `orderTotal`. You can also set the `sortDirection` in order to get the return values from largest to smallest: - -```graphql -query repsByPeriodAndTotal { - repsByPeriodAndTotal( - sortDirection: DESC, - salesPeriod: "January 2019", - orderTotal: { - ge: 1000 - }) { - items { - id - orderTotal - } - } -} -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx deleted file mode 100644 index 27e72469f51..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/client-code-generation/index.mdx +++ /dev/null @@ -1,245 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'JavaScript, Android, Swift, and Flutter client code generation', - description: "Amplify's codegen capabilities generate native code for iOS and Android, as well as types for Flow and TypeScript. Codegen can also generate GraphQL statements (queries, mutations, and subscriptions).", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/client-code-generation/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - - -"Codegen" generates native code for Swift (iOS), Java (Android), and JavaScript that represent your GraphQL API's data models. It can also generate GraphQL statements (queries, mutations, and subscriptions) so that you don't have to hand code them. - -The design of codegen functionality provides mechanisms to run at different points in your app development lifecycle, including when you create or update an API as well as independently when you want to just update the data fetching requirements of your app but leave your API alone. It additionally allows you to work in a team where the schema is updated or managed by another person. Finally, you can also include the codegen in your build process so that it runs automatically (such as from in Xcode). - -## Generate GraphQL client helper code for GraphQL APIs deployed with Amplify GraphQL CDK construct - -The necessary GraphQL client helper code differ from platform to platform. For JavaScript GraphQL client code, you need to reference the API ID that you receive after you deploy your application. For Android, iOS, and Flutter you can reference the local GraphQL schema to generate models for your API client. - -### JavaScript / TypeScript GraphQL API client helper code - -Go to your frontend app's root directory and run the following command in the Terminal: - -```bash -npx @aws-amplify/cli codegen add --apiId <...> --region <...> -``` - -This will download your API's schema and by default generate client helper code into the `src/graphql` folder. After every API deployment, you can rerun the following command to generate updated GraphQL statement and types: - -``` -npx @aws-amplify/cli codegen -``` - -### Generate "models" for Android, Swift, Flutter, and JavaScript DataStore - -The Android, Swift, Flutter, and DataStore on JavaScript use the "modelgen" pattern to interact with the client library. To generate models, run the following command from your frontend application's root directory: - -```bash -npx @aws-amplify/cli codegen models \ - --model-schema \ - --target [android|ios|flutter|javascript|typescript] \ - --output-dir ./ -``` - -## Generate GraphQL Client code with Amplify CLI-deployed GraphQL API - -### Create API then automatically generate code - -```bash -amplify init -amplify add api (select GraphQL) -amplify push -``` - -You’ll see questions as before, but now it will also automatically ask you if you want to generate GraphQL statements and do codegen. It will also respect the `./app/src/main` directory for Android projects. After the AppSync deployment finishes the Swift file will be automatically generated (Android you’ll need to kick off a [Gradle Build step](#androiduse)) and you can begin using in your app immediately. - -When you deploy your GraphQL API to the cloud, you are prompted to configure codegen. When a project is configured to generate code with codegen, it stores all the configuration `.graphqlconfig.yml` file in the root folder of your project. To make changes to the configuration, use `amplify configure codegen`. - -### Modify GraphQL schema, push, then automatically generate code - -During development, you might wish to update your GraphQL schema and generated code as part of an iterative dev/test cycle. Modify & save your schema in `amplify/backend/api//schema.graphql` then run: - -```bash -amplify push -``` - -Each time you will be prompted to update the code in your API and also ask you if you want to run codegen again as well, including regeneration of the GraphQL statements from the new schema. - -### No API changes, just update GraphQL statements & generate code - -One of the benefits of GraphQL is the client can define it's data fetching requirements independently of the API. Amplify codegen supports this by allowing you to modify the selection set (e.g. add/remove fields inside the curly braces) for the GraphQL statements and running type generation again. This gives you fine-grained control over the network requests that your application is making. Modify your GraphQL statements (default in the `./graphql` folder unless you changed it) then save the files and run: - -```bash -amplify codegen types -``` - -A new updated Swift file will be created (or run Gradle Build on Android for the same). You can then use the updates in your application code. - -## Shared schema, modified elsewhere (e.g. console or team workflows) - -Suppose you are working in a team and the schema is updated either from the AWS AppSync console or on another system. Your types are now out of date because your GraphQL statement was generated off an outdated schema. The easiest way to resolve this is to regenerate your GraphQL statements, update them if necessary, and then generate your types again. Modify the schema in the console or on a separate system, then run: - -```bash -amplify codegen statements -amplify codegen types -``` - -You should have newly generated GraphQL statements and Swift code that matches the schema updates. If you ran the second command your types will be updated as well. Alternatively, if you run `amplify codegen` alone it will perform both of these actions. - -## Introspection Schema outside of an initialized project - -If you would like to generate statements and types without initializing an amplify project, you can do so by providing your introspection schema named `schema.json` in your project directory and adding codegen from the same directory. To download your introspection schema from an AppSync api, in the AppSync console go to the schema editor and under "Export schema" choose `schema.json`. - -```bash -amplify add codegen -``` - -Once codegen has been added you can update your introspection schema, then generate statements and types again without re-entering your project information. - -```bash -amplify codegen -``` - -You can update your project and codegen configuration if required. - -```bash -amplify configure codegen -amplify codegen -``` - -When generating types, codegen uses GraphQL statements as input. It will generate only the types that are being used in the GraphQL statements. - -## Codegen commands - -### amplify add codegen - -```bash -amplify add codegen -``` - -The `amplify add codegen` allows you to add AppSync API created using the AWS console. If you have your API is in a different region then that of your current region, the command asks you to choose the region. If you are adding codegen outside of an initialized amplify project, provide your introspection schema named `schema.json` in the same directory that you make the add codegen call from. **Note**: If you use the --apiId flag to add an externally created AppSync API, such as one created in the AWS console, you will not be able to manage this API from the Amplify CLI with commands such as amplify api update when performing schema updates. You cannot add an external AppSync API when outside of an initialized project. - -### amplify configure codegen - -```bash -amplify configure codegen -``` - -The `amplify configure codegen` command allows you to update the codegen configuration after it is added to your project. When outside of an initialized project, you can use this to update your project configuration as well as the codegen configuration. - -### amplify codegen statements - -```bash -amplify codegen statements [--nodownload] [--maxDepth ] -``` - -The `amplify codegen statements` command generates GraphQL statements(queries, mutation and subscription) based on your GraphQL schema. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. - -### amplify codegen types - -```bash -amplify codegen types -``` - -The `amplify codegen types [--nodownload]` command generates GraphQL `types` for Flow and typescript and Swift class in an iOS project. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. - -### amplify codegen - -```bash -amplify codegen [--maxDepth ] -``` - -The `amplify codegen [--nodownload]` generates GraphQL `statements` and `types`. This command downloads introspection schema every time it is run but it can be forced to use previously downloaded introspection schema by passing `--nodownload` flag. If you are running codegen outside of an initialized amplify project, the introspection schema named `schema.json` must be in the same directory that you run amplify codegen from. This command will not download the introspection schema when outside of an amplify project - it will only use the introspection schema provided. - -## Statement depth - -In the below schema there are connections between `Comment` -> `Post` -> `Blog` -> `Post` -> `Comments`. When generating statements codegen has a default limit of 2 for depth traversal. But if you need to go deeper than 2 levels you can change the `maxDepth` parameter either when setting up your codegen or by passing `--maxDepth` parameter to `codegen` - -```graphql -type Blog @model { - id: ID! - name: String! - posts: [Post] @hasMany -} -type Post @model { - id: ID! - title: String! - blog: Blog @belongsTo - comments: [Comment] @hasMany -} -type Comment @model { - id: ID! - content: String - post: Post @belongsTo -} -``` - -```graphql -query GetComment($id: ID!) { - getComment(id: $id) { - # depth level 1 - id - content - post { - # depth level 2 - id - title - blog { - # depth level 3 - id - name - posts { - # depth level 4 - items { - # depth level 5 - id - title - } - nextToken - } - } - comments { - # depth level 3 - items { - # depth level 4 - id - content - post { - # depth level 5 - id - title - } - } - nextToken - } - } - } -} -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx deleted file mode 100644 index 82fb41406a0..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-api-to-existing-database/index.mdx +++ /dev/null @@ -1,1018 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Connect API to existing MySQL or PostgreSQL database', - description: 'Learn how to connect your API to an existing MySQL or PostgreSQL database.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/connect-api-to-existing-database/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - - -The following content requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). - - - - -In this section, you'll learn how to: - -- Connect Amplify GraphQL API to an existing MySQL or PostgreSQL database -- Execute SQL statements with custom GraphQL queries and mutations using the new `@sql` directive -- Generate create, read, update, and delete API operations based on your SQL database schema - -## Connect your API with an existing MySQL or PostgreSQL database - - - - -Pre-requisites: - -- Have an existing [MySQL database](https://aws.amazon.com/getting-started/hands-on/create-mysql-db/) or [PostgreSQL database](https://aws.amazon.com/getting-started/hands-on/create-connect-postgresql-db/) deployed -- The [AWS CDK CLI is installed](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install) -- Have an [AWS CDK application initialized](https://docs.aws.amazon.com/cdk/v2/guide/hello_world.html) - - - -This feature is not yet available in the Asia Pacific (Hong Kong, ap-east-1) or Europe (Milan, eu-south-1) regions. - - - - - -First, place your database connection information (hostname, username, password, port, and database name) into Systems Manager, each as a `SecureString`. - -Go to the Systems Manager console, navigate to Parameter Store, and click "Create Parameter". Create five different SecureStrings: one each for the hostname of your database server, the username and password to connect, the database port, and the database name. - -Your Systems Manager configuration should look something like this: - -![A screenshot of an AWS Systems Manager console page titled "Parameter Store". The page shows a list of parameters with names like "/amplify-cdk-app/username", "/amplify-cdk-app/password", and "/amplify-cdk-app/hostname" indicating database connection details. Each parameter is of Tier "Standard" and typed as "SecureString". The last modified date is displayed for each parameter.](/images/storing-db-creds-in-ssm.png) - - -First, place your database connection information (hostname, username, password, port, and database name) into Secrets Manager. - -Go to the Secrets Manager console, navigate to Secrets, and click "Store a new secret". You may create the secret in any manner as long as there are `username` and `password` keys defined. - -![A screenshot of a page in the Secrets Manager console titled "Secret value info". The screenshot shows an example of a secret's keys and values in a table including "username", "password", "engine", "host", "port", and "dbClusterIdentifier".](/images/storing-db-creds-in-secrets-manager.png) - -Optionally, you can decide whether to encrypt the secret using the KMS key that Secrets Manager creates or a customer managed KMS key that you create. - -You can also configure a rotation schedule and create a Lambda function or choose an existing Lambda function from your account to rotate the database credentials automatically. - - - -Install the following package to add the Amplify GraphQL API construct to your dependencies: - -```sh -npm install @aws-amplify/graphql-api-construct -``` - -Create a new `schema.sql.graphql` file within your CDK app’s `lib/` folder that includes the APIs you want to expose. Define your GraphQL object types, queries, and mutations to match the APIs you wish to expose. For example, define object types for database tables, queries to fetch data from those tables, and mutations to modify those tables. - -```graphql -type Post { - id: Int! - title: String! - content: String! - published: Boolean - publishedDate: AWSDate @refersTo(name: "published_date") -} - -type Query { - searchPosts(contains: String!): [Post] - @sql( - statement: "SELECT * FROM posts WHERE title LIKE CONCAT('%', :contains, '%');" - ) - @auth(rules: [{ allow: public }]) -} - -type Mutation { - createPost(title: String! content: String!): AWSJSON - @sql(statement: "INSERT INTO posts (title, content) VALUES (:title, :content);") - @auth(rules: [{ allow: public }]) -} -``` - -You can use the `:variable` notation to reference input variables from the query request. - - -Amplify’s GraphQL API operates on a deny-by-default basis. The `{ allow: public }` auth rule in the example schema above designates that anyone using an API Key is authorized to execute the query. - -Review [Authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) to limit access to these queries and mutations based on API Key, Amazon Cognito User Pool, OpenID Connect, AWS Identity and Access Management (IAM), or a custom Lambda function. - - - -Next, open the main stack file in your CDK project (usually located in `lib/-stack.ts`). Import the necessary constructs at the top of the file: - -```ts -import { - AmplifyGraphqlApi, - AmplifyGraphqlDefinition -} from '@aws-amplify/graphql-api-construct'; - -import path from 'path'; -``` - -In the main stack class, add the following code to define a new GraphQL API. Replace `stack` with the name of your stack instance (often referenced via `this`): - - - -```ts -new AmplifyGraphqlApi(stack, 'SqlBoundApi', { - apiName: 'MySqlBoundApi', - definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( - [path.join(__dirname, 'schema.sql.graphql')], - { - name: 'MySQLSchemaDefinition', - dbType: 'MYSQL', - vpcConfiguration: { - vpcId: 'vpc-123456', - securityGroupIds: ['sg-123', 'sg-456'], - subnetAvailabilityZoneConfig: [ - { subnetId: 'sn-123456', availabilityZone: 'us-east-1a' }, - { subnetId: 'sn-987654', availabilityZone: 'us-east-1b' } - ] - }, - dbConnectionConfig: { - hostnameSsmPath: - '/path/to/ssm/SecureString/containing/value/of/hostname', - portSsmPath: '/path/to/ssm/SecureString/containing/value/of/port', - usernameSsmPath: - '/path/to/ssm/SecureString/containing/value/of/username', - passwordSsmPath: - '/path/to/ssm/SecureString/containing/value/of/password', - databaseNameSsmPath: - '/path/to/ssm/SecureString/containing/value/of/databaseName' - } - } - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); -``` - - - - -```ts -new AmplifyGraphqlApi(this, 'SqlBoundApi', { - apiName: 'MySqlBoundApi', - definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( - [path.join(__dirname, 'schema.sql.graphql')], - { - name: 'MySQLSchemaDefinition', - dbType: 'MYSQL', - vpcConfiguration: { - vpcId: 'vpc-123456', - securityGroupIds: ['sg-123', 'sg-456'], - subnetAvailabilityZoneConfig: [ - { subnetId: 'sn-123456', availabilityZone: 'us-east-1a' }, - { subnetId: 'sn-987654', availabilityZone: 'us-east-1b' }, - ], - }, - dbConnectionConfig: { - databaseName: 'database', - port: 3306, - hostname: 'database-1-instance-1.id.region.rds.amazonaws.com', - secretArn: - 'arn:aws:secretsmanager:Region1:123456789012:secret:MySecret-a1b2c3', - }, - } - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30), - }, - }, -}); -``` - - - - -The API will have an API key enabled for authorization. - -Before deploying, make sure to: - -- Set a value for `name`. This will be used to name the AppSync DataSource itself, plus any associated resources like resolver Lambdas. This name must be unique across all schema definitions in a GraphQL API. - -- Change the `dbType` to match your database engine. This is the type of the SQL database used to process model operations for this definition. Supported engines are `"MYSQL"` or `"POSTGRES"`. - -- Update the SSM parameter paths within `dbConnectionConfig` to point to those existing in your AWS account. These are the parameters the SQL Lambda will use to connect to the database. - -- If your database instance exists within a VPC, update the `vpcConfiguration` properties - `vpcId`, `securityGroupIds`, and `subnetAvailabilityZoneConfig` with your vpc details. This is the configuration of the VPC into which to install the SQL Lambda. - - - -If your database exists within a VPC, the RDS instance must be configured to be `Publicly accessible`. This does not mean the instance needs to accessible from the internet. - -The target security group(s) must have two inbound rules set up: - -- A rule allowing traffic on port 443 from the security group. - -- An inbound rule allowing traffic on the database port from the security group. (Default: 3306 for MySQL. 5432 for PostgreSQL.) - -In addition, the target security group(s) must have two outbound rules set up: - -- An outbound rule allowing traffic on port 443 to the security group. - -- An outbound rule allowing traffic on the database port to the security group. (Default: 3306 for MySQL. 5432 for PostgreSQL.) - - - **NOTE:** Make sure to limit the type of inbound traffic your security group - allows according to your security needs and/or use cases. For information on - security group rules, please refer to the [Amazon EC2 Security Group Rules reference](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html?icmpid=docs_ec2_console). - - - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - - - -Consider adding an RDS Proxy in front of the cluster to manage database connections. - -When using Amplify GraphQL API with a relational database like Amazon RDS, each query from your application needs to open a separate connection to the database. - -If there are a large number of queries occurring concurrently, it can exceed the connection limit on the database and result in errors like "Too many connections". To avoid this, Amplify can use an RDS Proxy when connecting your GraphQL API to a database. - -The RDS Proxy acts as an intermediary sitting in front of your database. Instead of each application query opening a direct connection to the database, they will connect through the Proxy. The Proxy helps manage and pool these connections to avoid overwhelming your database cluster. This improves the availability of your API, allowing more queries to execute concurrently without hitting connection limits. - -However, there is a tradeoff of increased latency - queries may take slightly longer as they wait for an available connection from the Proxy pool. There are also additional costs associated with using RDS Proxy. Please refer to the [pricing page for RDS Proxy](https://aws.amazon.com/rds/proxy/pricing/) to learn more. - - - -## Create custom queries and mutations - -Amplify GraphQL API for SQL databases introduces the `@sql` directive, which allows you to define SQL statements in custom GraphQL queries and mutations. This provides more flexibility when the default, auto-generated GraphQL queries and mutations are not sufficient. - -There are two ways to specify the SQL statement - inline or by referencing a `.sql` file. - -### Inline SQL Statement - -For getting started, you can embed the SQL statement directly in the schema using the `statement` argument. - -The SQL statement can use parameters in the format `:variable`, which will be bound to the input variables passed when executing a custom GraphQL query or mutation. - -In the example below, a SQL statement is defined, accepting a `searchTerm` input variable. - -```graphql -type Query { - searchPosts(searchTerm: String): [Post] - @sql(statement: "SELECT * FROM posts WHERE title LIKE :searchTerm;") - @auth(rules: [{ allow: public }]) -} -``` - -{/* TODO: Add a NOTE: about proxy/connection pinning here. */} - -### SQL File Reference - -For longer, more complex SQL queries, you can specify the statement in separate `.sql` files rather than inline. Referencing a file keeps your schema clean and allows reuse of SQL statements across fields. - - - - -First, update your GraphQL schema file to reference a SQL file name without the `.sql` extension: - -```graphql -type Query { - getPublishedPosts(start: AWSDate, end: AWSDate): [Post] - @sql(reference: "getPublishedPostsByDateRange") - @auth(rules: [{ allow: public }]) -} -``` - -Next, create a new `lib/sql-statements` folder and add any custom queries or mutations as SQL files. For example, you could create different `.sql` files for different queries: - -```sql --- lib/sql-statements/getPublishedPostsByDateRange.sql -SELECT p.id, p.title, p.content, p.published_date -FROM posts p -WHERE p.published = 1 - AND p.published_date > :startDate - AND p.published_date < :endDate -ORDER BY p.published_date DESC -LIMIT 10 -``` - -```sql --- lib/sql-statements/getPostById.sql -SELECT * FROM posts WHERE id = :id; -``` - -Then, you can import the `SQLLambdaModelDataSourceStrategyFactory` which helps define the datasource strategy from the custom `.sql` files you've created. - -```js -import { SQLLambdaModelDataSourceStrategyFactory } from '@aws-amplify/graphql-api-construct'; -import path from 'path'; -import fs from 'fs'; -``` - -In your `lib/-stack.ts` file, read from the `sql-statements/` folder and add them as custom SQL statements to your Amplify GraphQL API: - -```js - -// Define custom SQL statements folder path -const sqlStatementsPath = path.join(__dirname, 'sql-statements'); - -// Use the Factory to define the SQL data source strategy -const sqlStrategy = SQLLambdaModelDataSourceStrategyFactory.fromCustomSqlFiles( - // File paths to all SQL statements - fs - .readdirSync(sqlStatementsPath) - .map((file) => path.join(sqlStatementsPath, file)), - // Move your connection information and VPC config into here - { - dbType: 'MYSQL', - name: 'MySQLSchemaDefinition', - dbConnectionConfig: { - //... - }, - vpcConfiguration: { - //... - } - } -); - - -const amplifyApi = new AmplifyGraphqlApi(this, 'SqlBoundApi', { - definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( - [path.join(__dirname, 'schema.sql.graphql')], - sqlStrategy - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); -``` - -The SQL statements defined in the `.sql` files will be executed as if they were defined inline in the schema. The same rules apply in terms of using parameters, ensuring valid SQL syntax, and matching the return type to row data. - - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - - -### Custom Query - -For reference, you define a GraphQL query by adding a new field under a `type Query` object: - -```graphql -type Query { - searchPostsByTitle(title: String): [Post] - @sql( - statement: "SELECT * FROM posts WHERE title LIKE CONCAT('%', :title, '%');" - ) - @auth(rules: [{ allow: public }]) -} -``` - -### Custom Mutation - -For reference, you define a GraphQL mutation by adding a new field under a `type Mutation` object: - -```graphql -type Mutation { - publishPostById(id: ID!): AWSJSON - @sql(statement: "UPDATE posts SET published = :published WHERE id = :id;") - @auth(rules: [{ allow: public }]) -} -``` - -### Returning row data from custom mutations - -SQL statements such as `INSERT`, `UPDATE` and `DELETE` return the number of rows affected. - -If you want to return the result of the SQL statement, you can use `AWSJSON` as the return type. - -```graphql -type Mutation { - publishPosts: AWSJSON @sql(statement: "UPDATE posts SET published = 1;") - @auth(rules: [{ allow: public }]) -} -``` - -This will return a JSON response similar to this: - -```json -{ - "data": { - "publishPosts": "{\"fieldCount\":0,\"affectedRows\":7,\"insertId\":0,\"info\":\"Rows matched: 7 Changed: 7 Warnings: 0\",\"serverStatus\":34,\"warningStatus\":0,\"changedRows\":7}" - } -} -``` - -However, you might want to return the actual row data instead. - - - - -In MySQL, you can create and call a stored procedure that performs both an UPDATE statement and SELECT query to return a single post. - -Create a stored procedure by running the following SQL statement in your MySQL database: - -```sql -CREATE PROCEDURE publish_post (IN postId VARCHAR(255)) - -BEGIN -UPDATE posts SET published = 1 WHERE id = postId; - -SELECT * FROM posts WHERE id = postId LIMIT 1; -END -``` - -Call the stored procedure from the custom mutation: - -```graphql -type Mutation { - publishPostById(id: String!): [Post] - @sql(statement: "CALL publish_post(:id);") - @auth(rules: [{ allow: public }]) -} -``` - - - - -In PostgreSQL, you can add a `RETURNING` clause to an `INSERT`, `UPDATE`, or `DELETE` statement and get the actual modified row data. - -Example: - -```graphql -type Mutation { - publishPostById(id: String!): [Post] - @sql(statement: "UPDATE posts SET price = :id RETURNING *;") - @auth(rules: [{ allow: public }]) -} -``` - - - - - - The return type for custom queries and mutations expecting row data must - be an array of the corresponding model. - - - -## Apply authorization rules - -### Model level authorization rules - -The `@auth` directive can be used to restrict access to data and operations by specifying authorization rules. It allows granular access control over the GraphQL API based on the user's identity and attributes. You can for example, limit a query or mutation to only logged-in users via an `@auth(rules: [{ allow: private }])` rule or limit access to only users of the "Admin" group via an `@auth(rules: [{ allow: groups, groups: ["Admin"] }])` rule. - -All model-level authorization rules are supported for Amplify GraphQL schemas generated from MySQL and PostgreSQL databases. - -In the example below, public users authorized via API Key are granted unrestricted access to all posts. - -Add the following auth rule to the `Post` model within the `schema.sql.graphql` file: - -```graphql -type Post @model @refersTo(name: "posts") @auth(rules: [{ allow: public }]) { - id: String! @primaryKey - title: String! - content: String! -} -``` - -For more information on each rule please refer to our documentation on [Authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules). - -### Field-level authorization rules - -Field level auth rules are also supported for Amplify GraphQL schemas generated from MySQL and PostgreSQL databases. - -In the example below, unauthenticated users can read post data but only the owner of the post can perform operations on the `published` field. - -```graphql -type Post - @model - @refersTo(name: "posts") - @auth(rules: [ - { allow: public, operations: [read] }, - { allow: owner } - ]) { - id: String! @primaryKey - title: String! - content: String! - published: Boolean - // highlight-start - @auth(rules: [{ allow: owner }]) - // highlight-end -} -``` - -For more information on field-level auth rules please refer to our documentation on [Field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules). - -## Deploy your API - - - -To deploy the API, you can use the `cdk deploy` command: - -```sh -cdk deploy -``` - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - -Now the API has been deployed and you can start using it! - -You can start querying from the AWS AppSync console or integrate it into your application using the AWS Amplify libraries! - -## Auto-generate CRUDL operations for existing tables - -You can generate common CRUDL operations for your database tables based on your database schema. This saves time from hand-authoring the GraphQL types, queries, and mutations and SQL statements for common CRUDL use cases. After you generate the operations, you can annotate the `@model` types with authorization rules. - -Create a `Ingredients` table in your database: - -```sql -CREATE TABLE Ingredients ( - id varchar(255) NOT NULL PRIMARY KEY, - name varchar(255) NOT NULL, - unit_of_measurement varchar(255) NOT NULL, - price decimal(10, 2) NOT NULL, - supplier_id int, -); -``` - -### Step 1 - Export database schema as CSV - -Execute the following SQL statement on your database using a MySQL, PostgreSQL Client, or CLI tool similar to `psql` and export the output to a CSV file: - - - You must include column headers when exporting the database schema output to a CSV file. - - -Replace `` with the name of your database/schema. - - - -```sql -SELECT DISTINCT - INFORMATION_SCHEMA.COLUMNS.TABLE_NAME, - INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME, - INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT, - INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION, - INFORMATION_SCHEMA.COLUMNS.DATA_TYPE, - INFORMATION_SCHEMA.COLUMNS.COLUMN_TYPE, - INFORMATION_SCHEMA.COLUMNS.IS_NULLABLE, - INFORMATION_SCHEMA.COLUMNS.CHARACTER_MAXIMUM_LENGTH, - INFORMATION_SCHEMA.STATISTICS.INDEX_NAME, - INFORMATION_SCHEMA.STATISTICS.NON_UNIQUE, - INFORMATION_SCHEMA.STATISTICS.SEQ_IN_INDEX, - INFORMATION_SCHEMA.STATISTICS.NULLABLE -FROM INFORMATION_SCHEMA.COLUMNS -LEFT JOIN INFORMATION_SCHEMA.STATISTICS ON INFORMATION_SCHEMA.COLUMNS.TABLE_NAME=INFORMATION_SCHEMA.STATISTICS.TABLE_NAME AND INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME=INFORMATION_SCHEMA.STATISTICS.COLUMN_NAME -WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA = ''; --- Replace database name here ^^^^^^^^^^^^^^^ -``` - -Your exported SQL schema should look something like this: - -```csv -TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,ORDINAL_POSITION,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,CHARACTER_MAXIMUM_LENGTH,INDEX_NAME,NON_UNIQUE,SEQ_IN_INDEX,NULLABLE -Ingredients,id,,1,int,int,NO,,PRIMARY,0,1,"" -Ingredients,name,,2,varchar,varchar(100),NO,100,,,, -Ingredients,unit_of_measurement,,3,varchar,varchar(50),NO,50,,,, -Ingredients,price,,4,decimal,"decimal(10,2)",NO,,,,, -Ingredients,supplier_id,,6,int,int,YES,,,,, -Meals,id,,1,int,int,NO,,PRIMARY,0,1,"" -``` - - - -```sql -SELECT DISTINCT - INFORMATION_SCHEMA.COLUMNS.table_name, - enum_name,enum_values,column_name,column_default,ordinal_position,data_type,udt_name,is_nullable,character_maximum_length,indexname,constraint_type, - REPLACE(SUBSTRING(indexdef from '\((.*)\)'), '"', '') as index_columns -FROM INFORMATION_SCHEMA.COLUMNS -LEFT JOIN pg_indexes -ON - INFORMATION_SCHEMA.COLUMNS.table_name = pg_indexes.tablename - AND INFORMATION_SCHEMA.COLUMNS.column_name = ANY(STRING_TO_ARRAY(REPLACE(SUBSTRING(indexdef from '\((.*)\)'), '"', ''), ', ')) - LEFT JOIN ( - SELECT - t.typname AS enum_name, - ARRAY_AGG(e.enumlabel) as enum_values - FROM pg_type t JOIN - pg_enum e ON t.oid = e.enumtypid JOIN - pg_catalog.pg_namespace n ON n.oid = t.typnamespace - WHERE n.nspname = 'public' - GROUP BY enum_name - ) enums - ON enums.enum_name = INFORMATION_SCHEMA.COLUMNS.udt_name - LEFT JOIN information_schema.table_constraints - ON INFORMATION_SCHEMA.table_constraints.constraint_name = indexname - AND INFORMATION_SCHEMA.COLUMNS.table_name = INFORMATION_SCHEMA.table_constraints.table_name -WHERE INFORMATION_SCHEMA.COLUMNS.table_schema = 'public' - AND INFORMATION_SCHEMA.COLUMNS.TABLE_CATALOG = ''; --- Replace database name here ^^^^^^^^^^^^^^^ -``` - -Your exported SQL schema should look something like this: - -```csv -"table_name","enum_name","enum_values","column_name","column_default","ordinal_position","data_type","udt_name","is_nullable","character_maximum_length","indexname","constraint_type","index_columns" -"Ingredients","","","id","","1","bigint","int8","NO","","Ingredients_pkey","PRIMARY KEY","id" -"Ingredients","","","name","","2","text","text","NO","","","","" -"Ingredients","","","unit_of_measurement","","3","text","text","NO","","","","" -"Ingredients","","","price","","4","text","text","NO","","","","" -"Ingredients","","","supplier_id","","5","bigint","int8","NO","","","","" -``` - - - - -### Step 2 - Generate GraphQL schema from database schema - -Next, generate an Amplify GraphQL API schema by running the following command, replacing the `--engine-type` value with your database engine of `mysql` or `postgres`, and the `--sql-schema` value with the path to the CSV file created in the previous step: - -```bash -npx @aws-amplify/cli api generate-schema --engine-type mysql --sql-schema schema.csv --out schema.sql.graphql -``` - - -Next, update the first argument of `AmplifyGraphqlDefinition.fromFilesAndStrategy` to include the `schema.sql.graphql` file generated in the previous step: - -```ts -new AmplifyGraphqlApi(stack, 'SqlBoundApi', { - apiName: 'MySqlBoundApi', - definition: AmplifyGraphqlDefinition.fromFilesAndStrategy( - [path.join(__dirname, 'schema.sql.graphql')], // file path - { - // ...strategy options - } - ) -}); -``` - -### Step 3 - Apply authorization rules for your generated GraphQL API - -Open your **schema.sql.graphql** file, you should see something like this. The auto-generated schema automatically changes the casing to better match common GraphQL conventions. Amplify's GraphQL API's operate on a **deny-by-default principle**, this means you must explicitly add `@auth` authorization rules in order to make this API accessible to your users. Currently only model-level authorization is supported. - -```graphql -input AMPLIFY { - engine: String = "mysql" -} - - -type Ingredient @refersTo(name: "Ingredients") @model { - id: Int! @refersTo(name: "ingredient_id") @primaryKey - name: String! - unitOfMeasurement: String! @refersTo(name: "unit_of_measurement") - price: Float! - supplierId: Int @refersTo(name: "supplier_id") -} -``` - -In our example, we'll add a public authorization rule, meaning anyone with an API key can create, read, update, and delete records from the database. Review [Customize authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) to see the full scope of model-level authorization capabilities. - -```diff -input AMPLIFY { - engine: String = "mysql" -} - - -- type Ingredient @refersTo(name: "Ingredients") @model { -+ type Ingredient -+ @refersTo(name: "Ingredients") -+ @model -+ @auth(rules: [{ allow: public }]) { - id: Int! @refersTo(name: "ingredient_id") @primaryKey - name: String! - unitOfMeasurement: String! @refersTo(name: "unit_of_measurement") - price: Float! - supplierId: Int @refersTo(name: "supplier_id") -} -``` - -Finally, remember to deploy your API to the cloud: - - - -To deploy the API, you can use the `cdk deploy` command: - -```sh -cdk deploy -``` - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - -Now the API has been deployed and you can start using it! - -### Rename & map models to tables - -To rename models and fields, you can use the `@refersTo` directive to map the models in the GraphQL schema to the corresponding table or field by name. - -By default, the Amplify CLI singularizes each model name using PascalCase and field names that are either snake_case or kebab-case will be converted to camelCase. - -In the example below, the Post model in the GraphQL schema is now mapped to the posts table in the database schema. Also, the `isPublished` is now mapped to the `published` column on the posts table. - -```graphql -type Post @refersTo(name: "posts") @model { - id: String! @primaryKey - title: String! - content: String! - isPublished: Boolean @refersTo(name: "published") - publishedDate: AWSDate @refersTo(name: "published_date") -} -``` - -### Create relationships between models - -You can use the `@hasOne`, `@hasMany`, and `@belongsTo` relational directives to create relationships between models. The field named in the `references` parameter of the relational directives must exist on the child model. - - - -Relationships that query across DynamoDB and SQL data sources are currently not supported. However, you can create relationships across SQL data sources. - - - -Assume that you have `users`, `blogs`, and `posts` tables in your database schema. The following examples demonstrate how you might create different types of relationships between them. Use them as references for creating relationships between the models in your own schema. - -#### Has One relationship - -Create a one-directional one-to-one relationship between two models using the `@hasOne` directive. - -In the example below, a User has a single Blog. - -```graphql -type User - @refersTo(name: "users") - @model - @auth(rules: [{ allow: owner }, { allow: groups, groups: ["Admin"] }]) { - id: String! @primaryKey - name: String! - owner: String - blog: Blog @hasOne(references: ["userId"]) -} -``` - -#### Has Many relationship - -Create a one-directional one-to-many relationship between two models using the `@hasMany` directive. - -In the example below, a Blog has many Posts. - -```graphql -type Blog @model { - id: String! @primaryKey - title: String! - posts: [Post] @hasMany(references: ["blogId"]) -} - -type Post @model { - id: String! @primaryKey - title: String! - content: String! - blogId: String! @refersTo(name: "blog_id") -} -``` - -#### Belongs To relationship - -Make a "has one" or "has many" relationship bi-directional with the `@belongsTo` directive. - -In the example below, a Post belongs to a Blog. - -```graphql -type Post @model { - id: String! @primaryKey - title: String! - content: String! - blogId: String! @refersTo(name: "blog_id") - blog: Blog @belongsTo(references: ["blogId"]) -} -``` - -### Apply iterative changes from the database definition - - - - 1. Make any adjustments to your SQL statements such as: - -```sql -CREATE TABLE posts ( - id varchar(255) NOT NULL PRIMARY KEY, - title varchar(255) NOT NULL, - content varchar(255) NOT NULL, - published tinyint(1) DEFAULT 0 NOT NULL - published_date date NULL -); -``` - -2. Regenerate the database schema as a CSV file by following the instructions in [Generate GraphQL schema from database schema](#step-2---generate-graphql-schema-from-database-schema). - -3. Generate an updated schema by running the following command, replacing the `--engine-type` value with your database engine of `mysql` or `postgres`, and the `--sql-schema` value with the path to the CSV file created in the previous step: - -```sh -npx @aws-amplify/cli api generate-schema --engine-type mysql --sql-schema schema.csv --out schema.sql.graphql -``` - -4. Deploy your changes to the cloud: - -```sh -cdk deploy -``` - - - - - - -This feature is currently not supported on Amplify CLI. It requires you to deploy the Amplify GraphQL APIs via AWS Cloud Development Kit (CDK). If you have not yet deployed an Amplify GraphQL API with AWS CDK yet, review [Set up GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api). - - - - - - -## How does it work? - -The Amplify uses AWS Lambda functions to enable features like querying data from your database. To work properly, these Lambda functions need access to common logic and dependencies. - -Amplify provides this shared code in the form of Lambda Layers. You can think of Lambda Layers as a package of reusable runtime code that Lambda functions can reference. - -When you deploy an Amplify API, it will create two Lambda functions: - -### SQL Lambda - -This allows you to query and write data to your database from your API. - - - **NOTE:** If the database is in a VPC, this Lambda function will be deployed - in the same VPC as the database. The usage of Amazon Virtual Private Cloud - (VPC) or VPC peering, with AWS Lambda functions will incur additional charges - as explained, this comes with an additional cost as explained on the [Amazon - Elastic Compute Cloud (EC2) on-demand pricing - page](https://aws.amazon.com/ec2/pricing/on-demand/). - - -### Updater Lambda - -This automatically keeps the SQL Lambda up-to-date by managing its Lambda Layers. - -A Lambda layer that includes all the core SQL connection logic lives within the AWS Amplify service account but is executed within your AWS account, when invoked by the SQL Lambda. This allows the Amplify service team to own the ongoing maintenance and security enhancements of the SQL connection logic. - -This allows the Amplify team to maintain and enhance the SQL Layer without needing direct access to your Lambdas. If updates to the Layer are needed, the Updater Lambda will receive a signal from Amplify and automatically update the SQL Lambda with the latest Layer. - -### Mapping of SQL data types to GraphQL types when auto-generating GraphQL schema - - - -**Note:** MySQL does not support time zone offsets in date time or timestamp fields. Instead, we will convert these values to `datetime`, without the offset. - -Unlike MySQL, PostgreSQL does support date time or timestamp values with an offset. - - - -| SQL | GraphQL | -|--------------------|--------------| -| **String** | | -| char | String | -| varchar | String | -| tinytext | String | -| text | String | -| mediumtext | String | -| longtext | String | -| **Geometry** | | -| geometry | String | -| point | String | -| linestring | String | -| geometryCollection | String | -| **Numeric** | | -| smallint | Int | -| mediumint | Int | -| int | Int | -| integer | Int | -| bigint | Int | -| tinyint | Int | -| float | Float | -| double | Float | -| decimal | Float | -| dec | Float | -| numeric | Float | -| **Date and Time** | | -| date | AWSDate | -| datetime | AWSDateTime | -| timestamp | AWSDateTime | -| time | AWSTime | -| year | Int | -| **Binary** | | -| binary | String | -| varbinary | String | -| tinyblob | String | -| blob | String | -| mediumblob | String | -| longblob | String | -| **Others** | | -| bool | Boolean | -| boolean | Boolean | -| bit | Int | -| json | AWSJSON | -| enum | ENUM | - -### Supported Amplify directives for auto-generated GraphQL schema - -| Name | Supported | Model Level | Field Level | Description | -|--------------|:---------:|:-----------:|:-----------:|-------------| -| `@model` | ✅ | ✅ | ❌ | Creates a datasource and resolver for a table. | -| `@auth` | ✅ | ✅ | ✅ | Allows access to data based on a set of authorization methods and operations. | -| `@primaryKey`| ✅ | ❌ | ✅ | Sets a field to be the primary key. | -| `@index` | ✅ | ❌ | ✅ | Defines an index on a table. | -| `@default` | ✅ | ❌ | ✅ | Sets the default value for a column. | -| `@hasOne` | ✅ | ❌ | ✅ | Defines a one-way 1:1 relationship from a parent to child model. | -| `@hasMany` | ✅ | ❌ | ✅ | Defines a one-way 1:M relationship between two models, the reference being on the child. | -| `@belongsTo` | ✅ | ❌ | ✅ | Defines bi-directional relationship with the parent model. | -| `@manyToMany`| ❌ | ❌ | ❌ | Defines a M:N relationship between two models. | -| `@refersTo` | ✅ | ✅ | ✅ | Maps a model to a table, or a field to a column, by name. | -| `@mapsTo` | ❌ | ❌ | ❌ | Maps a model to a DynamoDB table. | -| `@sql` | ✅ | ❌ | ✅ | Accepts an inline SQL statement or reference to a .sql file to be executed to resolve a Custom Query or Mutation. | - - -## Troubleshooting - -### Debug Mode - -To return the actual SQL error instead of a generic error from GraphQL responses, an environment variable `DEBUG_MODE` can be set to `true` on the Amplify-generated SQL Lambda function. You can find this Lambda function in the AWS Lambda console with the naming convention of: `--SQLLambdaFunction`. - -## Next steps - -Our recommended next steps include using the GraphQL API to mutate and query data on app clients or how to customize the authorization rules for your custom queries and mutations. Some resources that will help with this work include: - -- [Create, update, and delete application data](/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/) -- [Read application data](/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/) -- [Customize Authorization Rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx deleted file mode 100644 index 54d262205c7..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx +++ /dev/null @@ -1,266 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Incorporate machine learning', - description: 'Add AI/ML capabilities such as text recognition, image labeling, text-to-speech, and translation to your GraphQL API.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/connect-machine-learning-services/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - - -Amplify allows you to identify text on an image, identify labels on an image, translate text, and synthesize speech from text with the `@predictions` directive. - -> Note: The `@predictions` directive requires a S3 storage bucket configured via `amplify add storage` or set the `predictionsBucket` property when using CDK. - -## Identify text on an image - -To configure text recognition on an image use the `identifyText` action in the `@predictions` directive. - -```graphql -type Query { - recognizeTextFromImage: String @predictions(actions: [identifyText]) -} -``` - -In your GraphQL query, can pass in a S3 `key` for the image. At the moment, this directive works only with objects located within the `public/` folder of your S3 bucket. The `public/` prefix is automatically added to the `key` input. For instance, in the example below, `public/myimage.jpg` will be used as the input. - -```graphql -query RecognizeTextFromImage($input: RecognizeTextFromImageInput!) { - recognizeTextFromImage(input: { identifyText: { key: "myimage.jpg" } }) -} -``` - -## Identify labels on an image - -To configure label recognition on an image use the `identifyLabels` action in the `@predictions` directive. - -```graphql -type Query { - recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) -} -``` - -In your GraphQL query, you can pass in a S3 `key` for the image. At the moment, this directive works only with objects located within `public/` folder in your S3 bucket. The `public/` prefix is automatically added to the `key` input. For instance, in the example below, `public/myimage.jpg` will be used as the input. - -The query below will return a list of identified labels. Review [Detecting Labels](https://docs.aws.amazon.com/rekognition/latest/dg/labels.html) in the Amazon Rekognition documentation for the full list of supported labels. - -```graphql -query RecognizeLabelsFromImage($input: RecognizeLabelsFromImageInput!) { - recognizeLabelsFromImage(input: { identifyLabels: { key: "myimage.jpg" } }) -} -``` - -## Translate text - -To configure text translation use the `identifyLabels` action in the `@predictions` directive. - -```graphql -type Query { - translate: String @predictions(actions: [translateText]) -} -``` - -The query below will return the translated string. Populate the `sourceLanguage` and `targetLanguage` parameters with one of the [Supported Language Codes](https://docs.aws.amazon.com/translate/latest/dg/what-is.html#what-is-languages). Pass in the text to translate via the `text` parameter. - -```graphql -query TranslateText($input: TranslateTextInput!) { - translate( - input: { - translateText: { - sourceLanguage: "en" - targetLanguage: "de" - text: "Translate me" - } - } - ) -} -``` - -## Synthesize speech from text - -To configure Text-to-Speech synthesis use the `convertTextToSpeech` action in the `@predictions` directive. - -```graphql -type Query { - textToSpeech: String @predictions(actions: [convertTextToSpeech]) -} -``` - -The query below will return a presigned URL with the synthesized speech. Populate the `voiceID` parameter with one of the [Supported Voice IDs](https://docs.aws.amazon.com/polly/latest/dg/voicelist.htm). Pass in the text to synthesize via the `text` parameter. - -```graphql -query ConvertTextToSpeech($input: ConvertTextToSpeechInput!) { - textToSpeech( - input: { - convertTextToSpeech: { - voiceID: "Nicole" - text: "Hello from AWS Amplify!" - } - } - ) -} -``` - -## Combining Predictions actions - -You can also combine multiple Predictions actions together into a sequence. The following action sequences are supported: - -- `identifyText -> translateText -> convertTextToSpeech` -- `identifyLabels -> translateText -> convertTextToSpeech` -- `translateText -> convertTextToSpeech` - -In the example below, `speakTranslatedImageText` identifies text from an image, then translates it into another language, and finally converts the translated text to speech. - -```graphql -type Query { - speakTranslatedImageText: String - @predictions(actions: [identifyText, translateText, convertTextToSpeech]) -} -``` - -An example of that query will look like: - -```graphql -query SpeakTranslatedImageText($input: SpeakTranslatedImageTextInput!) { - speakTranslatedImageText( - input: { - identifyText: { key: "myimage.jpg" } - translateText: { sourceLanguage: "en", targetLanguage: "es" } - convertTextToSpeech: { voiceID: "Conchita" } - } - ) -} -``` - -A code example of this using the JS Library is shown below: - -```js -import React, { useState } from 'react'; -import { Amplify } from 'aws-amplify'; -import { uploadData, getUrl } from 'aws-amplify/storage'; -import { generateClient } from 'aws-amplify/api'; -import config from './amplifyconfiguration.json'; -import { speakTranslatedImageText } from './graphql/queries'; - -/* Configure Exports */ -Amplify.configure(config); - -const client = generateClient(); - -function SpeakTranslatedImage() { - const [src, setSrc] = useState(''); - const [img, setImg] = useState(''); - - function putS3Image(event) { - const file = event.target.files[0]; - uploadData({ - key: file.name, - data: file - }) - .result.then(async (result) => { - setSrc(await speakTranslatedImageTextOP(result.key)); - setImg((await getUrl({ key: result.key })).url.toString()); - }) - .catch((err) => console.log(err)); - } - - return ( -
-
-

Upload Image

- { - putS3Image(event); - }} - /> -
- {img && } - {src && ( -
- -
- )} -
-
- ); -} - -async function speakTranslatedImageTextOP(key) { - const inputObj = { - translateText: { - sourceLanguage: 'en', - targetLanguage: 'es' - }, - identifyText: { key }, - convertTextToSpeech: { voiceID: 'Conchita' } - }; - const response = await client.graphql({ - query: speakTranslatedImageText, - variables: { input: inputObj } - }); - return response.data.speakTranslatedImageText; -} - -function App() { - return ( -
-

Speak Translated Image

- -
- ); -} -export default App; -``` - -## How it works - -Definition of the `@predictions` directive: - -```graphql -directive @predictions(actions: [PredictionsActions!]!) on FIELD_DEFINITION -enum PredictionsActions { - identifyText # uses Amazon Rekognition to detect text - identifyLabels # uses Amazon Rekognition to detect labels - convertTextToSpeech # uses Amazon Polly in a lambda to output a presigned url to synthesized speech - translateText # uses Amazon Translate to translate text from source to target language -} -``` - -`@predictions` creates resources to communicate with Amazon Rekognition, Translate, and Polly. For each action the following is created: - -- IAM Policy for each service (e.g. Amazon Rekognition `detectText` Policy) -- An AppSync VTL function -- An AppSync DataSource - -Finally, a pipeline resolver is created for the query or field. The pipeline resolver is composed of AppSync functions which are defined by the action list provided in the directive. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx deleted file mode 100644 index 470efc896c4..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/index.mdx +++ /dev/null @@ -1,958 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Set up custom queries and mutations', - description: 'Add authorization rules to your GraphQL schema to control access to your data.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/custom-business-logic/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - - -Define your custom business logic in a Lambda function resolver, HTTP resolver, or an AppSync JavaScript or VTL resolver and expose them in a GraphQL query or mutation. Extend or override Amplify-generated GraphQL resolvers to optimize for your specific use cases. - -## Create a custom query or mutation - -While `@model` automatically generates dedicated "create", "read", "update", "delete", and "subscription" queries or mutations for you, there are some cases where you want to define a stand-alone query or mutation. - -1. Define your custom query or mutation - -```graphql -type Mutation { - myCustomMutation(args: String): String # your custom mutations here -} - -type Query { - myCustomQuery(args: String): String # your custom queries here -} -``` - -2. Use one of these resolver choices to handle the query or mutation request: - - - [Lambda function resolver](#lambda-function-resolver): use a custom Lambda function to handle query or mutation - - [HTTP resolver](#http-resolver): call an HTTP endpoint upon a query or mutation - - [AppSync JavaScript or VTL resolver](#appsync-javascript-or-vtl-resolver) (most advanced): use AppSync's JavaScript resolver or AppSync's VTL mapping templates to customize the query and mutation logic - -3. Secure your custom query or mutation with [field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) - - Note: Dynamic authorization rules are not supported on a custom query or mutation. - -## Lambda function resolver - -The `@function` directive allows you to quickly & easily configure a AWS Lambda resolvers with your GraphQL API. You can use any AWS Lambda functions that was created with the Amplify CLI, AWS CDK or reference an existing AWS Lambda function created with any other means. - - - - - -For example, use `amplify add function` to add a Lambda function called "echofunction" with the following handler: - -```js -exports.handler = async function (event, context) { - return event.arguments.msg; -}; -``` - -To connect an AWS Lambda resolver to the GraphQL API, add the `@function` directive to a field in your schema. - -```graphql -type Query { - echo(msg: String): String @function(name: "echofunction-${env}") -} -``` - -The Amplify CLI provides support for maintaining multiple environments. When you deploy a function via `amplify add function`, it will automatically add the environment suffix to your Lambda function name. For example, if you create a function named `echofunction` using `amplify add function` in the `dev` environment, the deployed function will be named `echofunction-dev`. The `@function` directive allows you to use `${env}` to reference the current Amplify CLI environment. - - - - - -First, create your Lambda function in CDK with your logic and set the `functionName` parameter. - -```ts -const echoLambda = new lambda.Function(this, 'EchoLambda', { - functionName: 'echofunction', // MAKE SURE THIS MATCHES THE @function's "name" PARAMETER - code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_18_X -}); - -const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); -``` - -To connect an AWS Lambda resolver to the GraphQL API, add the `@function` directive to a field in your GraphQL schema. - -```graphql -type Query { - echo(msg: String): String @function(name: "echofunction") -} -``` - -Optionally, if you don't want to hard-code the function name into the GraphQL schema, you can also set an arbitrary name in the GraphQL schema and then map a function within CDK to the function name. - -```ts -const coolLambdaFunction = new lambda.Function(this, 'MyCoolLambdaFunction', { - code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_18_X -}); - -const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - }, - functionNameMap: { - echofunction: coolLambdaFunction // Remap the function name to any function you define or reference within CDK. - } -}); -``` - - - - - -If you deployed your Lambda function without Amplify CLI then you must provide the full Lambda function name in the `name` parameter. If you deployed the same function with the name echofunction then you would have: - -```graphql -type Query { - echo(msg: String): String @function(name: "echofunction") -} -``` - - - - - -### Structure of the function event - -When writing Lambda functions that are connected via the `@function` directive, you can expect the following structure for the AWS Lambda `event` object. - -| Key | Description | -| --- | --- | -| typeName | The name of the parent object type of the field being resolved. | -| fieldName | The name of the field being resolved. | -| arguments | A map containing the arguments passed to the field being resolved. | -| identity | A map containing identity information for the request. Contains a nested key 'claims' that will contains the JWT claims if they exist. | -| source | When resolving a nested field in a query, the source contains parent value at runtime. For example when resolving `Post.comments`, the source will be the Post object. | -| request | The AppSync request object. Contains header information. | -| prev | When using pipeline resolvers, this contains the object returned by the previous function. You can return the previous value for auditing use cases. | - -Your function should follow the Lambda handler guidelines for your specific language. See the developer guides from the [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) documentation for your chosen language. If you choose to use structured types, your type should serialize the AWS Lambda event object outlined above. For example, if using Golang, you should define a struct with the above fields. - -### Calling functions in different regions - -By default, you expect the function to be in the same region as the Amplify project. If you need to call a function in a different or a specific region, you can provide the **region** argument. - -```graphql -type Query { - echo(msg: String): String @function(name: "echofunction", region: "us-east-1") -} -``` - -Calling functions in different AWS accounts is not supported via the `@function` directive but is supported by AWS AppSync. - -### Chaining functions - -You can chain together multiple `@function` resolvers such that they are invoked in series when your field's resolver is invoked. To create a pipeline resolver that calls to multiple AWS Lambda functions in series, use multiple `@function` directives on the field. Similarly, `@function` can be combined with field-level `@auth`. When combining these field directives, the ordering in the schema matches the ordering in the pipeline resolver. You can choose to have functions before and/or after field level authorization is applied. - -> **Note:** Be careful when using @auth directives on a field in a root type. @auth directives on field definitions use the source object to perform authorization logic and the source will be an empty object for fields on root types. Static group authorization should perform as expected. - -```graphql -type Mutation { - doSomeWork(msg: String): String - @function(name: "worker-function") - @function(name: "audit-function") -} -``` - -In the example above when you run a mutation that calls the `Mutation.doSomeWork` field, the **worker-function** will be invoked first then the **audit-function** will be invoked with an event that contains the results of the **worker-function** under the **event.prev.result** key. The **audit-function** would need to return **event.prev.result** if you want the result of **worker-function** to be returned for the field. - -### How it works - -Definition of `@function` directive: - -```graphql -directive @function(name: String!, region: String) on FIELD_DEFINITION -``` - -Under the hood, Amplify creates an `AppSync::FunctionConfiguration` for each unique instance of `@function` in a document and a pipeline resolver containing a pointer to a function for each `@function` on a given field. - -The `@function` directive generates these resources as necessary: - -1. An AWS IAM role that has permission to invoke the function as well as a trust policy with AWS AppSync. -2. An AWS AppSync data source that registers the new role and existing function with your AppSync API. -3. An AWS AppSync pipeline function that prepares the lambda event and invokes the new data source. -4. An AWS AppSync resolver that attaches to the GraphQL field and invokes the new pipeline functions. - -## HTTP resolver - -The `@http` directive allows you to quickly configure HTTP resolvers within your GraphQL API. - -To connect to an endpoint, add the @http directive to a field in your GraphQL schema. The directive allows you to define URL path parameters, and specify a query string and/or specify a request body. For example, given the definition of a Post type: - -```graphql -type Post { - id: ID! - title: String - description: String - views: Int -} - -type Query { - listPosts: [Post] @http(url: "https://www.example.com/posts") -} -``` - -Amplify generates the definition below that sends a request to the url when the listPosts query is used. - -```graphql -type Query { - listPosts: [Post] -} -``` - -### Request headers - -The `@http` directive generates resolvers that can handle XML and JSON responses. If an HTTP method is not defined, `GET` is used. You can specify a list of static headers to be passed with the HTTP requests to your backend in your directive definition. - -```graphql -type Query { - listPosts: [Post] - @http( - url: "https://www.example.com/posts" - headers: [{ key: "X-Header", value: "X-Header-Value" }] - ) -} -``` - -### Path parameters - -You can create dynamic paths by specifying parameters in the directive URL by using the special `:` notation. Your set of parameters can then be specified in the params input object of the query. Note that path parameters are not added to the request body or query string. You can define multiple parameters. - -```graphql -type Query { - getPost: Post @http(url: "https://www.example.com/posts/:id") -} -``` - -In the example above, the `:id` parameter will generate the appropriate query input as shown below: - -```graphql -type Query { - getPost(params: QueryGetPostParamsInput!): Post -} - -input QueryGetPostParamsInput { - id: String! -} -``` - -You can fetch a specific post by enclosing the id in the params input object. - -```graphql -query post { - getPost(params: { id: "POST_ID" }) { - id - title - } -} -``` - -This executes the following request: - -```console -GET /posts/POST_ID -Host: www.example.com -``` - -### Query String - -You can send a query string with your request by specifying variables for your query. The query string is supported with all request methods. - -Given the definition - -```graphql -type Query { - listPosts(sort: String!, from: String!, limit: Int!): Post - @http(url: "https://www.example.com/posts") -} -``` - -Amplify generates - -```graphql -type Query { - listPosts(query: QueryListPostsQueryInput!): [Post] -} - -input QueryListPostsQueryInput { - sort: String! - from: String! - limit: Int! -} -``` - -You can query for posts using the `query` input object - -```graphql -query posts { - listPosts(query: { sort: "DESC", from: "last-week", limit: 5 }) { - id - title - description - } -} -``` - -which sends the following request: - -```text -GET /posts?sort=DESC&from=last-week&limit=5 -Host: www.example.com -``` - -### Request Body - -The `@http` directive also allows you to specify the body of a request, which is used for `POST`, `PUT`, and `PATCH` requests. To create a new post, you can define the following. - -```graphql -type Mutation { - addPost(title: String!, description: String!, views: Int): Post - @http(method: POST, url: "https://www.example.com/post") -} -``` - -Amplify generates the `addPost` query field with the `query` and `body` input objects since this type of request also supports a query string. The generated resolver verifies that non-null arguments (e.g.: the `title` and `description`) are passed in at least one of the input objects; if not, an error is returned. - -```graphql -type Mutation { - addPost(query: QueryAddPostQueryInput, body: QueryAddPostBodyInput): Post -} - -input QueryAddPostQueryInput { - title: String - description: String - views: Int -} - -input QueryAddPostBodyInput { - title: String - description: String - views: Int -} -``` - -You can add a post by using the `body` input object: - -```graphql -mutation add { - addPost(body: { title: "new post", description: "fresh content" }) { - id - } -} -``` - -which will send - -```text -POST /post -Host: www.example.com -{ - title: "new post" - description: "fresh content" -} -``` - -### Reference Amplify environment name - -The `@http` directive allows you to use `${env}` to reference the current Amplify CLI environment. - -```graphql -type Query { - listPosts: Post @http(url: "https://www.example.com/${env}/posts") -} -``` - -which, in the `DEV` environment, will send - -```text -GET /DEV/posts -Host: www.example.com -``` - -**Combining the different components** - -You can use a combination of parameters, query, body, headers, and environments in your `@http` directive definition. - -Given the definition - -```graphql -type Post { - id: ID! - title: String - description: String - views: Int - comments: [Comment] -} - -type Comment { - id: ID! - content: String -} - -type Mutation { - updatePost( - title: String! - description: String! - views: Int - withComments: Boolean - ): Post - @http( - method: PUT - url: "https://www.example.com/${env}/posts/:id" - headers: [{ key: "X-Header", value: "X-Header-Value" }] - ) -} -``` - -you can update a post with - -```graphql -mutation update { - updatePost( - body: { title: "new title", description: "updated description", views: 100 } - params: { id: "EXISTING_ID" } - query: { withComments: true } - ) { - id - title - description - comments { - id - content - } - } -} -``` - -which, in the `DEV` environment, will send - -```text -PUT /DEV/posts/EXISTING_ID?withComments=true -Host: www.example.com -X-Header: X-Header-Value -{ - title: "new title" - description: "updated description" - views: 100 -} -``` - -### Reference existing field data - -In some cases, you may want to send a request based on existing field data. Take a scenario where you have a post and want to fetch comments associated with the post in a single query. Let's use the previous definition of `Post` and `Comment`. - -```graphql -type Post { - id: ID! - title: String - description: String - views: Int - comments: [Comment] -} - -type Comment { - id: ID! - content: String -} -``` - -A post can be fetched at `/posts/:id` and a post's comments at `/posts/:id/comments`. You can fetch the comments based on the post id with the following updated definition. `$ctx.source` is a map that contains the resolution of the parent field (`Post`) and gives access to `id`. - -```graphql -type Post { - id: ID! - title: String - description: String - views: Int - comments: [Comment] - @http(url: "https://www.example.com/posts/${ctx.source.id}/comments") -} - -type Comment { - id: ID! - content: String -} - -type Query { - getPost: Post @http(url: "https://www.example.com/posts/:id") -} -``` - -You can retrieve the comments of a specific post with the following query and selection set. - -```graphql -query post { - getPost(params: { id: "POST_ID" }) { - id - title - description - comments { - id - content - } - } -} -``` - -Assuming that `getPost` retrieves a post with the id `POST_ID`, the comments field is resolved by sending this request to the endpoint - -```text -GET /posts/POST_ID/comments -Host: www.example.com -``` - -Note that there is no check to ensure that the reference variable (here the post ID) exists. When using this technique, it is recommended to make sure the referenced field is non-null. - -### How it works - -Definition of `@http` directive: - -```graphql -directive @http( - method: HttpMethod - url: String! - headers: [HttpHeader] -) on FIELD_DEFINITION -enum HttpMethod { - PUT - POST - GET - DELETE - PATCH -} -input HttpHeader { - key: String - value: String -} -``` - -The `@http` transformer will create one HTTP datasource for each identified base URL. For example, if multiple HTTP resolvers are created that interact with the "https://www.example.com" endpoint, only a single datasource is created. Each directive generates one resolver. Depending on the definition, the appropriate `body`, `params`, and `query` input types are created. Note that `@http` transformer does not support calling other AWS services where Signature Version 4 signing process is required. - -## AppSync JavaScript or VTL resolver - -You can use AWS Cloud Development Kit (CDK) to define custom AppSync resolvers for your GraphQL API. `@auth` directives are not supported for custom queries or mutations that are connected to a JavaScript or VTL resolver. This is because you are replacing Amplify's auto-generated capabilities for that particular query or mutation with a custom-defined cloud resources. - -```bash -amplify add custom -``` - -```console -? How do you want to define this custom resource? -❯ AWS CDK -? Provide a name for your custom resource -❯ MyCustomResolvers -``` - -Next, install the AppSync dependencies for your custom resource: - -```bash -cd amplify/backend/custom/MyCustomResolvers -npm i @aws-cdk/aws-appsync@~1.172.0 -``` - -> **Note:** Installations using the '\~' character do not modify the package.json. To use '\~' for default npm configurations, make sure your package.json reflects the right dependency to avoid compatibility errors in CDK. - -Finally, add your custom resolvers into the `cdk-stack.ts` file. You can either add the JavaScript or VTL inline into your `cdk-stack.ts` file. - -#### Unit Resolver - - - - - -Review the [AppSync JavaScript resolver tutorial](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html) for JavaScript resolver examples for different data sources. - -```ts -import * as cdk from 'aws-cdk-lib'; -import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; -import * as appsync from 'aws-cdk-lib/aws-appsync'; -import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; -import { Construct } from 'constructs'; - -const jsResolverTemplate = ` -export function request(ctx) { - return { - payload: null - } -} - -export function response(ctx) { - return ctx.arguments.message -} -` - -export class cdkStack extends cdk.Stack { - constructor( - scope: Construct, - id: string, - props?: cdk.StackProps, - amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps - ) { - super(scope, id, props); - /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ - new cdk.CfnParameter(this, 'env', { - type: 'String', - description: 'Current Amplify CLI env name' - }); - - // Access other Amplify Resources - const retVal: AmplifyDependentResourcesAttributes = - AmplifyHelpers.addResourceDependency( - this, - amplifyResourceProps.category, - amplifyResourceProps.resourceName, - [ - { - category: 'api', - resourceName: '' - } - ] - ); - - const resolver = new appsync.CfnResolver(this, 'CustomResolver', { - // apiId: retVal.api.new.GraphQLAPIIdOutput, - // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 - // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. - // Previously the ApiId is the variable Name which is wrong , it should be variable value as below - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - fieldName: 'echo', - typeName: 'Query', // Query | Mutation | Subscription - code: jsResolverTemplate, - dataSourceName: 'NONE_DS', // DataSource name - runtime: { - name: 'APPSYNC_JS', - runtimeVersion: '1.0.0' - } - }); - } -} -``` - - - - - -Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. - -```ts -import * as cdk from 'aws-cdk-lib'; -import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; -import * as appsync from 'aws-cdk-lib/aws-appsync'; -import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; -import { Construct } from 'constructs'; - -const requestVTL = ` - -`; -const responseVTL = ` - -`; - -export class cdkStack extends cdk.Stack { - constructor( - scope: Construct, - id: string, - props?: cdk.StackProps, - amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps - ) { - super(scope, id, props); - /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ - new cdk.CfnParameter(this, 'env', { - type: 'String', - description: 'Current Amplify CLI env name' - }); - - // Access other Amplify Resources - const retVal: AmplifyDependentResourcesAttributes = - AmplifyHelpers.addResourceDependency( - this, - amplifyResourceProps.category, - amplifyResourceProps.resourceName, - [ - { - category: 'api', - resourceName: '' - } - ] - ); - - const resolver = new appsync.CfnResolver(this, 'custom-resolver', { - // apiId: retVal.api.new.GraphQLAPIIdOutput, - // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887 - // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack. - // Previously the ApiId is the variable Name which is wrong , it should be variable value as below - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - fieldName: 'querySomething', - typeName: 'Query', // Query | Mutation | Subscription - requestMappingTemplate: requestVTL, - responseMappingTemplate: responseVTL, - dataSourceName: 'TodoTable' // DataSource name - }); - } -} -``` - -#### Pipeline Resolver - -```ts -import * as cdk from 'aws-cdk-lib'; -import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; -import * as appsync from 'aws-cdk-lib/aws-appsync'; -import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref'; -import { Construct } from 'constructs'; - -const beforeMappingVTL = ` - -`; -const afterMappingVTL = ` - -`; -const function1requestVTL = ` - -`; -const function1responseVTL = ` - -`; -const function2requestVTL = ` - -`; -const function2responseVTL = ` - -`; -export class cdkStack extends cdk.Stack { - constructor( - scope: Construct, - id: string, - props?: cdk.StackProps, - amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps - ) { - super(scope, id, props); - /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */ - new cdk.CfnParameter(this, 'env', { - type: 'String', - description: 'Current Amplify CLI env name' - }); - - // Access other Amplify Resources - const retVal: AmplifyDependentResourcesAttributes = - AmplifyHelpers.addResourceDependency( - this, - amplifyResourceProps.category, - amplifyResourceProps.resourceName, - [ - { - category: 'api', - resourceName: '' - } - ] - ); - - const function1 = new appsync.CfnFunctionConfiguration(this, 'function1', { - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - dataSourceName: 'NONE_DS', // DataSource name - functionVersion: '2018-05-29', - name: 'function1', - requestMappingTemplate: function1requestVTL, - responseMappingTemplate: function1responseVTL - }); - - const function2 = new appsync.CfnFunctionConfiguration(this, 'function2', { - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - dataSourceName: 'TodoTable', // DataSource name - functionVersion: '2018-05-29', - name: 'function2', - requestMappingTemplate: function2requestVTL, - responseMappingTemplate: function2responseVTL - }); - - const resolver = new appsync.CfnResolver(this, 'pipeline-resolver', { - apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput), - fieldName: 'querySomething', - typeName: 'Query', // Query | Mutation | Subscription - kind: 'PIPELINE', - pipelineConfig: { - functions: [function1.attrFunctionId, function2.attrFunctionId] - }, - requestMappingTemplate: beforeMappingVTL, - responseMappingTemplate: afterMappingVTL - }); - } -} -``` - -> **Note:** Users moving from ElasticSearch to OpenSearch will need to change the datasource name from `ElasticSearchDomain` to `OpenSearchDataSource` if the upgrade process changes the source name. For new @searchable models the datasource name will default to `OpenSearchDataSource`. - -You can alternatively define the VTL templates in another file such as `Query.querySomething.req.vtl` or `Query.querySomething.res.vtl` in `amplify/backend/custom/MyCustomResolvers/`. Then use the following code snippets to retrieve them: - -```ts -requestMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.req.vtl")).renderTemplate(), -responseMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.res.vtl")).renderTemplate(), -``` - -> **Note:** the `..` is added to the path because the path is always relative to the `build` folder of the custom resource. - - - - - -## Add authorization rules to custom queries and mutations - -Authorization rules can be applied with the `@auth` directive in the same way as field-level authorization rules. See [Field-level authorization rules](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/#field-level-authorization-rules) for details. - -In the example below, `myCustomMutation` can only be executed by signed-in customers who are authenticated with IAM: - -```graphql -type Mutation { - myCustomMutation(args: String): String - @auth(rules: [{ allow: private, provider: iam }]) -} -``` - -> **Known limitation:** You can't combine the `@auth` directive with a custom query or mutation that is using a VTL resolver. - -## Override Amplify-generated resolvers - -Amplify generates [AWS AppSync pipeline resolver](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) for your queries and mutations. The resolvers are listed the following API resource's folder `amplify/backend/api//build/resolvers/`. - -To override an Amplify-generated resolver: - -1. Find the resolver file name you want to override under `build/resolvers` -2. Place a `.vtl` with the same file name the resource's `resolvers/` (not under `build/`) -3. Upon the next `amplify api gql-compile` or `amplify push` the Amplify-generated resolver file will be replaced with your overwritten resolver file - -```console -amplify/backend/api/ -├── build -│   ├── ... -│   ├── resolvers -│   │   ├── ... -│   │   ├── Query.searchTodos.req.vtl # Find resolver file name -│   │   └── ... -| ... -├── resolvers -│   └── Query.searchTodos.req.vtl # Place resolver overrides with the same file name here -``` - -The example above shows how the `Query.searchTodos.req.vtl` is overwritten with a custom resolver. Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. - -## Extend Amplify-generated resolvers - -Amplify generates [AWS AppSync pipeline resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html) for your queries and mutations. You can "slot" in your custom business logic between Amplify-generated resolvers. You can find Amplify-generated resolvers under your API resources' `build/resolvers/` folder. The resolver functions file name determines its placement within the slot sequence. - -```console -File name convention: - [Query|Mutation|Subscription].[field name].[slot name].[slot placement].[req|res].vtl -Example: - Mutation.createTodo.postAuth.1.req.vtl -``` - -To extend an Amplify-generated resolver: - -1. Find the [resolver slot](#supported-resolver-slots) you want to add your custom business logic to -2. Place a `.vtl` file with the correct the file naming convention into `resolvers/` (not under `build/`) -3. Upon the next `amplify api gql-compile` or `amplify push` the Amplify-generated resolver file will be replaced within the desired slot within the resolver sequence. - -```console -amplify/backend/api/ -├── build -│   ├── ... -│   ├── resolvers -│   │   ├── ... -│   │   ├── Mutation.createTodo.postAuth.1.req.vtl # Amplify-generated resolvers -│   │   └── ... -| ... -├── resolvers -│   └── Mutation.createTodo.postAuth.2.req.vtl # Custom resolver slotted in after postAuth.1 resolver -``` - -For example, the a resolver function file named `Mutation.createTodo.postAuth.2.req.vtl` will be slotted in right after the `Mutation.createTodo.postAuth.1.req.vtl` resolver. Review the [Resolver Mapping Template Programming Guide](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html) to learn more about the VTL template. - -### Supported resolver slots - -#### Query - -| Sequence | Slot name | Description | -| --- | --- | --- | -| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | -| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | -| 3 | auth | Resolvers that implement authorization rule checks. | -| 4 | postAuth | Resolvers that are run after authorization rule checks. | -| 5 | preDataLoad | Resolvers to configure values to make a request to the data source. | -| 6 | postDataLoad | Resolvers for post-processing after request to data source. | -| 7 | finish | Final set of resolvers before response is returned to client. Typically used for clean-up. | - -#### Mutation - -| Sequence | Slot name | Description | -| --- | --- | --- | -| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | -| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | -| 3 | auth | Resolvers that implement authorization rule checks. | -| 4 | postAuth | Resolvers that are run after authorization rule checks. | -| 5 | preUpdate | Resolvers to configure values to make a request to the data source. | -| 6 | postUpdate | Resolvers for post-processing after request to data source. | -| 7 | finish | Final set of resolvers before response is returned to client. Typically used for clean-up. | - -#### Subscription - -| Sequence | Slot name | Description | -| --- | --- | --- | -| 1 | init | Initial resolvers that are run. Usually used for initializing default values. | -| 2 | preAuth | Resolvers that are intended to run before authorization rule checks are applied. | -| 3 | auth | Resolvers that implement authorization rule checks. | -| 4 | postAuth | Resolvers that are run after authorization rule checks. | -| 5 | preSubscribe | Resolver slot that executes after auth but before the subscription returns | diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx deleted file mode 100644 index 550e08cb5c3..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx +++ /dev/null @@ -1,937 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Customize authorization rules', - description: 'Add authorization rules to your GraphQL schema to control access to your data.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/swift/build-a-backend/graphqlapi/customize-authorization-rules/' - }, - { - platforms: [ - 'angular', - 'nextjs', - 'javascript', - 'react', - 'vue', - 'react-native' - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/customize-authorization-rules/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - - -Use the `@auth` directive to configure authorization rules for public, sign-in user, per user, and per user group data access. **Authorization rules operate on the deny-by-default principle**. Meaning that if an authorization rule is not specifically configured, it is denied. - -```graphql -type Todo @model @auth(rules: [{ allow: owner }]) { - content: String -} -``` - -In the example above, each signed-in user, or also known as "owner", of a Todo can create, read, update, and delete their own Todos. - -Amplify also allows you to restrict the allowed operations, combine multiple authorization rules, and apply fine-grained field-level authorization. - -```graphql -type Todo - @model - @auth(rules: [{ allow: public, operations: [read] }, { allow: owner }]) { - content: String -} -``` - -In the example above, everyone (`public`) can read every Todo but owner (authenticated users) can create, read, update, and delete their own Todos. - -### Global authorization rule (only for getting started) - -To help you get started, there's a global authorization rule defined when you create a new GraphQL schema. For production environments, remove the global authorization rule and apply rules on each model instead. - - - - - -```graphql -input AMPLIFY { - globalAuthRule: AuthRule = { allow: public } -} -``` - - - - -In the CDK construct, we call this the "sandbox mode" that you need to explicitly enable via an input parameter. - -```ts -new AmplifyGraphqlApi(this, "MyNewApi", { - ..., - translationBehavior: { - sandboxModeEnabled: true - } -}); -``` - - - - - -The global authorization rule (in this case `{ allow: public }` - allows anyone to create, read, update, and delete) is applied to every data model in the GraphQL schema. - -**Note:** Amplify will always use the most specific authorization rule that's present. For example, a field-level authorization rule will be used in favor of a model-level authorization rule; similarly, a model-level authorization rule will be used in favor of a global authorization rule. - - - -Currently, only `{ allow: public }` is supported as a global authorization rule. - - - -## Authorization strategies - -Use the guide below to select the correct authorization strategy for your use case: - -| **Recommended use case** | **Strategy** | **Provider** | -| --- | --- | --- | -| Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access. | [`public`](#public-data-access) | `apiKey` | -| Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using AWS IAM controls. | [`public`](#public-data-access) | `iam` (or `identityPool` when using CDK construct) | -| Per user data access. Access is restricted to the "owner" of a record. Leverages `amplify add auth` Cognito user pool by default. | [`owner`](#per-user--owner-based-data-access) | `userPools` / `oidc` | -| Any signed-in data access. Unlike owner-based access, **any** signed-in user has access. | [`private`](#signed-in-user-data-access) | `userPools` / `oidc` / `iam` | -| Per user group data access. A specific or dynamically configured group of users have access | [`groups`](#user-group-based-data-access) | `userPools` / `oidc` | -| Define your own custom authorization rule within a Lambda function | [`custom`](#custom-authorization-rule) | `function` | - -### Public data access - -To grant everyone access, use the `public` authorization strategy. Behind the scenes, the API will be protected with an API Key. - -```graphql -type Todo @model @auth(rules: [{ allow: public }]) { - content: String -} -``` - -You can also override the authorization provider. In the example below, you can use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API Key. - - - - -When you run `amplify add auth`, the Amplify CLI generates scoped down IAM policies for the "Unauthenticated role" in Cognito identity pool automatically. - -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: public, provider: iam }]) { - id: ID! - title: String! -} -``` - - - - -Designate an Amazon Cognito identity pool's role for unauthenticated identities by setting the `identityPoolConfig` property: - -```ts -// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well -import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha'; - -const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', { - allowUnauthenticatedIdentities: true, - authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({ - userPool: , - userPoolClient: , - })] }, -}); - -new AmplifyGraphqlApi(this, "MyNewApi", { - definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - }, - identityPoolConfig: { - identityPoolId: identityPool.identityPoolId, - authenticatedUserRole: identityPool.authenticatedRole, - unauthenticatedUserRole: identityPool.unauthenticatedRole, - } - }, -}) -``` - -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: public, provider: identityPool }]) { - id: ID! - title: String! -} -``` - - - -In the Amplify Library's client configuration file (`amplifyconfiguration.json`) set `allowGuestAccess` to `true`. This lets the Amplify Library use the unauthenticated role from your Cognito identity pool when your user isn't logged in. - -```json -{ - "Auth": { - "Cognito": { - "userPoolId": "YOUR_USER_POOL_ID", - "userPoolClientId": "YOUR_USER_POOL_CLIENT_ID", - "identityPoolId": "YOUR_IDENTITY_POOL_ID", - "allowGuestAccess": true - }, - }, - "API": { - "GraphQL": { - "endpoint": "YOUR_API_ENDPOINT", - "region": "YOUR_API_REGION", - "defaultAuthMode": "YOUR_DEFAULT_AUTHORIZATION_MODE", - }, - }, -} -``` - - - - - - - - -### Per-user / owner-based data access - -To restrict a record's access to a specific user, use the `owner` authorization strategy. When `owner` authorization is configured, only the record's `owner` is allowed the specified operations. - -```graphql -# The "owner" of a Todo is allowed to create, read, update, and delete their own todos -type Todo @model @auth(rules: [{ allow: owner }]) { - content: String -} - -# The "owner" of a Todo record is only allowed to create, read, and update it. -# The "owner" of a Todo record is denied to delete it. -type Todo - @model - @auth(rules: [{ allow: owner, operations: [create, read, update] }]) { - content: String -} -``` - -Behind the scenes, Amplify will automatically add a `owner: String` field to each record which contains the record owner's identity information upon record creation. - -By default, the Cognito user pool's user information is populated into the `owner` field. The value saved includes `sub` and `username` in the format `::`. The API will authorize against the full value of `::` or `sub` / `username` separately and return `username`. You can alternatively configure [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). - -You can override the `owner` field to your own preferred field, by specifying a custom `ownerField` in the authorization rule. - - - -Do not set `ownerField` to your `@primaryKey` field or `id` field if no primary key is specified. If you want to query by the `ownerField`, use an `@index` on that `ownerField` to create a secondary index. - - - -```graphql -type Todo @model @auth(rules: [{ allow: owner, ownerField: "author" }]) { - content: String #^^^^^^^^^^^^^^^^^^^^ - author: String # record owner information now stored in "author" field -} -``` - - - -**By default, owners can reassign the owner of their existing record to another user.** - -To prevent an owner from reassigning their record to another user, protect the owner field (by default `owner: String`) with a [field-level authorization rule](#field-level-authorization-rules). For example, in a social media app, you would want to prevent Alice from being able to reassign Alice's Post to Bob. - -```graphql -type Todo @model @auth(rules: [{ allow: owner }]) { - id: ID! - description: String - owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }]) -} -``` - - - -### Multi-user data access - -If you want to grant a set of users access to a record, you can override the `ownerField` to a list of owners. Use this if you want a dynamic set of users to have access to a record. - -```graphql -type Todo @model @auth(rules: [{ allow: owner, ownerField: "authors" }]) { - content: String - authors: [String] -} -``` - -In the example above, upon record creation, the `authors` list is populated with the creator of the record. The creator can then update the `authors` field with additional users. Any user listed in the `authors` field can access the record. - -### Signed-in user data access - -To restrict a record's access to every signed-in user, use the `private` authorization strategy. - -> If you want to restrict a record's access to a specific user, see [Per-user / owner-based data access](#per-user--owner-based-data-access). `private` authorization applies the authorization rule to **every** signed-in user access. - -```graphql -type Todo @model @auth(rules: [{ allow: private }]) { - content: String -} -``` - -In the example above, anyone with a valid JWT token from Cognito user pool are allowed to access all Todos. - -You can also override the authorization provider. In the example below, you can use an "Authenticated Role" from the Cognito identity pool for granting access to signed-in users. - - - - -When you run `amplify add auth`, the Amplify CLI generates scoped down IAM policies for the "Authenticated role" in Cognito identity pool automatically. - -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: private, provider: iam }]) { - id: ID! - title: String! -} -``` - - - -Designate an Amazon Cognito identity pool role for authenticated identities by setting the `identityPoolConfig` property: - -```ts -// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well -import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha'; - -const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', { - allowUnauthenticatedIdentities: true, - authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({ - userPool: , - userPoolClient: , - })] }, -}); - -new AmplifyGraphqlApi(this, "MyNewApi", { - definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - }, - identityPoolConfig: { - identityPoolId: identityPool.identityPoolId, - authenticatedUserRole: identityPool.authenticatedRole, - unauthenticatedUserRole: identityPool.unauthenticatedRole, - } - }, -}) -``` - -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: private, provider: identityPool }]) { - id: ID! - title: String! -} -``` - - - - - -In addition, you can also use OpenID Connect with `private` authorization. See [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). - -**Note:** If you have a connected child model that allows `private` level access, any user authorized to fetch it from the parent model will be able to read the connected child model. For example, - -```graphql -type Todo @model @auth(rules: [{ allow: owner }]) { - id: ID! - name: String! - task: [Task] @hasMany -} - -type Task - @model - @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) { - id: ID! - description: String! -} -``` - -In the above relationship, the owner of a `Todo` record can query all the tasks connected to it, since the `Task` model allows `private` read access. - -### User group-based data access - -To restrict access based on user groups, use the `group` authorization strategy. - -**Static group authorization**: When you want to restrict access to a specific set of user groups, provide the group names in the `groups` parameter. - -```graphql -type Salary @model @auth(rules: [{ allow: groups, groups: ["Admin"] }]) { - id: ID! - wage: Int - currency: String -} -``` - -In the example above, only users that are part of the "Admin" user group are granted access to the Salary model. - - -**Dynamic group authorization**: When you want to restrict access to a set of user groups. - -```graphql -# Dynamic group authorization with multiple groups -type Post @model @auth(rules: [{ allow: groups, groupsField: "groups" }]) { - id: ID! - title: String - groups: [String] -} - -# Dynamic group authorization with a single group -type Post @model @auth(rules: [{ allow: groups, groupsField: "group" }]) { - id: ID! - title: String - group: String -} -``` - -With dynamic group authorization, each record contains an attribute specifying what Cognito groups should be able to access it. Use the `groupsField` argument to specify which attribute in the underlying data store holds this group information. To specify that a single group should have access, use a field of type `String`. To specify that multiple groups should have access, use a field of type `[String]`. - -By default, `group` authorization leverages Amazon Cognito user pool groups but you can also use OpenID Connect with `group` authorization. See [OpenID Connect as an authorization provider](#using-oidc-authorization-provider). - -**Known limitations for real-time subscriptions when using dynamic group authorization**: - -1. If you authorize based on a single group per record, then subscriptions are only supported if the user is part of 5 or fewer user groups -2. If you authorize via an array of groups (`groups: [String]` example above), - -- subscriptions are only supported if the user is part of 20 or fewer groups -- you can only authorize 20 or fewer user groups per record - - -### Custom authorization rule - -You can define your own custom authorization rule with a Lambda function. - -```graphql -type Salary @model @auth(rules: [{ allow: custom }]) { - id: ID! - wage: Int - currency: String -} -``` - -The Lambda function of choice will receive an authorization token from the client and execute the desired authorization logic. The AppSync GraphQL API will receive a payload from Lambda after invocation to allow or deny the API call accordingly. - - - - -Configure the GraphQL API with the Lambda authorization mode, run the following command in your Terminal: - -```bash -amplify update api -``` - -``` -? Select a setting to edit: -> Authorization modes - -> Lambda - -? Choose a Lambda source: -> Create a new Lambda function -``` - - - - -To configure a Lambda function as the authorization mode, set the `lambdaConfig` in the CDK construct. Use the `ttl` to designate the toke expiry time. - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'AWS_LAMBDA', - lambdaConfig: { - function: new lambda.Function(this, 'MyAuthLambda', { - code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/auth')), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_18_X - }), - ttl: cdk.Duration.seconds(10) - } - } -}); -``` - -You can leverage this Lambda function code template as a starting point to author your authorization handler code: - -```js -// This is sample code. Please update this to suite your schema - -/** - * @type {import('@types/aws-lambda').APIGatewayProxyHandler} - */ -exports.handler = async (event) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - const { - authorizationToken, - requestContext: { apiId, accountId } - } = event; - const response = { - isAuthorized: authorizationToken === 'custom-authorized', - resolverContext: { - // eslint-disable-next-line spellcheck/spell-checker - userid: 'user-id', - info: 'contextual information A', - more_info: 'contextual information B' - }, - deniedFields: [ - `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`, - `Mutation.createEvent` - ], - ttlOverride: 300 - }; - console.log(`response >`, JSON.stringify(response, null, 2)); - return response; -}; -``` - - - - -You can use the default Amplify provided template as a starting point for your custom authorization rule. The authorization Lambda function receives: - -```json -{ - "authorizationToken": "ExampleAuthToken123123123", # Authorization token specified by client - "requestContext": { - "apiId": "aaaaaa123123123example123", # AppSync API ID - "accountId": "111122223333", # AWS Account ID - "requestId": "f4081827-1111-4444-5555-5cf4695f339f", - "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", # GraphQL query - "operationName": "MyQuery", # GraphQL operation name - "variables": {} # any additional variables supplied to the operation - } -} -``` - -Your Lambda authorization function needs to return the following JSON: - -```json -{ - // required - "isAuthorized": true, // if "false" then an UnauthorizedException is raised, access is denied - "resolverContext": { "banana": "very yellow" }, // JSON object visible as $ctx.identity.resolverContext in VTL resolver templates - - // optional - "deniedFields": ["TypeName.FieldName"], // Forces the fields to "null" when returned to the client - "ttlOverride": 10 // The number of seconds that the response should be cached for. Overrides default specified in "amplify update api" -} -``` - -Review the Amplify Library documentation to set the custom authorization token for [GraphQL API](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules#aws-lambda) and [DataStore](/gen1/[platform]/prev/build-a-backend/more-features/datastore/authz-rules-setup/#configure-custom-authorization-logic-with-aws-lambda). - -## Configure multiple authorization rules - -When combining multiple authorization rules, they are "logically OR"-ed. - -```graphql -type Post - @model - @auth( - rules: [ - { allow: public, operations: [read], provider: iam } - { allow: owner } - ] - ) { - title: String - content: String -} -``` - -```js -import { createPost } from './graphql/mutations'; -import { listPosts } from './graphql/queries'; - -// Creating a post is restricted to Cognito User Pools -const newPostResult = await client.graphql({ - query: queries.createPost, - variables: { input: { title: 'Hello World' } }, - authMode: 'userPool' -}); - -// Listing posts is available to all users (verified by IAM) -const listPostsResult = await client.graphql({ - query: queries.listPosts, - authMode: 'iam' -}); -``` - -In the example above: - -- any user (signed in or not, verified by IAM) is allowed to read all posts -- owners are allowed to create, read, update, and delete their own posts. - -If you are using DataStore and have multiple authorization rules, you can let DataStore automatically determine the best authorization mode client-side. Review how to [Configure Multiple Authorization Types](/gen1/[platform]/prev/build-a-backend/more-features/datastore/authz-rules-setup/#configure-multiple-authorization-types) on DataStore for more details. - -## Field-level authorization rules - -When an authorization rule is added to a field, it'll strictly define the authorization rules applied on the field. Field-level authorization rules **do not** inherit model-level authorization rules. Meaning, only the specified field-level authorization rule is applied. - -```graphql -type Employee - @model - @auth(rules: [{ allow: private, operations: [read] }, { allow: owner }]) { - name: String - email: String - ssn: String @auth(rules: [{ allow: owner }]) -} -``` - -In the example above: - -- Owners are allowed to create, read, update, and delete Employee records they own -- Any signed in user has read access -- Any signed in user can read data with the exception of the `ssn` field. This field only has owner auth applied, the field-level auth rule means that model-level auth rules are not applied - - - -To prevent sensitive data from being sent over subscriptions, the GraphQL Transformer needs to alter the response of mutations for those fields by setting them to null. Therefore, to facilitate field-level authorization with subscriptions, you need to either apply field-level authorization rules to all **required** fields, make the other fields nullable, or disable subscriptions by setting it to public or off. - - - -In the example above: - -- **any signed in user** is allowed to read the list of employees' `name` and `email` fields -- **only the employee/owner themselves** have CRUD access to their `ssn` field - - - -To prevent unintended loss of data, the user or role that attempts to `delete` a record should have delete permissions on every field of the `@model` annotated GraphQL type. For example, in the schema below: - -```graphql -type Todo - @model - @auth( - rules: [ - { allow: private, provider: iam } - { allow: groups, groups: ["Admin"] } - ] - ) { - id: ID! - name: String! - @auth( - rules: [ - { allow: private, provider: iam } - { allow: groups, groups: ["Admin"] } - ] - ) - description: String @auth(rules: [{ allow: private, provider: iam }]) -} -``` - -Since the `description` field is not accessible by "Admin" Cognito group users, they cannot delete any `Todo` records. - - - -## Advanced - -### Review and print access control matrix - -Verify your API's access control matrix, by running the following command: - -```bash -amplify status api -acm Blog -``` - -```console -iam:public - ┌─────────┬────────┬──────┬────────┬────────┐ - │ (index) │ create │ read │ update │ delete │ - ├─────────┼────────┼──────┼────────┼────────┤ - │ title │ false │ true │ false │ false │ - │ content │ false │ true │ false │ false │ - └─────────┴────────┴──────┴────────┴────────┘ -userPools:owner:owner - ┌─────────┬────────┬──────┬────────┬────────┐ - │ (index) │ create │ read │ update │ delete │ - ├─────────┼────────┼──────┼────────┼────────┤ - │ title │ true │ true │ true │ true │ - │ content │ true │ true │ true │ true │ - └─────────┴────────┴──────┴────────┴────────┘ -``` - -### Use IAM authorization within the AppSync console - - - - - -IAM-based `@auth` rules are scoped down to only work with Amplify-generated IAM roles. To access the GraphQL API with IAM authorization within your AppSync console, you need to explicitly allow list the IAM user's name. Add the allow-listed IAM users by adding them to `amplify/backend/api//custom-roles.json`. (Create the `custom-roles.json` file if it doesn't exist). Append the `adminRoleNames` array with the IAM role or user names: - -```json -{ - "adminRoleNames": [""] -} -``` - - - - -To grant any IAM principal (AWS Resource, IAM role, IAM user, etc) access, **with the exception of Amazon Cognito identity pool roles**, to this GraphQL API in CDK, you need to enable IAM authorization mode via the `iamConfig` property of the CDK construct. - -```typescript -const userRole = Role.fromRoleName( - this, - 'MyUserRole', - '' -); - -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - }, - iamConfig: { - // Set this value to true. - enableIamAuthorizationMode: true - } - } -}); -``` - -These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. - - - -These "Admin Roles" have special access privileges that are scoped based on their IAM policy instead of any particular `@auth` rule. - -### Using OIDC authorization provider - -`private`, `owner`, and `group` authorization can be configured with an OpenID Connect (OIDC) authorization mode. Add `provider: oidc` to the authorization rule. - - - - -Upon the next `amplify push`, Amplify CLI prompts you for the _OpenID Connect provider domain_, _Client ID_, _Issued at TTL_, and _Auth Time TTL_. - - - - -Use the `oidcConfig` property to configure the _OpenID Connect provider domain_, _Client ID_, _Issued at TTL_, and _Auth Time TTL_. - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'OPENID_CONNECT', - oidcConfig: { - oidcIssuerUrl: '...', - oidcProviderName: '...', - tokenExpiryFromAuth: '...', - tokenExpiryFromIssue: '...', - clientId: '...' - } - } -}); -``` - - - - -```graphql -type Todo - @model - @auth( - rules: [ - { allow: owner, provider: oidc, identityClaim: "user_id" } - { allow: private, provider: oidc } - { allow: group, provider: oidc, groupClaim: "user_groups" } - ] - ) { - content: String -} -``` - -The example above highlights the supported authorization strategies with `oidc` authorization provider. For `owner` and `group` authorization, you also need to [specify a custom identity and group claim](#configure-custom-identity-and-group-claims). - -### Configure custom identity and group claims - -`@auth` supports using custom claims if you do not wish to use the default Amazon Cognito-provided "cognito:groups" or the double-colon-delimited claims, "sub::username", from your JWT token. This can be helpful if you are using tokens from a 3rd party OIDC system or if you wish to populate a claim with a list of groups from an external system, such as when using a [Pre Token Generation Lambda Trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html) which reads from a database. To use custom claims specify `identityClaim` or `groupClaim` as appropriate like in the example below: - -```graphql -type Post - @model - @auth( - rules: [ - { allow: owner, identityClaim: "user_id" } - { allow: groups, groups: ["Moderator"], groupClaim: "user_groups" } - ] - ) { - id: ID! - owner: String - postname: String - content: String -} -``` - -In this example the record owner will check against a `user_id` claim. Similarly, if the `user_groups` claim contains a "Moderator" string then access will be granted. - -### Grant Lambda function access to GraphQL API - -Lambda functions' IAM execution role do not immediately grant access to Amplify's GraphQL API because the API operates on a "deny-by-default"-basis. Access need to be explicitly granted. Depending on how your function is deployed, the workflow slightly differ - - - - - -If you grant a Lambda function in your Amplify project access to the GraphQL API via `amplify update function`, then the Lambda function's IAM execution role is allow-listed to honor the permissions granted on the `Query`, `Mutation`, and `Subscription` types. - -Therefore, these functions have special access privileges that are scoped based on their IAM policy instead of any particular `@auth` rule. - - - -Once you grant a function access to the GraphQL API, it is required to redeploy the API to apply the permissions. To do so, run the command `amplify api gql-compile --force` before deployment via `amplify push`. - - - - - - -To grant any IAM principal (AWS Resource, IAM role, IAM user, etc), **with the exception of Amazon Cognito identity pool roles**, to this GraphQL API in CDK, you need to enable IAM authorization mode on the CDK construct. - -```typescript -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - }, - iamConfig: { - // Must be set to `true`. Then grant your Lambda function's execution role access to the API - enableIamAuthorizationMode: true - } - } -}); -``` - -These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. - - - - - -To grant an external AWS Resource or an IAM role access to this GraphQL API, you need to explicitly list the IAM role's name or the AWS Resource's name by adding it to `amplify/backend/api//custom-roles.json`. (Create the `custom-roles.json` file if it doesn't exist). Append the `adminRoleNames` array with the IAM role name or AWS Resource name: - -```json -{ - "adminRoleNames": ["", ""] -} -``` - -You can use the symbol `${env}` to reference the current Amplify CLI environment. - -These "Admin Roles" have special access privileges that are scoped based on their [IAM policy](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization) instead of any particular `@auth` rule. - - - - - - - -Refer to the [sample code](/[platform]/build-a-backend/graphqlapi/connect-from-server-runtime/#iam-authorization) to learn how to sign the request to call the GraphQL API using IAM authorization. - - - -### How it works - -Definition of the `@auth` directive: - -```graphql -# When applied to a type, augments the application with -# owner and group-based authorization rules. -directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION -input AuthRule { - allow: AuthStrategy! - provider: AuthProvider - ownerField: String # defaults to "owner" when using owner auth - identityClaim: String # defaults to "sub::username" when using owner auth - groupClaim: String # defaults to "cognito:groups" when using Group auth - groups: [String] # Required when using Static Group auth - groupsField: String # defaults to "groups" when using Dynamic Group auth - operations: [ModelOperation] # Required for finer control -} - -enum AuthStrategy { - owner - groups - private - public - custom -} -enum AuthProvider { - apiKey - iam - oidc - userPools - function -} -enum ModelOperation { - create - update - delete - read # Short-hand to allow "get", "list", "sync", "listen", and "search" - get # Retrieves an individual item - list # Retrieves a list of items - sync # Enables ability to sync offline/online changes (including via DataStore) - listen # Subscribes to real-time changes - search # Enables ability to search using @searchable directive -} -``` - -Authorization rules consists of: - -- **authorization strategy** (`allow`): who the authorization rule applies to -- **authorization provider** (`provider`): which mechanism is used to apply the authorization rule (API Key, IAM, Amazon Cognito user pool, OIDC) -- **authorized operations** (`operations`): which operations are allowed for the given strategy and provider. If not specified, `create`, `read`, `update`, and `delete` operations are allowed. - - **`read` operation**: `read` operation can be replaced with `get`, `list`, `sync`, `listen`, and `search` for a more granular query access - - - -If you use DataStore instead of the API category to connect to your AppSync API, then you must allow `listen` and `sync` operations for your data model. - - - -**API Keys** are best used for public APIs (or parts of your schema which you wish to be public) or prototyping, and you must specify the expiration time before deploying. **IAM** authorization uses [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) to make request with policies attached to Roles. OIDC tokens provided by **Amazon Cognito user pool** or **3rd party OpenID Connect** providers can also be used for authorization, and enabling this provides a simple access control requiring users to authenticate to be granted top level access to API actions. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx deleted file mode 100644 index 6f66ba7b691..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/data-modeling/index.mdx +++ /dev/null @@ -1,1266 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Customize your data model', - description: 'Customize your data model with primary keys, secondary indexes, and model relationships.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/data-modeling/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - - -Amplify automatically creates Amazon DynamoDB database tables for GraphQL types annotated with the `@model` directive in your GraphQL schema. You can create relations between the data models via the `@hasOne`, `@hasMany`, `@belongsTo`, and `@manyToMany` directives. - -## Setup database tables - -The following GraphQL schema automatically creates a database table for "Todo". `@model` will also automatically add an `id` field as a primary key to the database table. _See [Configure a primary key](#configure-a-primary-key) to learn how to customize the primary key._ - -```graphql -type Todo @model { - content: String -} -``` - -Upon `amplify push` or `cdk deploy`, Amplify deploys the Todo database table and a corresponding GraphQL API to perform create, read, update, delete, and list operations. - -In addition, `@model` also adds the helper fields `createdAt` and `updatedAt` to your type. The values for those fields are read-only by clients unless explicitly overwritten. See [Customize creation and update timestamps](#customize-creation-and-update-timestamps) to learn more. - -Try listing all the todos by executing the following query: - -```graphql -query QueryAllTodos { - listTodos() { - todos { - items { - id - content - createdAt - updatedAt - } - } - } -} -``` - - - -```js -import { Amplify } from 'aws-amplify'; -import { generateClient } from 'aws-amplify/api'; -import config from './amplifyconfiguration.json'; -import { listTodos } from './graphql/queries'; - -const client = generateClient(); - -Amplify.configure(config); - -try { - const result = await client.graphql({ query: listTodos }); - const todos = result.data.listTodos; -} catch (res) { - const { errors } = res; - console.error(errors); -} -``` - - - -### Configure a primary key - -Every GraphQL type with the `@model` directive will automatically have an `id` field set as the primary key. You can override this behavior by marking another required field with the `@primaryKey` directive. - -In the example below, `todoId` is the database's primary key and an `id` field will no longer be created automatically anymore by the `@model` directive. - -```graphql -type Todo @model { - todoId: ID! @primaryKey - content: String -} -``` - -Without any further configuration, you'll only be able to query for a Todo via an exact equality match of its primary key field. In the example above, this is the `todoId` field. - -> Note: After a primary key is configured and deployed, you can't change it without deleting and recreating your database table. - -You can also specify "sort keys" to use a combination of different fields as a primary key. This also allows you to apply more advanced sorting and filtering conditions on the specified "sort key fields". - -```graphql -type Inventory @model { - productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) - warehouseID: ID! - InventoryAmount: Int! -} -``` - -The schema above will allow you to pass different conditions to query the correct inventory item: - -```graphql -query QueryInventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { - getInventory(productID: $productID, warehouseID: $warehouseID) { - productID - warehouseID - inventoryAmount - } -} -``` - - - -```js -import { getInventory } from './graphql/queries'; - -const result = await client.graphql({ - query: getInventory, - variables: { - productID: 'product-id', - warehouseID: 'warehouse-id' - } -}); -const inventory = result.data.getInventory; -``` - - - -### Configure a secondary index - -Amplify uses Amazon DynamoDB tables as the underlying data source for @model types. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `@index` directive to configure a secondary index. - -> **Amazon DynamoDB** is a key-value and document database that delivers single-digit millisecond performance at any scale but making it work for your access patterns requires a bit of forethought. DynamoDB query operations may use at most two attributes to efficiently query data. The first query argument passed to a query (the hash key) must use strict equality and the second attribute (the sort key) may use gt, ge, lt, le, eq, beginsWith, and between. DynamoDB can effectively implement a wide variety of access patterns that are powerful enough for the majority of applications. - -A secondary index consists of a "hash key" and, optionally, a "sort key". Use the "hash key" to perform strict equality and the "sort key" for greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), equals (eq), begins with, and between operations. - -```graphql -type Customer @model { - id: ID! - name: String! - phoneNumber: String - accountRepresentativeID: ID! @index -} -``` - -The example client query below allows you to query for "Customer" records based on their `accountRepresentativeID`: - -```graphql -query QueryCustomersForAccountRepresentative($accountRepresentativeID: ID!) { - customersByAccountRepresentativeID( - accountRepresentativeID: $accountRepresentativeID - ) { - customers { - items { - id - name - phoneNumber - } - } - } -} -``` - - - - -```js -import { customersByAccountRepresentativeID } from './graphql/queries'; - -const result = await client.graphql({ - query: customersByAccountRepresentativeID, - variables: { - accountRepresentativeID: 'account-rep-id' - } -}); -const customers = result.data.customersByAccountRepresentativeID; -``` - - - -You can also overwrite the `queryField` or `name` to customize the GraphQL query name, or secondary index name respectively: - -```graphql -type Customer @model { - id: ID! - name: String! - phoneNumber: String - accountRepresentativeID: ID! - @index(name: "byRepresentative", queryField: "customerByRepresentative") -} -``` - -```graphql -query QueryCustomersForAccountRepresentative($representativeId: ID!) { - customerByRepresentative(accountRepresentativeID: $representativeId) { - customers { - items { - id - name - phoneNumber - } - } - } -} -``` - - - -```js -import { customerByRepresentative } from './graphql/queries'; - -const result = await client.graphql({ - query: customerByRepresentative, - variables: { - accountRepresentativeID: 'account-rep-id' - } -}); -const customer = result.data.customerByRepresentative; -``` - - - -To optionally configure sort keys, provide the additional fields in the `sortKeyFields` parameter: - -```graphql -type Customer @model @auth(rules: [{ allow: public }]) { - id: ID! - name: String! @index(name: "byNameAndPhoneNumber", sortKeyFields: ["phoneNumber"], queryField: "customerByNameAndPhone") - phoneNumber: String - accountRepresentativeID: ID! @index -``` - -The example client query below allows you to query for "Customer" based on their `name` and filter based on `phoneNumber`: - -```graphql -query MyQuery { - customerByNameAndPhone(phoneNumber: { beginsWith: "+1" }, name: "Rene") { - items { - id - name - phoneNumber - } - } -} -``` - - - -```js -import { customerByNameAndPhone } from './graphql/queries'; - -const result = await client.graphql({ - query: customerByNameAndPhone, - variables: { - phoneNumber: { beginsWith: '+1' }, - name: 'Rene' - } -}); - -const customer = result.data.customerByNameAndPhone; -``` - - - - - -## Setup relationships between models - -Create "has one", "has many", "belongs to", and "many to many" relationships between `@model` types. - -| Relationship | Description | -| --- | --- | -| `@hasOne` | Create a one-directional one-to-one relationship between two models. For example, a Project "has one" Team. This allows you to query the team from the project record. | -| `@hasMany` | Create a one-directional one-to-many relationship between two models. For example, a Post has many comments. This allows you to query all the comments from the post record. | -| `@belongsTo` | Use a "belongs to" relationship to make a "has one" or "has many" relationship bi-directional. For example, a Project has one Team and a Team belongs to a Project. This allows you to query the team from the project record and vice versa. | -| `@manyToMany` | Configures a "join table" between two models to facilitate a many-to-many relationship. For example, a Blog has many Tags and a Tag has many Blogs. | - -### Has One relationship - -import gqlv2callout from '/src/fragments/cli/gqlv2callout.mdx'; - - - -Create a one-directional one-to-one relationship between two models using the `@hasOne` directive. - -In the example below, a Project has a Team. - -```graphql -type Project @model { - id: ID! - name: String - team: Team @hasOne -} - -type Team @model { - id: ID! - name: String! -} -``` - -This generates queries and mutations that allow you to retrieve the related record from the source record: - -```graphql -mutation CreateProject { - createProject(input: { projectTeamId: "team-id", name: "Some Name" }) { - team { - name - id - } - name - id - } -} -``` - - -```js -import { createProject } from './graphql/mutations'; - -const result = await client.graphql({ - query: createProject, - variables: { - input: { projectTeamId: 'team-id', name: 'Some Name' } - } -}); - -const project = result.data.createProject; -``` - -To customize the field to be used for storing the relationship information, set the `fields` array argument and matching it to a field on the type: - -```graphql -type Project @model { - id: ID! - name: String - teamID: ID - team: Team @hasOne(fields: ["teamID"]) -} - -type Team @model { - id: ID! - name: String! -} -``` - -In this case, the Project type has a `teamID` field added as an identifier for the team. @hasOne can then get the connected Team object by querying the Team table with this `teamID`: - -```graphql -mutation CreateProject { - createProject(input: { name: "New Project", teamID: "a-team-id" }) { - id - name - team { - id - name - } - } -} -``` - - -```js -import { createProject } from './graphql/mutations'; - -const result = await client.graphql({ - query: createProject, - variables: { - input: { - teamID: 'team-id', - name: 'New Project' - } - } -}); -const project = result.data.createProject; -``` - -A `@hasOne` relationship always uses a reference to the primary key of the related model, by default `id` unless overridden with the [`@primaryKey` directive](#configure-a-primary-key). - -### Has Many relationship - - - -Create a one-directional one-to-many relationship between two models using the `@hasMany` directive. - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany -} - -type Comment @model { - id: ID! - content: String! -} -``` - -This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record: - -```graphql -mutation CreatePost { - createPost(input: { title: "Hello World!!" }) { - title - id - comments { - items { - id - content - } - } - } -} -``` - - -```js -import { createPost } from './graphql/mutations'; - -const result = await client.graphql({ - query: createPost, - variables { - input: { title: 'Hello World!!' }, - } -}); -const post = result.data.createPost; -const comments = post.comments.items; -``` - -Under the hood, `@hasMany` configures a default secondary index on the related table to enable you to query the related model from the source model. - -To customize the specific secondary index used for the "has many" relationship, create an `@index` directive on the field in the related table where you want to assign the secondary index. - -Next, provide the secondary index with a `name` attribute and a value. Optionally, you can configure a “sort key” on the secondary index by providing a `sortKeyFields` attribute and a designated field as its value. - -On the `@hasMany` configuration, pass in the name value from your secondary index as the value for the `indexName` parameter. Then, pass in the respective `fields` that match the connected index. - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) -} - -type Comment @model { - id: ID! - postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) - content: String! -} -``` - -In this case, the Comment type has a `postID` field added to store the reference of Post record. The `id` field referenced by `@hasMany` is the `id` on the `Post` type. `@hasMany` can then get the connected Comment object by querying the Comment table's secondary index "byPost" with this `postID`: - -```graphql -mutation CreatePost { - createPost(input: { title: "Hello world!" }) { - comments { - items { - postID - content - id - } - } - title - id - } -} -``` - -```js -import { createPost, createComment } from './graphql/mutations'; -import { getPost } from './graphql/mutations'; - -// create post -const result = await client.graphql({ - query: createPost, - variables: { - input: { title: 'Hello World!!' } - } -}); -const post = result.data.createPost; - -// create comment -await client.graphql({ - query: createComment, - variables: { - input: { content: 'Hi!', postID: post.id } - } -}); - -// get post -const result = await client.graphql({ - query: getPost, - variables: { id: post.id } -}); - -const postWithComments = result.data.createPost; -const postComments = postWithComments.comments.items; // access comments from post -``` - - -### Belongs To relationship - -Make a "has one" or "has many" relationship bi-directional with the `@belongsTo` directive. - - - - - -For 1:1 relationships, the @belongsTo directive solely facilitates the ability for you to query from both sides of the relationship. When updating a bi-directional hasOne relationship, you must update both sides of the relationship with corresponding IDs. - -```graphql -type Project @model { - id: ID! - name: String - team: Team @hasOne -} - -type Team @model { - id: ID! - name: String! - project: Project @belongsTo -} -``` - -This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: - -```graphql -mutation CreateProject { - createProject(input: { name: "New Project", teamID: "a-team-id" }) { - id - name - team { - # query team from project - id - name - project { - # bi-directional query: team to project - id - name - } - } - } -} -``` - -```js -import { createProject, createTeam, updateTeam } from './graphql/mutations'; - -// create team -const result = await client.graphql({ - query: createTeam, - variables: { - input: { name: 'New Team' } - } -}); -const team = result.data.createTeam; - -// create project -const result = await client.graphql({ - query: createProject, - variables: { - input: { name: 'New Project', projectTeamId: team.id } - } -}); -const project = result.data.createProject; -const projectTeam = project.team; // access team from project - -// update team -const updateTeamResult = await client.graphql({ - query: updateTeam, - variables: { - input: { id: team.id, teamProjectId: project.id } - } -}); - -const updatedTeam = updateTeamResult.data.updateTeam; -const teamProject = updatedTeam.project; // access project from team -``` - - - - - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany -} - -type Comment @model { - id: ID! - content: String! - post: Post @belongsTo -} -``` - -This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: - -```graphql -mutation CreatePost { - createPost(input: { title: "Hello World!!" }) { - title - id - comments { - # query comments from the post - items { - id - content - post { - # bi-directional query: comment to post - id - title - } - } - } - } -} -``` - -```js -import { createPost, createComment } from './graphql/mutations'; -import { getPost } from './graphql/mutations'; - -// create post -const result = await client.graphql({ - query: createPost, - variables: { - input: { title: 'Hello World!!' } - } -}); -const post = result.data.createPost; - -// create comment -await client.graphql({ - query: createComment, - variables: { - input: { content: 'Hi!', postID: post.id } - } -}); - -// get post -const result = await client.graphql({ - query: getPost, - variables: { id: post.id } -}); - -const postWithComments = result.data.createPost; -const postComments = postWithComments.comments.items; // access comments from post - -const commentPost = postComments[0].post; // access post from comment; -``` - - - - - -`@belongsTo` can be used without the `fields` argument. In those cases, a field is automatically generated to reference the parent’s primary key. - -Alternatively, you set up a custom field to store the reference of the parent object. An example bidirectional “has many” relationship is shown below. - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) -} - -type Comment @model { - id: ID! - postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) - content: String! - post: Post @belongsTo(fields: ["postID"]) -} -``` - -> Note: The `@belongsTo` directive requires that a `@hasOne` or `@hasMany` relationship already exists from parent to the related model. - -### Many-to-many relationship - -Create a many-to-many relationship between two models with the `@manyToMany` directive. Provide a common `relationName` on both models to join them into a many-to-many relationship. - -```graphql -type Post @model { - id: ID! - title: String! - content: String - tags: [Tag] @manyToMany(relationName: "PostTags") -} - -type Tag @model { - id: ID! - label: String! - posts: [Post] @manyToMany(relationName: "PostTags") -} -``` - -Under the hood, the `@manyToMany` directive will create a "join table" named after the `relationName` to facilitate the many-to-many relationship. This generates queries and mutations that allow you to retrieve the related Comment records from the source Post record and vice versa: - -```graphql -mutation CreatePost { - createPost(input: { title: "Hello World!!" }) { - id - title - content - tags { - # queries the "join table" PostTags - items { - tag { - # related Tag records from Post - id - label - posts { - # queries the "join table" PostTags - items { - post { - # related Post records from Tag - id - title - content - } - } - } - } - } - } - } -} -``` - -```js -import { createPost, createTag, createPostTags } from './graphql/mutations'; -import { listPosts } from './graphql/queries'; - -// create post -const result = await client.graphql({ - query: createPost, - variables: { - input: { title: 'Hello World' } - } -}); -const post = result.data.createPost; - -// create tag -const tagResult = await client.graphql({ - query: createTag, - variables: { - input: { - label: 'My Tag' - } - } -}); -const tag = tagResult.data.createTag; - -// connect post and tag -await client.graphql({ - query: createPostTags, - variables: { - input: { - postId: post.id, - tagId: tag.id - } - } -}); - -// get posts -const listPostsResult = await client.graphql({ query: listPosts }); -const posts = listPostsResult.data.listPosts; - -const postTags = posts[0].tags; // access tags from post -``` - -## Assign default values for fields - -You can use the `@default` directive to specify a default value for optional [scalar type fields](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html) such as `Int`, `String`, and more. - -```graphql -type Todo @model { - content: String @default(value: "My new Todo") - # Note: all "value" parameters must be passed as a string value. - # Under the hood, Amplify will parse the string values into respective types. - # For example, to set a default value for an integer field, - # you must pass in `"0"` instead of `0` without the double-quotes. - likes: Int @default(value: "0") # -} -``` - -If you create a new Todo and don't supply a `content` input, Amplify will ensure that `My new Todo` is auto populated as a value. When `@default` is applied, non-null assertions using `!` are disregarded. For example, `String!` is treated the same as `String`. - -## Server-side filtering for subscriptions - -A server-side subscription filter expression is automatically generated for any `@model` type. - -```graphql -type Task @model { - title: String! - description: String - type: String - priority: Int -} -``` - -You can filter the subscriptions server-side by passing a filter expression. For example: If you want to subscribe to tasks of type `Security` and priority greater than `5`, you can set the `filter` argument accordingly. - -```graphql -subscription OnCreateTask { - onCreateTask( - filter: { and: [{ type: { eq: "Security" } }, { priority: { gt: 5 } }] } - ) { - title - description - type - priority - } -} -``` - -```js -import { onCreateTask } from './graphql/subscriptions'; - -const subscription = client.graphql({ - query: onCreateTask, - variables: { - filter: { - and: [ - { type: { eq: "Security" } } - { priority: { gt: 5 } } - ] - } - } -}).subscribe({ - next: ({ data }) => console.log(data), - error: (error) => console.warn(error) -}); -``` - -If you want to get all subscription events, don’t pass any `filter` parameters. - - - -**Important**: Passing an empty object `{}` as a filter is NOT recommended. Using `{}` as a filter might cause inconsistent behavior based on your data model's authorization rules. - - - - - -## Advanced - -### Rename generated queries, mutations, and subscriptions - -You can override the names of any `@model`-generated GraphQL queries, mutations, and subscriptions by supplying the desired name. - -```graphql -type Todo @model(queries: { get: "queryFor" }) { - name: String! - description: String -} -``` - -In the example above, you will be able to run a `queryForTodo` query to get a single Todo element. - -### Disable generated queries, mutations, and subscriptions - -You can disable specific operations by assigning their value to `null`. - -```graphql -type Todo @model(queries: { get: null }, mutations: null, subscriptions: null) { - name: String! - description: String -} -``` - -The example above disables the `getTodo` query, all mutations, and all subscriptions while allowing the generation of other queries such as `listTodo`. - -### Creating a custom query - -You can disable the `get` query and create a custom query that enables us to retrieve a single Todo model. - -```graphql -type Query { - getMyTodo(id: ID!): Todo @function(name: "getmytodofunction-${env}") -} -``` - -The example above creates a custom query that utilizes the `@function` directive to call a Lambda function for this query. - -For the type definitions of queries, mutations, and subscriptions, see [Type Definitions of the `@model` Directive](#type-definition-of-the-`@model`-directive). - -### Customize creation and update timestamps - -The `@model` directive automatically adds `createdAt` and `updatedAt` timestamps to each entity. The timestamp field names can be changed by passing timestamps attribute to the directive. - -```graphql -type Todo - @model(timestamps: { createdAt: "createdOn", updatedAt: "updatedOn" }) { - name: String! - description: String -} -``` - -For example, the schema above will allow you to query for the following contents: - -```graphql -type Todo { - id: ID! - name: String! - description: String - createdOn: AWSDateTime! - updatedOn: AWSDateTime! -} -``` - -### Modify subscriptions (real-time updates) access level - -By default, real-time updates are on for all `@model` types, which means customers receive real-time updates and authorization rules are applied during initial connection time. You can also turn off subscriptions for that model or make the real-time updates public, receivable by all subscribers. - -```graphql -type Todo - @model(subscriptions: { level: off }) { # or level: public - name: String! - description: String -} -``` - -### Create multiple relationships between two models - -You need to explicitly specify the connection field names if relational directives are used to create two connections of the same type between the two models. - -```graphql -type Individual @model { - id: ID! - homeAddress: Address @hasOne - shippingAddress: Address @hasOne -} - -type Address @model { - id: ID! - homeIndividualID: ID - shippingIndividualID: ID - homeIndividual: Individual @belongsTo(fields: ["homeIndividualID"]) - shipIndividual: Individual @belongsTo(fields: ["shippingIndividualID"]) -} -``` - -### Relationships to a model with a composite primary key - -When a primary key is defined by a _sort key_ in addition to the _hash key_, then it's called a **composite primary key**. - -If you explicitly define the `fields` argument on the `@hasOne`, `@hasMany`, or `@belongsTo` directives and reference a model that has a composite primary key, then you must set the values in the `fields` argument in a specific order: - -- The first value should always be the primary key of the related model. -- Remaining values should match the `sortKeyFields` specified in the `@primaryKey` directive of the related model. - - - - - -```graphql -type Project @model { - projectId: ID! @primaryKey(sortKeyFields: ["name"]) - name: String! - team: Team @hasOne(fields: ["teamId", "teamName"]) - teamId: ID # customized foreign key for child primary key - teamName: String # customized foreign key for child sort key -} - -type Team @model { - teamId: ID! @primaryKey(sortKeyFields: ["name"]) - name: String! -} -``` - - - - - -```graphql -type Project @model { - projectId: ID! @primaryKey(sortKeyFields: ["name"]) - name: String! - team: Team @hasOne(fields: ["teamId", "teamName"]) - teamId: ID # customized foreign key for child primary key - teamName: String # customized foreign key for child sort key -} - -type Team @model { - teamId: ID! @primaryKey(sortKeyFields: ["name"]) - name: String! - project: Project @belongsTo(fields: ["projectId", "projectName"]) - projectId: ID # customized foreign key for parent primary key - projectName: String # customized foreign key for parent sort key -} -``` - - - - - -```graphql -type Post @model { - postId: ID! @primaryKey(sortKeyFields: ["title"]) - title: String! - comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"]) -} - -type Comment @model { - commentId: ID! @primaryKey(sortKeyFields: ["content"]) - content: String! - postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key - postTitle: String # customized foreign key for parent sort key -} -``` - - - - - -```graphql -type Post @model { - postId: ID! @primaryKey(sortKeyFields: ["title"]) - title: String! - comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"]) -} - -type Comment @model { - commentId: ID! @primaryKey(sortKeyFields: ["content"]) - content: String! - post: Post @belongsTo(fields: ["postId", "postTitle"]) - postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key - postTitle: String # customized foreign key for parent sort key -} -``` - - - - - -### Generate a secondary index without a GraphQL query - -Because query creation against a secondary index is automatic, if you wish to define a secondary index that does not have a corresponding query in your API, set the `queryField` parameter to `null`. - -```graphql -type Customer @model { - id: ID! - name: String! - phoneNumber: String - accountRepresentativeID: ID! @index(queryField: null) -} -``` - -### Split GraphQL files - - -Amplify Studio does not support splitting GraphQL schemas. - -If using Amplify Studio, please follow the [Limitations](https://docs.amplify.aws/javascript/tools/console/data/data-model/#split-graphql-files) section of the Data Modeling documentation for Amplify Studio. - - -AWS Amplify supports splitting your GraphQL schema into separate `.graphql` files. - -You can start by creating a `amplify/backend/api//schema/` directory. As an example, you might split up the schema for a blog site by creating `Blog.graphql`, `Post.graphql`, and `Comment.graphql` files. - -You can then run `amplify api gql-compile` and the output build schema will include all the types declared across your schema files. - -As your project grows, you may want to organize your custom queries, mutations, and subscriptions depending on the size and maintenance requirements of your project. You can either consolidate all of them into one file or colocate them with their corresponding models. - -**Using a Single `Query.graphql` File** - -This method involves consolidating all queries into a single `Query.graphql` file. It is useful for smaller projects or when you want to keep all queries in one place. - -1. In the `amplify/backend/api//schema/` directory, create a file named `Query.graphql`. - -2. Copy all query type definitions from your multiple schema files into the `Query.graphql` file. - -3. Make sure all your queries are properly formatted and enclosed within a single `type Query { ... }` block. - -**Using the `extend` Keyword** - -Declaring a `Query` type in separate schema files will result in schema validation errors similar to the following when running `amplify api gql-compile`: - -```sh -🛑 Schema validation failed. - -There can be only one type named "Query". -``` - -Amplify GraphQL schemas support the `extend` keyword, which allows you to extend types with additional fields. In this case, it also allows you to split your custom queries, mutations, and subscriptions into multiple files. This may be more ideal for larger, more complex projects. - -1. Organize your GraphQL schema into multiple files as per your project's architecture. - -2. In one of the files (e.g., `schema1.graphql`), declare your type normally: - -```graphql -type Query { - # initial custom queries -} -``` - -3. In other schema files (e.g., `schema2.graphql`), use the `extend` keyword to add to the type: - -```graphql -extend type Query { - # additional custom queries -} -``` - -The order in which the Query types are extended does not affect the compilation of separate schema files. - - - -Declaring custom Query, Mutation, and/or Subscription with the same field names in another schema file will result in schema validation errors similar to the following: - -`🛑 Object type extension 'Query' cannot redeclare field getBlogById` - - - -## How it works - -### Model directive - -The `@model` directive will generate: - -- An Amazon DynamoDB table with PAY_PER_REQUEST billing mode enabled by default. -- An AWS AppSync DataSource configured to access the table above. -- An AWS IAM role attached to the DataSource that allows AWS AppSync to call the above table on your behalf. -- Up to 8 resolvers (create, update, delete, get, list, onCreate, onUpdate, onDelete) but this is configurable via the queries, mutations, and subscriptions arguments on the @model directive. -- Input objects for create, update, and delete mutations. -- Filter input objects that allow you to filter objects in list queries and relationship fields. -- For list queries the default number of objects returned is 100. You can override this behavior by setting the limit argument. - -**Type definition of the `@model` directive** - -```graphql -directive @model( - queries: ModelQueryMap - mutations: ModelMutationMap - subscriptions: ModelSubscriptionMap - timestamps: TimestampConfiguration -) on OBJECT - -input ModelMutationMap { - create: String - update: String - delete: String -} - -input ModelQueryMap { - get: String - list: String -} - -input ModelSubscriptionMap { - onCreate: [String] - onUpdate: [String] - onDelete: [String] - level: ModelSubscriptionLevel -} - -enum ModelSubscriptionLevel { - off - public - on -} - -input TimestampConfiguration { - createdAt: String - updatedAt: String -} -``` - -### Relational directives - -The relational directives are `@hasOne`, `@hasMany`, `@belongsTo` and `@manyToMany`. - - - - -The `@hasOne` will generate: - -- Foreign key fields in parent type that refer to the primary key and sort key fields of the child model. -- Foreign key fields in parent input object of `create` and `update` mutations. - -**Type definition of the `@hasOne` directive** - -```graphql -directive @hasOne(fields: [String!]) on FIELD_DEFINITION -``` - - - - -The `@hasMany` will generate: - -- Foreign key fields in child type that refer to the primary key and sort key fields of the parent model. -- Foreign key fields in child input object of `create` and `update` mutations. -- A global secondary index (GSI) in the child type Amazon DynamoDB table. - -**Type definition of the `@hasMany` directive** - -```graphql -directive @hasMany( - indexName: String - fields: [String!] - limit: Int = 100 -) on FIELD_DEFINITION -``` - -- The default number of nested objects returned is 100. You can override this behavior by setting the limit argument. - - - - -The `@belongsTo` will generate: - -- Foreign key fields that refer to the primary key and sort key fields of the related model. -- Foreign key fields in the input object of `create` and `update` mutations. - -**Type definition of the `@belongsTo` directive** - -```graphql -directive @belongsTo(fields: [String!]) on FIELD_DEFINITION -``` - - - - -The `@manyToMany` will generate: - -- A joint table defining the intermediate model type with the name of `relationName`. -- Foreign key fields in the joint table that refer to the primary key and sort key fields of both models. -- Foreign key fields in the intermediate model input object of `create` and `update` mutations. - -**Type definition of the `@manyToMany` directive** - -```graphql -directive @manyToMany( - relationName: String! - limit: Int = 100 -) on FIELD_DEFINITION -``` - -- The default number of nested objects returned is 100. You can override this behavior by setting the limit argument. - - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx deleted file mode 100644 index fb93e0d0a27..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/modify-amplify-generated-resources/index.mdx +++ /dev/null @@ -1,504 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Modify Amplify-generated resources', - description: 'Learn more about how to modify Amplify-generated resources for Amplify GraphQL APIs. This allows you to modify underlying AppSync, DynamoDB, Lambda, and OpenSearch resources.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/modify-amplify-generated-resources/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -Amplify GraphQL API uses a variety of auto-generated, underlying AWS services and resources. You can customize these underlying resources to optimize the deployed stack for your specific use case. - - - - - -```bash -amplify override api -``` - -Run the command above to override Amplify-generated GraphQL API resources including AWS AppSync API, Amazon DynamoDB table, Amazon OpenSearch domain, and more. - - - -If you need to customize a specific Amplify-generated VTL resolver, review [Override Amplify-generated resolvers](/gen1/[platform]/prev/build-a-backend/graphqlapi/custom-business-logic/#override-amplify-generated-resolvers) first. - - - -The command creates a new `overrides.ts` file under `amplify/backend/api//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). - - - - -In your CDK project, you can access every underlying resource as an ["L2"](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_using) or ["L1" construct](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_l1_using). Access the generated resources as L2 constructs via the `.resources` property on the returned stack or access the generated resources as L1 constructs using the `.resources.cfnResources` property. - -```ts -const api = new AmplifyGraphQlApi(this, 'api', { }); - -// Access L2 resources under `.resources` -api.resources.tables["Todo"].tableArn; - -// Access L1 resources under `.resources.cfnResources` -api.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true; -Object.values(api.resources.cfnResources.cfnTables).forEach(table => { - table.pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: false }; -}); -``` - - - - - -## Customize Amplify-generated AppSync GraphQL API resources - - - - - -Apply all the overrides in the `override(...)` function. For example to enable X-Ray tracing for the AppSync GraphQL API: - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.api.GraphQLAPI.xrayEnabled = true; -} -``` - -You can override the following GraphQL API resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [GraphQLAPI](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlapi.html) | AWS AppSync GraphQL API resource | -| [GraphQLAPIDefaultApiKey](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-apikey.html) | API Key resource for the AppSync GraphQL API | -| [GraphQLAPITransformerSchema](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlschema.html) | The GraphQL schema that's being deployed. (The output of the GraphQL Transformer) | -| [GraphQLAPINONEDS](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | A "none" data source that is used for requests that don't exit the AppSync API | -| [AmplifyDataStore](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The delta sync table used for Amplify DataStore's conflict resolution | -| [AmplifyDataStoreIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role used to access the delta sync table for DataStore | -| [DynamoDBAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access the DynamoDB resources from AppSync | - - - - -Apply all the customizations on `.resources.graphqlApi` or `.resources.cfnResources.cfnGraphqlApi`. For example to enable X-Ray tracing for the AppSync GraphQL API: - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); - -amplifyApi.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true; -``` - - - - - -## Customize Amplify-generated resources for @model directive - - - - - -Apply all the overrides in the `override(...)` function. Pass in the @model type name into `resources.models[...]` to modify the resources generated for that particular @model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Todo'].modelDDBTable.timeToLiveSpecification = { - attributeName: 'ttl', - enabled: true - }; -} -``` - -You can override the following @model directive resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [modelStack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html) | The nested stack containing all resources for the @model type | -| [modelDDBTable](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The DynamoDB table containing the data for this @model type | -| [modelIamRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access the DynamoDB table for this @model type | -| [modelIamRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access the delta sync table for this @model type in case DataStore is enabled | -| [dynamoDBAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | Default policy associated with the IAM role to access the DynamoDB table for this @model type | -| [modelDatasource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | The AppSync DataSource to representing the DynamoDB table | -| [invokeLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for Lambda-based conflict resolution function | - -For example, you can override a model generated DynamoDB table configuration. - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Todo'].modelDatasource.dynamoDbConfig['deltaSyncConfig'][ - 'baseTableTtl' - ] = '3600'; -} -``` - - - - -Apply all the customizations on `.resources.tables["MODEL_NAME"]` or `.resources.cfnResources.cfnTables["MODEL_NAME"]`. Pass in the @model type name into `.resources.cfnResources.cfnTables["MODEL_NAME"]` to modify the resources generated for that particular @model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); - -amplifyApi.resources.cfnResources.cfnTables['Todo'].timeToLiveSpecification = { - attributeName: 'ttl', - enabled: true -}; -``` - - - - - -### Example - Configure DynamoDB table's billing mode - -Set the [DynamoDB billing mode](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-billingmode) for the DynamoDB table. Either "PROVISIONED" or "PAY_PER_REQUEST". - - - - - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Post'].modelDDBTable.billingMode = 'PAY_PER_REQUEST'; -} -``` - - - - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); - -amplifyApi.resources.cfnResources.cfnTables['Todo'].billingMode = - 'PAY_PER_REQUEST'; -``` - - - - - -### Example - Configure provisioned throughput for DynamoDB table or Global Secondary Index - -Override the default [ProvisionedThroughput](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-provisionedthroughput) provisioned for each `@model` table and its Global Secondary Indexes (GSI). - -Only valid if the "DynamoDBBillingMode" is set to "PROVISIONED" - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Post'].modelDDBTable.provisionedThroughput = { - readCapacityUnits: 5, - writeCapacityUnits: 5 - }; - - /** - * When billing mode is set to "PROVISIONED", it is necessary to specify `provisionedThroughput` for every Global Secondary Index (GSI) that exists in the table. - */ - - resources.models[ - 'Post' - ].modelDDBTable.globalSecondaryIndexes[0].provisionedThroughput = { - readCapacityUnits: 5, - writeCapacityUnits: 5 - }; -} -``` - -### Example - Enable point-in-time recovery for DynamoDB table - -Enable/disable [DynamoDB point-in-time recovery](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-pointintimerecoveryspecification.html) for each `@model` table. - - - - - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.models['Post'].modelDDBTable.pointInTimeRecoverySpecification = { - pointInTimeRecoveryEnabled: true - }; -} -``` - - - - -```ts -const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { - definition: AmplifyGraphqlDefinition.fromFiles( - path.join(__dirname, 'schema.graphql') - ), - authorizationModes: { - defaultAuthorizationMode: 'API_KEY', - apiKeyConfig: { - expires: cdk.Duration.days(30) - } - } -}); - -amplifyApi.resources.cfnResources.cfnTables[ - 'Todo' -].pointInTimeRecoverySpecification = { - pointInTimeRecoveryEnabled: true -}; -``` - - - - - -## Customize Amplify-generated resources for @searchable (OpenSearch) directive - -Apply all the overrides in the `override(...)` function. For example, to modify the OpenSearch instance count: - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { - ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, - instanceCount: 6 - }; -} -``` - -You can override the following @searchable directive resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [OpenSearchDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | The AppSync data source representing the OpenSearch integration | -| [OpenSearchAccessIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access OpenSearch domain | -| [OpenSearchAccessIAMRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access OpenSearch domain | -| [OpenSearchDomain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticsearch-domain.html) | OpenSearch domain containing the @searchable data | -| [OpenSearchStreamingLambdaIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to stream DynamoDB data to OpenSearch domain | -| [OpenSearchStreamingLambdaIAMRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to stream DynamoDB data to OpenSearch domain | -| [CloudwatchLogsAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for granting CloudWatch logs access | -| [OpenSearchStreamingLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | Lambda function to stream DynamoDB data to OpenSearch domain | -| [OpenSearchModelLambdaMapping](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html) | Event source mapping for DynamoDB table stream to Lambda function | - -### Example - Configure Runtime for Streaming Lambda - -You can define the runtime for the `@searchable` by setting the runtime value on the function object itself. This can be done to resolve a deprecated runtime in the event that you cannot upgrade your version of the Amplify CLI. - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchStreamingLambdaFunction.runtime = 'python3.9'; -} -``` - -### Example - Configure OpenSearch Streaming function name - -Override the name of the AWS Lambda searchable streaming function - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchStreamingLambdaFunction.FunctionName = - 'CustomFunctionName'; -} -``` - -### Example - Configure OpenSearch instance version - -Override the `elasticsearchVersion` in the OpenSearch domain created by `@searchable` - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.elasticsearchVersion = 'OpenSearch_1.3'; -} -``` - -### Example - Configure OpenSearch instance type - -Override the type of instance launched into the OpenSearch domain created by `@searchable` - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { - ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, - instanceType: 'm3.medium.elasticsearch' - }; -} -``` - -### Example - Configure OpenSearch instance count - -Override the number of instances launched into the OpenSearch domain created by `@searchable` - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig = { - ...resources.opensearch.OpenSearchDomain.elasticsearchClusterConfig, - instanceCount: 2 - }; -} -``` - -### Example - Configure OpenSearch EBS volume size - -Override the amount of disk space allocated to each instance in the OpenSearch domain created by `@searchable` - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.opensearch.OpenSearchDomain.ebsOptions = { - ...resources.opensearch.OpenSearchDomain.ebsOptions, - volumeSize: 10 - }; -} -``` - -## Customize Amplify-generated resources for @predictions directive - -Apply all the overrides in the `override(...)` function. For example, to add a Path to IAM role that facilitates text translation: - -```ts -import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiGraphQlResourceStackTemplate) { - resources.predictions.TranslateDataSourceServiceRole.path = - '/my/organization/'; -} -``` - -You can override the following @predictions directive resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [RekognitionDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync HTTP data source to connect to Amazon Rekognition service | -| [RekognitionDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Amazon Rekognition | -| [TranslateDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync HTTP data source to connect to Amazon Translate service | -| [translateTextAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to connect to Amazon Translate | -| [LambdaDataSource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-datasource.html) | AppSync Lambda data source to connect to Amazon Polly | -| [LambdaDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Lambda function calling Amazon Polly | -| [LambdaDataSourceServiceRoleDefaultPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for AppSync to connect to Lambda function calling Amazon Polly | -| [TranslateDataSourceServiceRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | AppSync service role to connect to Amazon Translate | -| [predictionsLambdaIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role for Lambda function calling Amazon Polly | -| [predictionsLambdaFunction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | Lambda function calling Amazon Polly | -| [PredictionsLambdaAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy for Lambda function to access Amazon Polly | -| [predictionsIAMRole](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) | IAM role to access s3 bucket used by @predictions | -| [PredictionsStorageAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to access S3 bucket used by @predictions | -| [identifyTextAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to enable Identify Text | -| [identifyLabelsAccess](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | IAM policy to enable Identify Text | - -## Place AppSync Resolvers in Custom-named Stacks - -If you have a particularly large GraphQL schema, you may run into issues with too many resources defined in a stack. The most common case where this happens is in the ConnectionStack which contains the resolvers for all of the relational directives in the schema. - -Creating a stack mapping does not create an additional root stack for the Amplify environment. All mapped stacks will still be placed under the existing Amplify environment root stack. To map a resolver to a different stack, update `/amplify/api//transform.conf.json` with a "StackMapping" block. The StackMapping defines a map from resolver logical ID to stack name. - -```json -{ - "Version": 5, - "ElasticsearchWarning": true, - "StackMapping": { - "": "Custom stack name" - } -} -``` - -The easiest way to determine a resolver logical ID is to run `amplify api gql-compile` and note the resolver logical ID in the list of Resources in the generated CloudFormation stack. Resolvers for model operations will be of the form `Resolver`. Resolvers for relational directives are of the form `Resolver`. - -### Example - -Given the following schema: - -```graphql -type Blog @model { - id: ID! - name: String! - posts: [Post] @hasMany -} - -type Post @model { - id: ID! - title: String! - content: String - blog: Blog @belongsTo -} -``` - -To map the CreatePostResolver and the relational resolvers to a stack named 'MyCustomStack', add the following in `transform.conf.json`: - -```json -"StackMapping": { - "CreatePostResolver": "MyCustomStack", - "BlogpostsResolver": "MyCustomStack", - "PostblogResolver": "MyCustomStack", -} -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx deleted file mode 100644 index f90599e51bf..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/schema-evolution/index.mdx +++ /dev/null @@ -1,145 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Evolving GraphQL schemas', - description: 'Evolve your GraphQL schema over time using the @mapsTo directive to retain tables while renaming models', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/schema-evolution/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - -GraphQL schemas change over the lifecycle of a project. Sometimes these changes include breaking API changes. One such change is renaming a model in the schema, which Amplify offers a way to do while retaining the underlying records for that model. - -## Renaming models while retaining data - -Amplify supports renaming models in a GraphQL schema by using the `@mapsTo` directive. -Normally when renaming a model, Amplify will remove the underlying table for the model and create a new table with the new name. Once a table contains production data that cannot be deleted, `@mapsTo` can be used to specify the original name. Amplify will use the original name to ensure the underlying DynamoDB tables and other resources point to the existing data. -Other GraphQL API references to the model will use the new name. - -For example, a schema such as: -```graphql -type Todo @model { - id: ID! - title: String! -} -``` -becomes: -```graphql -type Task @model @mapsTo(name: "Todo") { - id: ID! - title: String! -} -``` -Amplify will update all of the GraphQL operations and types to use the name Task, but the Task model will point to the table that Todo was originally using. - - - -- `@mapsTo` cannot be used to point a model to an arbitrarily named table. It can only be used to point a renamed model to it's original name. -- `@mapsTo` can only be used on @model GraphQL types that are backed by a DynamoDB table. - - - -When renaming a model that has relationships with other models, Amplify will automatically map auto-generated foreign key fields to their original name. For example, given: - -```graphql -type Post @model { - id: ID! - title: String! - comments: [Comment] @hasMany -} - -type Comment @model { - id: ID! - message: String! - # postCommentsId: String is an autogenerated field containing the foreign key -} -``` -Amplify will automatically add a field named `postCommentsId` to the Comment model that contains the foreign key of the Post. If the Post type is renamed to Article: - -```graphql -type Article @model @mapsTo(name: "Post") { - id: ID! - title: String! - comments: [Comment] @hasMany -} - -type Comment @model { - id: ID! - message: String! - # articleCommentsId: String is the new autogenerated field containing the foreign key -} -``` -The underlying table still contains records with `postCommentsId` as the foreign key field in the Comment table. In the new schema the foreign key field is now `articleCommentsId`. -Amplify is aware of this and will automatically map incoming requests with `articleCommentsId` to `postCommentsId` and do the reverse mapping for results. - -## Limitations - -### Constraint on relationship field names with @mapsTo - -In the above example if you renamed Comment to Reaction: -```graphql -type Post @model { - id: ID! - title: String! - comments: [Reaction] @hasMany # this field cannot be renamed and still access existing relationship data -} - -type Reaction @model @mapsTo(name: "Comment") { - id: ID! - message: String! - # autogenerated field postCommentsId: String contains the foreign key -} -``` -The `@hasMany` field `comments` cannot be renamed to `reactions`. This is because the foreign key field in Reaction uses the parent field name as part of the name. Amplify cannot determine the original name if this is changed. - -If a model is renamed multiple times, the value specified in `@mapsTo` must be the _original_ name, not the previous name. - -### Constraints to prevent naming conflicts - -A model in the schema cannot have the same name as the name another type maps to. For example, the following schema is invalid: -```graphql -type Article @model @mapsTo(name: "Post") { - id: ID! -} - -type Post @model { - id: ID! -} -``` -This schema would create a conflict on the Post table. - -Furthermore, even if the Post model is mapped to a different name, it is still not allowed. While this scenario technically does not pose a conflict, it is disallowed to prevent confusion. - -If you are accessing the table of a renamed model directly (ie. without going through AppSync), your access patterns will need to be aware that foreign key fields of records in the database are not renamed. See "How it works" below. - -## How it works -`@mapsTo` does not modify any existing tables or records. Instead, it points AppSync resolvers for the new name to the existing DynamoDB table for the original name. - -To handle renamed autogenerated foreign key fields when using relational directives, Amplify adds additional AppSync pipeline resolvers before and after fetching data from the database. -The resolvers before the fetch map any occurrence of the renamed foreign keys in the request to the original name. Then the resolvers after the fetch map any occurrence of the original name to the current name before returning the result. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx deleted file mode 100644 index 29727d4c43f..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/search-and-result-aggregations/index.mdx +++ /dev/null @@ -1,483 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Build search and aggregate queries', - description: 'Add authorization rules to your GraphQL schema to control access to your data.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/search-and-result-aggregations/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - - - -Add the `@searchable` directive to an `@model` type to enable OpenSearch-based data search and result aggregations. This gives you the ability to: - -- search for data using advanced filters, such as substring matching, wildcards, regex, `and`/`or`/`not` conditions -- get aggregation values, such as sum, average, min, max, terms -- retrieve total search result count -- sort the search results across one or multiple fields - -```graphql -type Student @model @searchable { - name: String - dateOfBirth: AWSDate - email: AWSEmail - examsCompleted: Int -} -``` - -> Once the `@searchable` directive is added, all new records added to the model are streamed to OpenSearch. To backfill existing data, see [Backfill OpenSearch index from DynamoDB table](/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/#backfill-opensearch-index-from-dynamodb-table). - -## Search and filter data - -Every model with a `@searchable` directive attached generates a new "search" GraphQL query to search and filter for records. The example above provides you the ability to search for "Student" records using a "searchStudents" query. - -The `filter` parameter allows you to filter for records based on their field values. - -```graphql -query SearchStudentsByEmail { - searchStudents(filter: { name: { eq: "Rene Brandel" } }) { - items { - id - name - email - } - } -} -``` - -In the example above, the search result consists of students with the name "Rene Brandel" - -### Supported search operations - -| Field type | Supported search operations | -| --- | --- | -| String | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp | -| Int | ne, gt, lt, gte, lte, eq, range | -| Float | ne, gt, lt, gte, lte, eq, range | -| Boolean | eq, ne | -| Enum | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp | - -### Nested search conditions (and, or, not) - -Use the filter parameter to pass a nested `and`/`or`/`not` condition. - -```graphql -query MyQuery { - searchStudents( - filter: { - name: { wildcard: "*Brandel" } - or: [{ dateOfBirth: { lt: "2000-01-01" } }, { email: { exists: true } }] - } - ) { - items { - id - name - email - dateOfBirth - } - } -} -``` - -By default, every operation in the filter properties is `and`ed. Use the `or` or `not` properties in the search query's `filter` parameter to override this behavior. - -The query above returns a "Student" if: - -- their name ends with "Brandel" -- `and` - - their date of birth is earlier than 2000-01-01 - - `or` - - their email exists. - -## Sort search results - -Use the `sort` parameter to sort your search results by a field in ascending or descending order. The `field` argument accepts any field available on the model. The `direction` accepts either `asc` or `desc`. - -```graphql -query SearchAndSort { - searchStudents( - filter: { name: { wildcard: "*Brandel" } } - sort: { direction: desc, field: name } - ) { - items { - name - id - } - } -} -``` - -In the example above, the search result is sorted based on their `name` in a `desc`ending order. - -### Sort search result over multiple fields - -To sort over multiple fields, provide array of sort conditions. When sorting over multiple fields, the sort conditions are applied in the `sort` array's order. - -```graphql -query SearchAndSort { - searchStudents( - filter: { name: { wildcard: "*Brandel" } } - sort: [ - { field: name, direction: desc } # Sort condition #1 - { field: dateOfBirth, direction: asc } # Sort condition #2 - ] - ) { - items { - id - name - dateOfBirth - } - } -} -``` - -In the example above, the search result is first sorted by `name` in a `desc`ending order and then by `dateOfBirth` in an `asc`ending order. - -## Paginate over search results - -By default, the search result page size is 100. To customize the page size modify the `limit` parameter. Query for the `nextToken` and use it in your subsequent pagination requests: - -``` -query MyQuery { - searchTodos(nextToken: "") { # Pass in your nextToken in query - items { - description - id - name - createdAt - } - nextToken # Next token to paginate on - } -} -``` - -## Total count of search results - -Add the `total` field in your query response to get the total count of search result hits. - -```graphql -query MyQuery { - searchStudents(filter: { name: { wildcard: "*Brandel" } }) { - items { - id - } - total # Specify to get total counts - } -} -``` - -In the example above, the response's `total` field contains the total search result count for "Students" whose name ends with "Brandel". Note: `total` is calculated based on all records, irrespective of pagination configurations. - -## Aggregate values for search result (minimum, maximum, average, sum, terms) - -Use the `aggregates` parameter to get aggregate values such as "minimum", "maximum", "average", and "sum" returned in the `aggregateItems` field. Note: `aggregates` are calculated based on all records, irrespective of pagination configurations. - - - - -Provide the `min` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchStudents( - aggregates: { - type: min # Specifies that you want the "min" value - field: examsCompleted # Specifies the field for the aggregate value - name: "minimumExams" # provides a name to reference in the response field - } - filter: { name: { wildcard: "Rene*" } } - ) { - aggregateItems { - name - result { - ... on SearchableAggregateScalarResult { - value - } - } - } - } -} -``` - -In the example above, the response includes the minimum value of "examsCompleted" for all Students whose name starts with "Rene". - -```graphql -{ - "data": { - "searchStudents": { - "aggregateItems": [{ - "name": "minimumExams", - "result": { - "value": 7 - } - }] - } - } -} -``` - - - - - -Provide the `max` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchStudents( - aggregates: { - type: max # Specifies that you want the "max" value - field: examsCompleted # Specifies the field for the aggregate value - name: "maximumExams" # provides a name to reference in the response field - } - filter: { name: { wildcard: "Rene*" } } - ) { - aggregateItems { - name - result { - ... on SearchableAggregateScalarResult { - value - } - } - } - } -} -``` - -In the example above, the response includes the maximum value of "examsCompleted" for all Students whose name starts with "Rene". - -```graphql -{ - "data": { - "searchStudents": { - "aggregateItems": [{ - "name": "maximumExams", - "result": { - "value": 28 - } - }] - } - } -} -``` - - - - - -Provide the `avg` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchStudents( - aggregates: { - type: avg # Specifies that you want the "avg" value - field: examsCompleted # Specifies the field for the aggregate value - name: "averageExams" # provides a name to reference in the response field - } - filter: { name: { wildcard: "Rene*" } } - ) { - aggregateItems { - name - result { - ... on SearchableAggregateScalarResult { - value - } - } - } - } -} -``` - -In the example above, the response includes the average value of "examsCompleted" for all Students whose name starts with "Rene". - -```graphql -{ - "data": { - "searchStudents": { - "aggregateItems": [{ - "name": "averageExams", - "result": { - "value": 17.3 - } - }] - } - } -} -``` - - - - - -Provide the `sum` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchStudents( - aggregates: { - type: sum # Specifies that you want the "sum" value - field: examsCompleted # Specifies the field for the aggregate value - name: "examsSum" # provides a name to reference in the response field - } - filter: { name: { wildcard: "Rene*" } } - ) { - aggregateItems { - name - result { - ... on SearchableAggregateScalarResult { - value - } - } - } - } -} -``` - -In the example above, the response includes the sum of all "examsCompleted" values for all Students whose name starts with "Rene". - -```graphql -{ - "data": { - "searchStudents": { - "aggregateItems": [{ - "name": "examsSum", - "result": { - "value": 392 - } - }] - } - } -} -``` - - - - -Provide the `terms` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. - -```graphql -query MyQuery { - searchTodos( - aggregates: { field: description, type: terms, name: "descriptionTerms" } - ) { - aggregateItems { - result { - ... on SearchableAggregateBucketResult { - __typename - buckets { - doc_count - key - } - } - } - name - } - } -} -``` - -In the example above, the response includes the terms for the description and their count: - -```graphql -{ - "data": { - "searchTodos": { - "aggregateItems": [ - { - "result": { - "__typename": "SearchableAggregateBucketResult", - "buckets": [{ - "doc_count": 2, - "key": "Shopping list" - }, { - "doc_count": 1, - "key": "Me next todo" - }] - }, - "name": "descriptionTerms" - } - ] - } - } -} -``` - - - - -## Set up OpenSearch for production environments - -By default, Amplify CLI will configure a t2.small instance type. This is great for getting started and prototyping but not recommended to be used in the production environment per the [OpenSearch best practices](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp.html). - -To configure the OpenSearch instance type per environment: - -1. Run `amplify env add` to create a new environment (e.g. "prod") -2. Edit the `amplify/team-provider-info.json` file and set `OpenSearchInstanceType` to the instance type that works for your application - -```json -{ - "dev": { - "categories": { - "api": { - "": { - "OpenSearchInstanceType": "t2.small.elasticsearch" - } - } - } - }, - "prod": { - "categories": { - "api": { - "": { - "OpenSearchInstanceType": "t2.medium.elasticsearch" - } - } - } - } -} -``` - -3. Deploy your changes with `amplify push` - -Learn more about Amazon OpenSearch Service instance types [here](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-instance-types.html). - -## How it works - -The `@searchable` directive streams the data of an @model type to Amazon OpenSearch Service and configures search resolvers to query against OpenSearch. - -Type definition of the `@searchable` directive: - -```graphql -# Streams data from DynamoDB to OpenSearch and exposes search capabilities. -directive @searchable(queries: SearchableQueryMap) on OBJECT -input SearchableQueryMap { - search: String -} -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx deleted file mode 100644 index 84d29845a33..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/troubleshooting/index.mdx +++ /dev/null @@ -1,96 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Troubleshooting', - description: 'Add authorization rules to your GraphQL schema to control access to your data.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/graphqlapi/troubleshooting/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -## Deploying multiple index changes at once - -You can make `@index` updates on one "amplify push". Under the hood, Amplify CLI needs to locally sequence multiple individual deployments to your DynamoDB table because each Global Secondary Index (GSI), managed by `@index`, change requires time to create the new index. - -If your deployment fails locally when updating multiple GSIs, you'll have the ability to run: - -- `amplify push --iterative-rollback` to rollback the last-known-good state -- `amplify push --force` to rollback the last-known-good state and try redeploying your changes again using. - -```console -Attempting to mutate more than 1 global secondary index at the same time. -``` - -If you're running into the error above during `amplify push`, it is likely that you don't have this feature enabled. To enable multiple GSI updates, set the "enableIterativeGsiUpdates" feature flag to true in your `amplify/cli.json` file. - -## Backfill OpenSearch index from DynamoDB table - -When you add `@searchable` to a `@model` type with existing data, then you need to backfill the OpenSearch index. Download the following Python script to help you backfill your OpenSearch index: - -[DynamoDB to OpenSearch backfill script](https://raw.githubusercontent.com/aws-amplify/amplify-category-api/main/packages/graphql-elasticsearch-transformer/scripts/ddb_to_es.py) - -The script creates an event stream of your DynamoDB records and sends them to your OpenSearch Index. Execute the script with the following parameters to initiate the backfill: - -| Parameter | Description | Required | -| --- | --- | --- | -| `--rn` | DynamoDB table region. See [AWS Regions](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) for available options | Yes | -| `--tn` | DynamoDB table name. Format: `{@model type name}-{AppSync API ID}-{Amplify environment}` | Yes | -| `--lf` | ARN of the "DynamoDB to OpenSearch streaming" Lambda function. Format: `arn:aws:lambda:{region}:{AWS Account ID}:function:amplify-{Amplify project name}-{Amplify environment}-{Random string}-OpenSearchStreamingLambd-{Random string}` | Yes | -| `--esarn` | ARN of the DynamoDB table stream. Format: `arn:aws:dynamodb:{region}:{AWS Account ID}:table/{@model type name}-{AppSync API ID}-{Amplify environment}/stream/{Table creation date}` | Yes | -| `--ak` | AWS Access Key ID. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | -| `--sk` | AWS Secret Access Key. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | -| `--st` | AWS Session Token. This is used to authenticate with your AWS account in case no local AWS profile is set up. | No | - -In the example below, the `Post` table data in `us-west-2` gets backfilled in the OpenSearch index. - -```bash -python3 ddb_to_es.py \ - --rn 'us-west-2' \ # Use the region in which your table and OpenSearch domain reside - --tn 'Post-XXXX-dev' \ # Table name - --lf 'arn:aws:lambda:us-west-2:<...>:function:amplify-<...>-OpenSearchStreamingLambd-<...>' \ # Lambda function ARN, find the DynamoDB to OpenSearch streaming functions, copy entire ARN - --esarn 'arn:aws:dynamodb:us-west-2:<...>:table/Post-<...>/stream/2019-20-03T00:00:00.350' # Event source ARN, copy the full DynamoDB table ARN -``` - -## Index with multiple sort key fields - -When you add an `@index` directive with 2 or more sort key fields, you will need to backfill the new composite sort key for existing data. With `@index(sortKeyFields: ["status", "date"])`, you will need to backfill the `status#date` field with composite key values made up of each object's `status` and `date` fields joined by a `#`. You do not need to backfill data for `@index` directives with zero to one sort key field(s). - -## Maximum Global Secondary Index limit for DynamoDB table exceeded - -If you have increased the [soft limit of GSI per table](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html) in DynamoDB beyond 20, then iterative deployments will fail with the following warning. - -```console -DynamoDB
can have max of 20 GSIs. -To disable this check, use the --disable-gsi-limit-check option. -``` - -In order to disable this behavior, update your deploy scripts or ci commands to include the `--disable-gsi-limit-check` option to circumvent this validation during pushes. - -```bash -amplify push --disable-gsi-limit-check -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx deleted file mode 100644 index 808d3d59af5..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/configure-rest-api/index.mdx +++ /dev/null @@ -1,290 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure REST API', - description: "Use Amplify CLI's simple guided workflow to add REST APIs to cloud-based web and mobile apps.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/restapi/configure-rest-api/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -The Amplify CLI provides a guided workflow to easily add, develop, test and manage REST APIs to access your AWS resources from your web and mobile applications. - -A REST API or HTTP endpoint will be composed by one or more paths. Eg: `/items`. Each path will use a Lambda function to handle HTTP requests and responses. Amplify CLI creates a single resource in Amazon API Gateway so you can handle all routes, HTTP Methods and paths, with a single Lambda function via a Lambda Proxy integration. HTTP proxy integrations forward all requests and responses directly through to your HTTP endpoint. - -Amplify CLI let's you choose either an existing Lambda function or create a new one. To kickstart your implementation, you can choose between the following templates: - -- Serverless ExpressJS function -- CRUD function for DynamoDB - -> Lambda templates use [serverless-express](https://github.com/awslabs/aws-serverless-express) and provide the building blocks to start your REST API development. - -> See the list of all [supported Lambda runtimes](/[platform]/build-a-backend/functions/set-up-function/). - -Amplify CLI allows you to restrict REST API access to - -- Only authenticated users; or -- Authenticated and Guest users -- User Pool Groups - -See a description of these user types below - -| User type | Description | -| --- | --- | -| Authenticated user | User needs to sign in to use the REST API | -| Guest user | User doesn't need to sign in to use the REST API | -| User Pool Group | User needs to sign in and belong to the User Pool Group to use the REST API | - -For each user type you can further specify what actions it has access to. - -| User type | Actions | Http Method | Authentication Provider | -| --- | --- | --- | --- | -| Authenticated user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | -| Guest user | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | -| User Pool Group | create, read, update, delete | POST, GET, PUT, PATCH, DELETE | Amazon Cognito | - -REST APIs have support for [multiple environments](/gen1/[platform]/tools/cli/teams/) (e.g. dev, qa, and prod). This means that you can easily isolate different versions of your REST API by using different Amplify environments. - -Because Amplify environments could be in separate AWS accounts, you cannot use the environment feature of API Gateway. Each Amplify environment will have a separate API Gateway resource associated with it. For example: - -```console -https://.execute-api.eu-west-2.amazonaws.com/dev/items -https://.execute-api.eu-west-2.amazonaws.com/prod/items -``` - -## Create a REST API - -Navigate into the root of a JavaScript, iOS, or Android project and run: - -```bash -amplify init -``` - -Follow the wizard to create a new app. After finishing the wizard run: - -```bash -amplify add api -``` - -Select the following options: - -- Please select from one of the below mentioned services: **REST** -- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsApi** -- Provide a path (e.g., /book/\{isbn}): **/items** - -This will be the configuration for `/items` path in API Gateway: - -```console -/ - |_ /items Main resource. Eg: /items - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser - |_ /\{proxy+} Proxy resource. Eg: /items/, /items/id, items/object/\{id} - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser -``` - -By default Amplify CLI creates a greedy path variable `/items/\{proxy+}` that catches all child resources for a path and forwards them to your Lambda. This will match all child routes including `/items/id` and `/items/object/id`. - -- Choose a Lambda source **Create a new Lambda function** -- Provide a friendly name for your resource to be used as a label for this category in the project: **itemsLambda** -- Provide the AWS Lambda function name: **itemsLambda** -- Choose the runtime that you want to use: **NodeJS** -- Choose the function template that you want to use: **Serverless ExpressJS function** - -The Lambda function template **Serverless ExpressJS function** implements route handlers for `GET`, `POST`, `PUT` and `DELETE` Http Methods and paths for `/items` and `/items/*`. Some possible routes examples include: - -```console -GET /items List all items -GET /items/1 Load an item by id -POST /items Create an item -PUT /items Update an item -DELETE /items/1 Delete an item by id -``` - -- Do you want to access other resources in this project from your Lambda function? **No** -- Do you want to invoke this function on a recurring schedule? **No** -- Do you want to configure Lambda layers for this function? **No** -- Do you want to edit the local lambda function now? **Yes** - -> You are not going to change this template but it's good that you have it open as you follow the next steps. - -- Press enter to continue -- Restrict API access **Yes** -- Who should have access? **Authenticated and Guest users** -- What kind of access do you want for Authenticated users? **create, read, update, delete** -- What kind of access do you want for Guest users? **read** - -When configuration of your API is complete, the CLI displays a message confirming that you have configured local CLI metadata for this category. You can confirm this by running `amplify status`. Finally deploy your changes to the cloud: - -Amplify CLI restricts API access combining Amazon Cognito for authentication and AWS IAM (Identity and Access Management) for granting execution permissions on routes. - -- Do you want to add another path? **No** - -Deploy your new API. - -```bash -amplify push -``` - -At the end of this command you can take note of your new REST API url. - -```console -REST API endpoint: https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev -``` - -> REST APIs follow this pattern `https://{restapi-id}.execute-api.\{region}.amazonaws.com/\{environment}/\{path}`. - -Let's see an overview of all the resources created by Amplify CLI. - -```console -REST - |_ /items (path) - |_ itemsApi (Amazon API Gateway) - |_ itemsLambda (AWS Lambda) - |_ Logs (Amazon CloudWatch) -``` - -## Create REST API and restrict specific routes to specific User Pool Groups - -If your app uses User Pool Groups to manage different user types and would like to restrict access of specific routes to specific User Pool Groups. You can accomplish this by the following flow: - -- Create API route. -- Add API route handler function. -- Restrict-access to the API route to the User Pool Group. - -> The following example flow assumes the existence of two User Pool Groups : AdminUsers and GuestUsers for a Book store. The app would like to limit admin functionality like updating book records to the AdminUsers User Pool Group, while borrowing and returning books would be limited to the GuestUsers User Pool Group. -> -> - Path : /book/admin is restricted to AdminUsers and commands are handled by the bookAdminHandler lambda function -> - Path : /book/guest is restricted to GuestUsers and commands are handled by the bookGuestHandler lambda function - -```bash -amplify add api -$> ? Select from one of the below mentioned services: REST -$> ✔ Provide a friendly name for your resource to be used as a label for this category in the project: · mybookapi -$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/admin -$> ✔ Choose a Lambda source · Create a new Lambda function -$> ? Provide an AWS Lambda function name: bookAdminHandler -$> ? Choose the runtime that you want to use: NodeJS -$> ? Choose the function template that you want to use: Hello World -$> ? Do you want to configure advanced settings? No -$> ? Do you want to edit the local lambda function now? No -Successfully added resource bookAdminHandler locally. -$> ✔ Restrict API access? (Y/n) · yes -$> ✔ Restrict access by: · Individual Groups -$> ✔ Select groups: AdminUsers -$> ✔ What permissions do you want to grant to AdminUsers users? · create, read, update, delete -$> ✔ Do you want to add another path? (y/N) · yes -$> ✔ Provide a path (e.g., /book/\{isbn}): · /book/guest -$> ✔ Choose a Lambda source · Create a new Lambda function -$> ? Provide an AWS Lambda function name: bookGuestHandler -$> ? Choose the runtime that you want to use: NodeJS -$> ? Choose the function template that you want to use: Hello World -$> ? Do you want to configure advanced settings? No -$> ? Do you want to edit the local lambda function now? No -Successfully added resource bookGuestHandler locally. -$> ✔ Restrict API access? (Y/n) · yes -$> ✔ Restrict access by: Individual Groups -$> ✔ Select groups: GuestUsers -$> ✔ What permissions do you want to grant to GuestUsers users? create, read, update -$> ✔ Do you want to add another path? (y/N) No -✅ Successfully added resource mybookapi locally -``` - -At the end of this command you can verify the routes and their respective User Pool Group restrictions in the `cli-inputs.json` file at the following path. - -```bash - /amplify/backend/api//cli-inputs.json -``` - -## REST endpoint that triggers new Lambda functions - -During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths. - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source Create a new Lambda function -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda -? Provide the AWS Lambda function name: itemsLambda -? Choose the function template that you want to use: - CRUD function for Amazon DynamoDB -❯ Serverless ExpressJS function -``` - -## REST endpoint that triggers existing Lambda functions - -During the CLI setup, you'll be guided through to use your own Lambda functions which you've initialized as a part of your CLI project using the `amplify add function` command. This would allow you to have custom logic in your Lambda function and not use the predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) templates generated by the CLI as in the examples above. - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source - Create a new Lambda function -❯ Use a Lambda function already added in the current Amplify project -``` - -## Set up a REST API with Amazon DynamoDB - -During the CLI setup, you'll be guided through to create a new Lambda function with a predefined [serverless-express](https://github.com/awslabs/aws-serverless-express) template with routing enabled for your REST API paths with support for CRUD operations to DynamoDB tables (which you can create by following the CLI prompts or use the tables which you've already configured using the `amplify add storage` command). - -```bash -amplify add api -``` - -```console -? Please select from one of the below mentioned services REST -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsApi -? Provide a path (e.g., /book/\{isbn}) /items -? Choose a Lambda source Create a new Lambda function -? Provide a friendly name for your resource to be used as a label for this category in the project: itemsLambda -? Provide the AWS Lambda function name: itemsLambda -? Choose the function template that you want to use: -❯ CRUD function for Amazon DynamoDB - Serverless ExpressJS function -``` - -In the example above with `/items` path, the following API will be created for you: - -1. GET /items/[ID] will return a list containing the item at the [ID]. If the item does not exist then an empty array is returned. -2. GET /items/object/[ID] will return a single item at [ID]. If the item does not exist then an empty object is returned. -3. PUT /items with your item in the request body will create or update the item. -4. POST /items with your item in the request body will create or update the item. -5. DELETE /items/object/[ID] will delete the item. - -When you have a sort key, you can append it to the end of the path, for example: `GET /items/object/[ID]/[SORT_KEY_ID]` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx deleted file mode 100644 index 667062f433d..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/override-api-gateway/index.mdx +++ /dev/null @@ -1,325 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Override Amplify-generated API Gateway resources', - description: "The 'amplify override api' command generates a developer-configurable 'overrides' TypeScript file which provides Amplify-generated API Gateway resources as CDK constructs. For example, developers can configure a custom description or the minimum compression size of their REST API.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/restapi/override-api-gateway/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import ios_maintenance from '/src/fragments/lib-v1/ios-maintenance.mdx'; - - - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -```bash -amplify override api -``` - -Run the command above to override Amplify-generated Amazon API Gateway resources. - -The command creates a new `overrides.ts` file under `amplify/backend/api//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). - -Apply all the overrides in the `override(...)` function. For example: - -```ts -// This file is used to override the REST API resources configuration -import { AmplifyApiRestResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyApiRestResourceStackTemplate) { - resources.restApi.description = "Custom description"; - resources.restApi.minimumCompressionSize = 1024; -} -``` - -To change a field on a particular path, use `resources.restApi.body.paths[\]`: - -```ts -export function override(resources: AmplifyApiRestResourceStackTemplate) { - // Change the default CORS response header Access-Control-Allow-Origin from "'*'" to the API's domain - resources.restApi.body.paths['/items'].options['x-amazon-apigateway-integration'].responses.default.responseParameters['method.response.header.Access-Control-Allow-Origin'] = { 'Fn::Sub': "'https://${ApiId}.execute-api.${AWS::Region}.amazonaws.com'" }; -} -``` - -You can override the following REST API resources that Amplify generates: - -
- -|Amplify-generated resource|Description| -|-|-| -|[restApi](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html)|The Amazon API Gateway REST API created by `amplify add api`| -|[deploymentResource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-deployment.html)|The deployment resource that deploys the REST API above to a stage.| -|[policies](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html)|User pool group-related IAM policy. Example: `resources.policies["/items"].groups["Admin"]` - - -
- -## Authorize API requests with Cognito User Pools - -Amazon Cognito User Pools is a common service to use alongside API Gateway when -adding user Sign-Up and Sign-In to your application. If your application needs to -interact with other AWS services such as S3 on behalf of the user who invoked -an endpoint, you will need to use IAM credentials with Cognito Identity Pools. - -Amplify CLI does not support Cognito User Pool authorizers out-of-the-box. To -implement this functionality, you must override your REST API and add a Cognito -User Pool authorizer yourself by adding the following code into the -`override(...)` function, in order. - -First, assuming the Cognito User Pool you would like to use as an authorizer is -the Auth resource configured with your Amplify Project, create a parameter that resolves -to its User Pool ARN: - -```ts -// Replace the following with your Auth resource name -const authResourceName = ""; -const userPoolArnParameter = "AuthCognitoUserPoolArn"; - -// Add a parameter to your Cloud Formation Template for the User Pool's ID -resources.addCfnParameter({ - type: "String", - description: "The ARN of an existing Cognito User Pool to authorize requests", - default: "NONE", - }, - userPoolArnParameter, - { "Fn::GetAtt": [`auth${authResourceName}`, "Outputs.UserPoolArn"], } -); -``` - - - -Make sure to replace `` with the name of your auth resource. -This is the name of the folder in `amplify/backend/auth` that was created when -you added an Auth resource to your Amplify project. - - - -Now, define a REST API authorizer with Cognito User Pools using the OpenAPI extension, [`x-amazon-apigateway-authorizer`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html). This change will be applied by modifying the security definition of your REST API: - -```ts -// Create the authorizer using the AuthCognitoUserPoolArn parameter defined above -resources.restApi.addPropertyOverride("Body.securityDefinitions", { - Cognito: { - type: "apiKey", - name: "Authorization", - in: "header", - "x-amazon-apigateway-authtype": "cognito_user_pools", - "x-amazon-apigateway-authorizer": { - type: "cognito_user_pools", - providerARNs: [ - { - 'Fn::Join': ['', [{ Ref: userPoolArnParameter }]], - }, - ], - }, - }, -}); -``` - -Finally, update the security methods for all of the paths in your REST API to -use this new Cognito User Pool authorizer. You also add the `Authorization` header -as a parameter on incoming requests for these paths as a place for users to provide -their Cognito User ID Tokens. - -```ts -// For every path in your REST API -for (const path in resources.restApi.body.paths) { - // Add the Authorization header as a parameter to requests - resources.restApi.addPropertyOverride( - `Body.paths.${path}.x-amazon-apigateway-any-method.parameters`, - [ - ...resources.restApi.body.paths[path]["x-amazon-apigateway-any-method"] - .parameters, - { - name: "Authorization", - in: "header", - required: false, - type: "string", - }, - ] - ); - // Use your new Cognito User Pool authorizer for security - resources.restApi.addPropertyOverride( - `Body.paths.${path}.x-amazon-apigateway-any-method.security`, - [ { Cognito: [], }, ] - ); -} -``` - - - -Note that you can add more advanced logic to only use the Cognito User Pool authorizer -with some paths or methods. - - - -When performing requests to your REST API, make sure to add the `Authorization` -header with an ID Token provided by Cognito. - -Requests to endpoints are now populated with information from Cognito about the -user who is invoking the -endpoint, and you can reuse the verified ID Token in your endpoint resolvers to assume -the identity of the user for accessing other services like AWS AppSync or S3. - -## Authorize API requests with Lambda authorizer - -While Amplify CLI does not support Lambda authorizers natively out-of-box, you can implement this functionality by overriding your REST API resources. The following steps will walk you through how to create token-based Lambda authorizer. - -First, you need to have a Lambda authorizer function with required [authorization logic](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html#api-gateway-lambda-authorizer-lambda-function-create) in your Amplify project to use it as an authorizer. Refer to the steps to [set up a function](/[platform]/build-a-backend/functions/set-up-function/) using `amplify add function` - -After running `amplify override api`, add the following code to `override(...)` function. -Initially, create a parameter that resolves to Lambda Function ARN - -```ts -// Replace the following with your Function resource name -const functionResourcename = ""; -const functionArnParameter = "FunctionArn"; - -// Adding parameter to your Cloud Formation Template for Authorizer function arn -resources.addCfnParameter( - { - type: "String", - description: "The ARN of an existing Lambda Function to authorize requests", - default: "NONE", - }, - functionArnParameter, - { "Fn::GetAtt": [`function${functionResourcename}`, "Outputs.Arn"], } -); -``` - - - - -Make sure to replace `` with the name of your function resource. This is the name of the folder in `amplify/backend/function` that was created when you added an function resource to your Amplify project. - - - -Next, define the Lambda authorizer using the OpenAPI extension, [`x-amazon-apigateway-authorizer`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html). This change will be applied by modifying the security definition of your REST API: - -```ts -// Create the authorizer using the functionArnParameter parameter defined above -resources.restApi.addPropertyOverride("Body.securityDefinitions", { - Lambda: { - type: "apiKey", - name: "Authorization", - in: "header", - "x-amazon-apigateway-authtype": "oauth2", - "x-amazon-apigateway-authorizer": { - type: "token", - authorizerUri: - { - 'Fn::Join': [ - '', - [ - "arn:aws:apigateway:", - { Ref: 'AWS::Region' }, - ":lambda:path/2015-03-31/functions/", - { Ref: functionArnParameter }, - "/invocations" - ] - ], - }, - authorizerResultTtlInSeconds: 0 - }, - }, -}); -``` - -As API Gateway needs permission to invoke the Authorizer lambda function, add resource based policy to the function using following code: - -```ts -// Adding Resource Based policy to Lambda authorizer function -resources.addCfnResource( - { - type: "AWS::Lambda::Permission", - properties: { - Action: "lambda:InvokeFunction", - FunctionName: {Ref: functionArnParameter}, - Principal: "apigateway.amazonaws.com", - SourceArn:{ - "Fn::Join": [ - "", - [ - "arn:aws:execute-api:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":", - { - "Ref": "" - }, - "/*/*" - ] - ] - } - } - }, - "LambdaAuthorizerResourceBasedPolicy" -); - -``` - - -Make sure to replace `` with the name of your Rest API resource. This is the name of the folder in `amplify/backend/api` that was created when you added an Rest API resource to your Amplify project. - - - -Finally, update the security methods for all of the paths in your REST API to use this new Lambda authorizer. You can also add the Authorization header as a parameter on incoming requests for these paths as a place for users to provide their Auth token. - -```ts -for (const path in resources.restApi.body.paths) { - // Add the Authorization header as a parameter to requests - resources.restApi.addPropertyOverride( - `Body.paths.${path}.x-amazon-apigateway-any-method.parameters`, - [ - ...resources.restApi.body.paths[path]["x-amazon-apigateway-any-method"] - .parameters, - { - name: "Authorization", - in: "header", - required: false, - type: "string", - }, - ] - ); - // Use your new Lambda authorizer for security - resources.restApi.addPropertyOverride( - `Body.paths.${path}.x-amazon-apigateway-any-method.security`, - [ { Lambda: [], }, ] - ); -} -``` - -Note that you can add more advanced logic to only use the Lambda authorizer with some paths or methods. - - -When performing requests to your REST API, make sure to add the Authorization header with an token required by Lambda authorizer function. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx deleted file mode 100644 index 3be70d14016..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/restapi/test-api/index.mdx +++ /dev/null @@ -1,185 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Test the REST API', - description: 'Learn how you can test the REST API from the terminal, with Amplify Mock, or with the API Gateway console.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/restapi/test-api/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import ios_maintenance from '/src/fragments/lib-v1/ios-maintenance.mdx'; - - - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -## Test the API from the terminal - -If Guest users have access to your REST API you can test it from the terminal using Curl. - -[Curl](https://github.com/curl/curl) is a command-line tool that lets you transfer data to and from a server using various protocols. - -> Curl is available in many distributions including Mac, Windows and Linux. Follow the install instructions in the [docs](https://curl.haxx.se/docs/install.html). - - - - -### GET method example - -```bash -curl https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos -``` - -### POST method example - -```bash -curl -H "Content-Type: application/json" -d '{"name":"todo-1"}' https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos -``` - - - - -### GET method example - -```bash -curl https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos -``` - -### POST method example - -```bash -curl -H "Content-Type: application/json" -d {\"name\":\"todo-1\"} https://a5b4c3d2e1.execute-api.eu-west-2.amazonaws.com/dev/todos -``` - - - - -> Important! Testing methods using production endpoints may result in changes to resources that cannot be undone. - -## Test the API with Amplify Mock - -Amplify CLI allows you to quickly test your REST APIs by using the `amplify mock function` command. - -Let's test your new REST API using the route below with HTTP Method `GET` and path `/todos?limit=10` which includes a `limit` query string parameter. - -```console -GET /todos?limit=10 -``` - -> Important! Testing methods using production endpoints may result in changes to resources that cannot be undone. - -Before you continue, edit the file at `{project}/amplify/backend/function/todosLambda/src/event.json` and replace its content for the purpose of the test. - -```json -{ - "httpMethod": "GET", - "path": "/todos", - "queryStringParameters": { - "limit": "10" - } -} -``` - -Make sure you have saved the changes and run - -```bash -amplify mock function todosLambda -``` - -Select the following options: - -- Provide the path to the event JSON object relative to `{project}/amplify/backend/function/todosLambda` __src/event.json__ - -```console -Starting execution... -EVENT: {"httpMethod":"GET","path":"/todos","queryStringParameters":{"limit":"10"}} -App started - -Result: -{"statusCode":200,"body":"{\"success\":\"get call succeed!\",\"url\":\"/todos?limit=10\"}","headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","content-type":"application/json; charset=utf-8","content-length":"55", "date":"Tue, 18 Aug 2020 16:50:53 GMT","connection":"close"},"isBase64Encoded":false} -Finished execution. -``` - -## Test the API with API Gateway console - -Let's test your new REST API using the route below with HTTP Method `GET` and path `/todos?limit=10` which includes a `limit` query string parameter. - -```console -GET /todos?limit=10 -``` - -> Important! Testing methods with the API Gateway console may result in changes to resources that cannot be undone. - -- Sign in to the API Gateway console at [https://console.aws.amazon.com/apigateway](https://console.aws.amazon.com/apigateway). -- Choose the `todosApi` REST API. -- In the Resources pane, choose the method you want to test. Pick `ANY` right under `/todos`. - -```console -/ - |_ /todos Main resource. Eg: /todos - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser - |_ /{proxy+} Proxy resource. Eg: /todos/, /todos/id, todos/object/{id} - ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT - OPTIONS Allow pre-flight requests in CORS by browser -``` - -- In the Method Execution pane, in the Client box, choose TEST. Choose the `GET` method. Add `limit=10` to the Query String `{todos}` field. - -- Choose Test to run the test for `GET /todos?limit=10`. The following information will be displayed: request, status, latency, response body, response headers and logs. - -```bash -Request: /todos?limit=10 -Status: 200 -Latency: 139 ms -Response Body -{ - "success": "get call succeed!", - "url": "/todos?limit=10" -} -Response Headers -{"access-control-allow-origin":"*","date":"Tue, 18 Aug 2020 17:36:14 GMT","content-length":"55","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","x-powered-by":"Express","content-type":"application/json; charset=utf-8","connection":"close"} -Logs -Execution log for request 4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f -Tue Aug 18 17:36:14 UTC 2020 : Starting execution for request: 4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f -Tue Aug 18 17:36:14 UTC 2020 : HTTP Method: GET, Resource Path: /todos -Tue Aug 18 17:36:14 UTC 2020 : Method request path: {} -Tue Aug 18 17:36:14 UTC 2020 : Method request query string: {limit=10} -Tue Aug 18 17:36:14 UTC 2020 : Method request headers: {} -Tue Aug 18 17:36:14 UTC 2020 : Method request body before transformations: -Tue Aug 18 17:36:14 UTC 2020 : Endpoint request URI: https://lambda.eu-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-west-2:664371068953:function:expressLambda-dev/invocations -Tue Aug 18 17:36:14 UTC 2020 : Endpoint request headers: { X-Amz-Date=20200818T173614Z, X-Amz-Source-Arn=arn:aws:execute-api:eu-west-2:664371068953:s3zmw6fqy5/test-invoke-stage/GET/todos, Accept=application/json, User-Agent=AmazonAPIGateway_s3zmw6fqy5, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDEaCWV1LXdlc3QtMiJGMEQCIC3KIeR66WhaCBw+eJ+GPhF7y4hz9xC2nN+ARb7T3psyAiBdsoaD9yMfiw2dHWjQM5x7vM11XmToNSGu64mckUQdzSq0AwgaEAEaDDU0NDM4ODgxNjY2MyIMIzObNbCd6QtYwb0IKpEDpHXEzkM2OYq7JfL0U/WbF09KNamodfnifRYwZd/GNOwykykc/zHiU9X0XZPRd+QTnQe/9eoy8DaxBkDgRzQQjTThQWJWadtcfjryTLRKpVeo1UueL+f6DTUDf+URjb0P9CN1gPm+ntZD3LSyAXGwACKG7YMA5/HyeEk [TRUNCATED] -Tue Aug 18 17:36:14 UTC 2020 : Endpoint request body after transformations: {"resource":"/todos","path":"/todos","httpMethod":"GET","headers":null,"multiValueHeaders":null,"queryStringParameters":{"limit":"10"},"multiValueQueryStringParameters":{"limit":["10"]},"pathParameters":null,"stageVariables":null,"requestContext":{"resourcePath":"/todos","httpMethod":"GET","requestTime":"18/Aug/2020:17:36:14 +0000","path":"/todos","accountId":"EXAMPLE_ID","protocol":"HTTP/1.1","stage":"test-invoke-stage","domainPrefix":"testPrefix","requestTimeEpoch":1597772174890,"requestId":"4fc3c0c7-6f9f-4ac3-84d7-205500f39b5f","identity":{"cognitoIdentityPoolId":null,"cognitoIdentityId":null,"apiKey":"test-invoke-api-key","principalOrgId":null,"cognitoAuthenticationType":null,"userArn":"arn:aws:iam::664371068953:root","apiKeyId":"test-invoke-api-key-id","userAgent":"aws-internal/3 aws-sdk-java/1.11.820 Linux/4.9.217-0.1.ac.205.84.332.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.252-b09 java/1.8.0_252 v [TRUNCATED] -Tue Aug 18 17:36:14 UTC 2020 : Sending request to https://lambda.eu-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-west-2:664371068953:function:expressLambda-dev/invocations -Tue Aug 18 17:36:15 UTC 2020 : Received response. Status: 200, Integration latency: 137 ms -Tue Aug 18 17:36:15 UTC 2020 : Endpoint response headers: {Date=Tue, 18 Aug 2020 17:36:15 GMT, Content-Type=application/json, Content-Length=443, Connection=keep-alive, sampled=0} -Tue Aug 18 17:36:15 UTC 2020 : Endpoint response body before transformations: {"statusCode":200,"body":"{\"success\":\"get call succeed!\",\"url\":\"/todos?limit=10\"}","headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","content-type":"application/json; charset=utf-8","content-length":"55","date":"Tue, 18 Aug 2020 17:36:14 GMT","connection":"close"},"isBase64Encoded":false} -Tue Aug 18 17:36:15 UTC 2020 : Method response body after transformations: {"success":"get call succeed!","url":"/todos?limit=10"} -Tue Aug 18 17:36:15 UTC 2020 : Method response headers: {x-powered-by=Express, access-control-allow-origin=*, access-control-allow-headers=Origin, X-Requested-With, Content-Type, Accept, content-type=application/json; charset=utf-8, content-length=55, date=Tue, 18 Aug 2020 17:36:14 GMT, connection=close, Sampled=0} -Tue Aug 18 17:36:15 UTC 2020 : Successfully completed execution -Tue Aug 18 17:36:15 UTC 2020 : Method completed with status: 200 -``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx deleted file mode 100644 index e450da12a4b..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/configure-storage/index.mdx +++ /dev/null @@ -1,190 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure Storage', - description: 'Use Amplify CLI to create and manage cloud-connected file and data storage for your app.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/storage/configure-storage/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -Amplify CLI's `storage` category enables you to create and manage cloud-connected file & data storage. Use the `storage` category when you need to store: - -1. app content (images, audio, video etc.) in an public, protected or private storage bucket or -2. app data in a NoSQL database and access it with a REST API + Lambda - -## Setup a new storage resource - -You can setup a new storage resource by running the following command: - -```bash -amplify add storage -``` - -Amplify allows you to either setup a app content storage (images, audio, video etc.) backed by Amazon S3 or a NoSQL database backed by Amazon DynamoDB. - -### Adding S3 storage - -```console -? Please select from one of the below mentioned services: -> Content (Images, audio, video, etc.) - NoSQL Database -? Please provide a friendly name for your resource that will be used to label this category in the project: -> mystorage -? Please provide bucket name: -> mybucket -``` - -Follow the prompts to provide your content storage's resource name. - - - -The storage resource created by Amplify CLI has retention enabled which prevents accidental deletion or loss of data. Hence, running `amplify remove storage` will not delete the storage resource and will need to be manually deleted on the AWS console. - - - -### S3 Access permissions - -Next, configure the access permissions for your Amazon S3 bucket. If you haven't set up the `auth` category already, the Amplify CLI will guide you through a workflow to enable the auth category. - -```console -? Restrict access by? -> Auth/Guest Users - Individual Groups - Both - Learn more -``` - - - -**NOTE:** Run `amplify update storage` to change the access permissions for your Amazon S3 bucket - - - -#### Auth/Guest Users access - -Select `Auth/Guest Users`, to scope permissions based on an individual user's authentication status. On the next question you'll be able to select if only authenticated users can access resources, or authenticated and guest users: - -``` -? Who should have access: -❯ Auth users only - Auth and guest users -``` - -Then you'll be prompted to set the access scopes for your authenticated and (if selected prior) unauthenticated users. - -```console -? What kind of access do you want for Authenticated users? -> ◉ create/update - ◯ read - ◯ delete -? What kind of access do you want for Guest users? - ◯ create/update -> ◉ read - ◯ delete -``` - -Granting access to authenticated users will allow the specified CRUD operations on objects in the bucket starting with the prefix `/public/`, `/protected/{cognito:sub}/`, and `/private/{cognito:sub}/`. `{cognito:sub}` is the sub of the Cognito identity of the authenticated user. - -Granting access to guest users will allow the specified CRUD operations on objects in the bucket starting with the prefix `/public/`. - -#### Individual Group access - -Select `Individual Groups` to scope access permissions based on [Cognito User Groups](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-rules/) - -```console -? Select groups: - ◉ EMPLOYEE -> ◉ MANAGER -``` - -Then select the CRUD operations you want to permit for each selected Cognito user group - -```console -? What kind of access do you want for EMPLOYEE users? - ◯ create/update -> ◉ read - ◯ delete -? What kind of access do you want for MANAGER users? - ◉ create/update - ◯ read -> ◉ delete -``` - -> Note: CRUD operations selected here will apply to ALL objects in the bucket, not just objects under a particular prefix. - -> Note: If you combine `Auth/Guest user access` and `Individual Group access`, users who are members of a group will only be granted the permissions of the group, and not the authenticated user permissions. - -### S3 Lambda trigger - -Lastly, you have the option of configuring a Lambda function that can execute in response to S3 events. - -```console -? Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) -``` - -Learn more about this workflow [here](/gen1/[platform]/tools/cli/usage/lambda-triggers/#s3-lambda-triggers). - -That's it! Your content storage is set up! Head to the [library's storage docs](/[platform]/build-a-backend/storage/set-up-storage/) to integrate this newly created S3 bucket into your app. - -### Adding a NoSQL database - -```console -? Please select from one of the below mentioned services: -> Content (Images, audio, video, etc.) - NoSQL Database -? Please provide a friendly name for your resource that will be used to label this category in the project: -> dynamo2e1dc4eb -? Please provide table name: -> dynamo2e1dc4eb -``` - -Follow the prompts to provide your NoSQL Database's resource name. Next, you'll go through a table-creation wizard. First, you'll create the columns of your table: - -```console -You can now add columns to the table. - -? What would you like to name this column: id -? Please choose the data type: string -? Would you like to add another column? Yes -``` - -Then, you'll need to specify your indexes. The concept behind "indexes", "partition key", "sort key" and "global secondary indexes" are explained in-depth [here](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey). - -```console -? Please choose partition key for the table: id -? Do you want to add a sort key to your table? (y/N) -``` - -```console -? Do you want to add a Lambda Trigger for your Table? (y/N) -``` - -If you want to configure a Lambda trigger for your Table, you'll have the option. Learn more about this workflow [here](/gen1/[platform]/tools/cli/usage/lambda-triggers/#dynamodb-lambda-triggers). - -That's it! Your NoSQL Database is set up! diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx deleted file mode 100644 index acf062881db..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/import/index.mdx +++ /dev/null @@ -1,260 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Import an S3 bucket or DynamoDB table', - description: 'Learn how you can import existing S3 bucket or DynamoDB table resources as a storage resource for other Amplify categories (API, Function, and more) using the Amplify CLI.', - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/storage/import/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -Import an existing S3 bucket or DynamoDB tables into your Amplify project. Get started by running `amplify import storage` command to search for & import an S3 or DynamoDB resource from your account. - -```bash -amplify import storage -``` - -Make sure to run `amplify push` to complete the import process and deploy this backend change to the cloud. - -The `amplify import storage` command will: - -- automatically populate your Amplify Library configuration files (aws-exports.js, amplifyconfiguration.json) with your chosen S3 bucket information -- provide your designated S3 bucket or DynamoDB table as a storage mechanism for all storage-dependent categories (API, Function, Predictions, and more) -- enable Lambda functions to access the chosen S3 or DynamoDB resource if you permit it - -This feature is particularly useful if you're trying to: - -- enable Amplify categories (such as API and Function) to access your existing storage resources; -- incrementally adopt Amplify for your application stack; -- independently manage S3 and DynamoDB resources while working with Amplify. - -> Note: Amplify does not manage the lifecycle of an imported resource. - -## Import an existing S3 bucket - -Select the "S3 bucket - Content (Images, audio, video, etc.)" option when you've run `amplify import storage`. - -Run `amplify push` to complete the import procedure. - -> Amplify projects are limited to exactly one S3 bucket. - -### Connect to an imported S3 bucket with Amplify Libraries - -By default, Amplify Libraries assumes that S3 buckets are configured with the following access patterns: - -- `public/` - Accessible by all users of your app -- `protected/{user_identity_id}/` - Readable by all users, but writable only by the creating user -- `private/{user_identity_id}/` - Only accessible for the individual user - -You can either configure your IAM role to use the Amplify-recommended policies or in your Amplify libraries configuration [overwrite the default storage path behavior](/gen1/[platform]/prev/build-a-backend/storage/configure-access/#customize-object-key-path). - -It is highly recommended to review your S3 bucket's CORS settings. Review the [recommendation guide here](/[platform]/build-a-backend/storage/set-up-storage/#amazon-s3-bucket-cors-policy-setup). - -### Configuring IAM role to use Amplify-recommended policies - -If you're using an imported S3 bucket with an imported Cognito resource, then you'll need to update the policy of your Cognito Identity Pool's authenticated and unauthenticated role. Create new __managed policies__ (not *inline policies*) for these roles with the following statements: - -> Make sure to replace `{YOUR_S3_BUCKET_NAME}` with your S3 bucket's name. - -#### Unauthenticated role policies - -- IAM policy statement for `public/`: - -```json -{ - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/public/*" - ], - "Effect": "Allow" -} -``` - -- IAM policy statement for read access to `public/`, `protected/`, and `private/`: - -```json -{ - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/*" - ], - "Effect": "Allow" -}, -{ - "Condition": { - "StringLike": { - "s3:prefix": [ - "public/", - "public/*", - "protected/", - "protected/*" - ] - } - }, - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}" - ], - "Effect": "Allow" -} -``` - -#### Authenticated role policies - -- IAM policy statement for `public/`: - -```json -{ - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/public/*" - ], - "Effect": "Allow" -} -``` - -- IAM policy statement for `protected/`: - -```json -{ - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/${cognito-identity.amazonaws.com:sub}/*" - ], - "Effect": "Allow" -} -``` - -- IAM policy statement for `private/`: - -```json -{ - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/private/${cognito-identity.amazonaws.com:sub}/*" - ], - "Effect": "Allow" -} -``` - -- IAM policy statement for read access to `public/`, `protected/`, and `private/`: - -```json -{ - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}/protected/*" - ], - "Effect": "Allow" -}, -{ - "Condition": { - "StringLike": { - "s3:prefix": [ - "public/", - "public/*", - "protected/", - "protected/*", - "private/${cognito-identity.amazonaws.com:sub}/", - "private/${cognito-identity.amazonaws.com:sub}/*" - ] - } - }, - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::{YOUR_S3_BUCKET_NAME}" - ], - "Effect": "Allow" -} -``` - -## Import an existing DynamoDB table - -Select the "DynamoDB table - NoSQL Database" option when you've run `amplify import storage`. In order to successfully import your DynamoDB table, your DynamoDB table needs to be located within the same region as your Amplify project. - -Run `amplify push` to complete the import procedure. - -> Amplify projects can contain multiple DynamoDB tables. - -## Multi-environment support - -When you create a new environment through `amplify env add`, Amplify CLI will assume by default that you're managing your app's storage resources outside of an Amplify project. You'll be asked to either import a different S3 bucket or DynamoDB tables or maintain the same imported storage resource. - -If you want to have Amplify manage your storage resources in a new environment, run `amplify remove storage` to unlink the imported storage resources and `amplify add storage` to create new Amplify-managed S3 buckets and DynamoDB tables in the new environment. - -## Unlink an existing S3 bucket or DynamoDB table - -In order to unlink your existing Storage resource run `amplify remove storage`. This will only unlink the S3 bucket or DynamoDB table referenced from the Amplify project. It will not delete the S3 bucket or DynamoDB table itself. - -Run `amplify push` to complete the unlink procedure. - -## Configure environment variables for Amplify Hosting builds - -In order to successfully build your application with Amplify Hosting add the following environment variables to your build environment: - -|Environment Variable|Description|Imported Resource|Required -|-|-|-|-| -|AMPLIFY_STORAGE_BUCKET_NAME|The name of the S3 bucket being imported for storage|S3 bucket|Yes -|AMPLIFY_STORAGE_REGION|The AWS region in which the S3 bucket or the DynamoDB table is located (for example: us-east-1, us-west-2, etc.)|S3 bucket or DynamoDB table|Yes -|AMPLIFY_STORAGE_TABLES|The name of the storage resource and DynamoDB table being imported for storage|DynamoDB table|Yes - - -The value of the AMPLIFY_STORAGE_TABLES environment variable needs to be in a json format such as: - -``` -{ - "STORAGE_RESOURCE_NAME_1":"DDB_TABLE_NAME_1", - "STORAGE_RESOURCE_NAME_2":"DDB_TABLE_NAME_2" // If you are importing more than a single DynamoDB table -} -``` -The values for the `STORAGE_RESOURCE_NAME` and `DDB_TABLE_NAME` fields can be retrieved from the amplify/team-provider-info.json file. - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx deleted file mode 100644 index 294302bab3a..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/modify-amplify-generated-resources/index.mdx +++ /dev/null @@ -1,114 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Modify Amplify-generated resources', - description: "The 'amplify override storage' command generates a developer-configurable 'overrides' TypeScript file which provides Amplify-generated S3 and DynamoDB resources as CDK constructs. For example, developers can run the 'amplify override storage' command to enable Transfer Acceleration for Amplify-generated S3 buckets.", - platforms: [ - 'flutter', - ], - canonicalObjects: [ - { - platforms: [ - 'flutter', - ], - canonicalPath: '/javascript/build-a-backend/storage/modify-amplify-generated-resources/' - } - ] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -Amplify Storage uses underlying AWS services and resources such as S3 and DynamoDB. You can customize these underlying resources for your specific use case. - -```bash -amplify override storage -``` - -Run the command above to override Amplify-generated storage resources including the S3 bucket, DynamoDB tables, and more. - -The command creates a new `overrides.ts` file under `amplify/backend/storage//` which provides you the Amplify-generated resources as [CDK constructs](https://docs.aws.amazon.com/cdk/latest/guide/home.html). - -## Customize Amplify-generated S3 resources - -Apply all the overrides in the `override(...)` function. For example to enable versioning on your S3 bucket: - -```ts -import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyS3ResourceTemplate) { - resources.s3Bucket.versioningConfiguration = { - status: 'Enabled' - }; -} -``` - -You can override the following S3-related resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [s3Bucket](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html) | The S3 bucket that Amplify generates for file storage upon `amplify add storage` | -| [s3AuthPublicPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `public/*` prefix | -| [s3AuthProtectedPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `protected/*` prefix | -| [s3AuthPrivatePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `private/*` prefix | -| [s3AuthUploadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' write access to `uploads/*` prefix | -| [s3AuthReadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for authenticated users' read access | -| [s3GuestPublicPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' write access to `public/*` prefix | -| [s3GuestUploadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' write access to `uploads/*` prefix | -| [s3GuestReadPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) | The IAM policy for guest users' read access | - -
- -For example, you can use `amplify override storage` to add additional PUT and GET access IAM policy statements to the S3 bucket's default public Auth policy: - -```ts -import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyS3ResourceTemplate) { - resources.s3AuthPublicPolicy.policyDocument.Statement = [ - ...resources.s3AuthPublicPolicy.policyDocument.Statement, - { - Effect: 'Allow', - Action: ['s3:PutObject', 's3:PutObjectAcl', 's3:GetObject'], - Resource: '' - } - ]; -} -``` - -Please refer to the [IAM documentation](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html) for more information on actions, resources, and condition keys for Amazon S3. - -## Customize Amplify-generated DynamoDB tables - -Apply all the overrides in the `override(...)` function. For example to enable time-to-live specification on your DynamoDB table: - -```ts -import { AmplifyDDBResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; - -export function override(resources: AmplifyDDBResourceTemplate) { - resources.dynamoDBTable.timeToLiveSpecification = { - attributeName: 'ttl', - enabled: true - }; -} -``` - -You can override the following DynamoDB resources that Amplify generates: - -| Amplify-generated resource | Description | -| --- | --- | -| [dynamoDBTable](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | The DynamoDB table that Amplify creates upon `amplify add storage` | From 7ac992aa68c3c7caa7f1215225ad99a110c20ec5 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Mon, 13 May 2024 18:07:01 -0400 Subject: [PATCH 54/88] chore: fix flutter v2 PN docs (#7580) * chore: refactor push notification docs * chore: remove dup notification fragments * chore: remove `/lib/` references * chore: update references to `prev` --- .../getting_started/fcm-cli-resources.mdx | 19 +++ .../android/getting_started/fcm-pre-req.mdx | 1 + .../android/setup_push_service/setup-fcm.mdx | 28 +++ .../common/app-badge-count.mdx | 8 +- .../common/enable-rich-notifications.mdx | 4 +- .../getting_started/cross-platform-prereq.mdx | 8 +- .../getting_started/getting-started.mdx | 52 ++---- .../common/identify-user.mdx | 23 +-- .../interact-with-notifications.mdx | 6 +- .../notification-lifecycle.mdx | 4 +- .../notification-opened.mdx | 8 +- .../notification-received.mdx | 8 +- .../common/receive-device-token.mdx | 4 +- .../common/record-notifications.mdx | 21 --- .../common/register-device.mdx | 17 -- .../common/remote-media.mdx | 3 - .../common/request-permissions.mdx | 12 +- .../cross-platform-setup.mdx | 6 +- .../setup_push_service/setup-push-service.mdx | 10 +- .../app_badge_count/get-badge-count.mdx | 3 + .../app_badge_count/set-badge-count.mdx | 3 + .../add-notifications-pod.mdx | 19 +++ .../getting_started/20_cli_resources.mdx | 20 +++ .../getting_started/30_existing_resources.mdx | 17 ++ .../40_install_lib.mdx | 0 .../50_init_push_notifications.mdx | 48 ++++++ .../identify_user/10_get_auth_user.mdx | 7 + .../identify_user/20_send_to_pinpoint.mdx | 12 ++ .../get-launch-notification.mdx | 5 + .../on-background-notification.mdx | 33 ++++ .../on-foreground-notification.mdx | 12 ++ .../on-notification-opened.mdx | 11 ++ .../terminology.mdx | 2 + .../on-token-received.mdx | 11 ++ .../get-permission-status.mdx | 7 + .../request-permissions.mdx | 9 + .../sample-permissions-flow.mdx | 21 +++ .../getting_started/apns-cli-resources.mdx | 48 ++++++ .../ios/getting_started/apns-pre-req.mdx | 7 + .../getting_started/ios-set-entitlements.mdx | 25 +++ .../ios/setup_push_service/setup-apns.mdx | 160 ++++++++++++++++++ .../app_badge_count/app-badge-count.mdx | 22 --- .../enable-rich-notifications.mdx | 42 ----- .../getting_started/20_cli_resources.mdx | 4 +- .../getting_started/cross-platform-prereq.mdx | 45 ----- .../getting_started/getting-started.mdx | 62 ------- .../identify_user/identify-user.mdx | 15 -- .../interact-with-notifications.mdx | 32 ---- .../notification-lifecycle.mdx | 31 ---- .../notification-opened.mdx | 32 ---- .../notification-received.mdx | 32 ---- .../react-native/receive-device-token.mdx | 20 --- .../on-token-received.mdx | 9 + .../react-native/request-permissions.mdx | 83 --------- .../get-permission-status.mdx | 9 + .../request-permissions.mdx | 11 ++ .../sample-permissions-flow.mdx | 29 ++++ .../app-badge-count/index.mdx | 8 +- .../enable-rich-notifications/index.mdx | 8 +- .../identify-user/index.mdx | 11 +- .../interact-with-notifications/index.mdx | 7 +- .../receive-device-token/index.mdx | 7 +- .../request-permissions/index.mdx | 8 +- .../set-up-push-notifications/index.mdx | 11 +- .../set-up-push-service/index.mdx | 11 +- .../test-notifications/index.mdx | 11 +- 66 files changed, 657 insertions(+), 625 deletions(-) create mode 100644 src/fragments/lib-v1/push-notifications/android/getting_started/fcm-cli-resources.mdx create mode 100644 src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx create mode 100644 src/fragments/lib-v1/push-notifications/android/setup_push_service/setup-fcm.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/common/record-notifications.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/common/register-device.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/common/remote-media.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/app_badge_count/get-badge-count.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/app_badge_count/set-badge-count.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/enable_rich_notifications/add-notifications-pod.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/getting_started/20_cli_resources.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/getting_started/30_existing_resources.mdx rename src/fragments/lib-v1/push-notifications/flutter/{getting-started => getting_started}/40_install_lib.mdx (100%) create mode 100644 src/fragments/lib-v1/push-notifications/flutter/getting_started/50_init_push_notifications.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/identify_user/20_send_to_pinpoint.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/get-launch-notification.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-background-notification.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-foreground-notification.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-notification-opened.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/terminology.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/receive_device_token/on-token-received.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/request_permissions/get-permission-status.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/request_permissions/request-permissions.mdx create mode 100644 src/fragments/lib-v1/push-notifications/flutter/request_permissions/sample-permissions-flow.mdx create mode 100644 src/fragments/lib-v1/push-notifications/ios/getting_started/apns-cli-resources.mdx create mode 100644 src/fragments/lib-v1/push-notifications/ios/getting_started/apns-pre-req.mdx create mode 100644 src/fragments/lib-v1/push-notifications/ios/getting_started/ios-set-entitlements.mdx create mode 100644 src/fragments/lib-v1/push-notifications/ios/setup_push_service/setup-apns.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/app_badge_count/app-badge-count.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/enable_rich_notifications/enable-rich-notifications.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/getting_started/cross-platform-prereq.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/getting_started/getting-started.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/identify_user/identify-user.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/interact-with-notifications.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-lifecycle.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-opened.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-received.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/receive-device-token.mdx create mode 100644 src/fragments/lib-v1/push-notifications/react-native/receive_device_token/on-token-received.mdx delete mode 100644 src/fragments/lib-v1/push-notifications/react-native/request-permissions.mdx create mode 100644 src/fragments/lib-v1/push-notifications/react-native/request_permissions/get-permission-status.mdx create mode 100644 src/fragments/lib-v1/push-notifications/react-native/request_permissions/request-permissions.mdx create mode 100644 src/fragments/lib-v1/push-notifications/react-native/request_permissions/sample-permissions-flow.mdx diff --git a/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-cli-resources.mdx b/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-cli-resources.mdx new file mode 100644 index 00000000000..2a42b844f3e --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-cli-resources.mdx @@ -0,0 +1,19 @@ +Choose _FCM_ when promoted: + +```console +? Choose the push notification channel to enable. + APNS |  Apple Push Notifications +❯ FCM | » Firebase Push Notifications + In-App Messaging + Email + SMS + +? Provide your pinpoint resource name: + `yourPinpointResourceName` + +? Apps need authorization to send analytics events. Do you want to allow guests and unauthenticated users to send analytics events? (we recommend you allow this when getting started) (Y/n) + 'Y' + +``` + +The CLI will prompt for your _Server Key_, paste the **Token** you copied while [setting up push notification services](/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/). diff --git a/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx b/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx new file mode 100644 index 00000000000..b562aafc0b2 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx @@ -0,0 +1 @@ +Push Notifications are delivered via Firebase Cloud Messaging (FCM). In order to use FCM, you need to register your app on the Firebase console. See [Setting up push notification services](/gen1/[platform/prev/build-a-backend/push-notifications/set-up-push-service/) for more information. diff --git a/src/fragments/lib-v1/push-notifications/android/setup_push_service/setup-fcm.mdx b/src/fragments/lib-v1/push-notifications/android/setup_push_service/setup-fcm.mdx new file mode 100644 index 00000000000..155f8dc74df --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/android/setup_push_service/setup-fcm.mdx @@ -0,0 +1,28 @@ +## Setting Up FCM for Push Notifications + +Push notifications for Android apps are sent using Firebase Cloud Messaging (FCM). Before you can send push notifications to Android devices, you must perform the following steps: + +- [Create a Firebase project](https://firebase.google.com/docs/cloud-messaging/android/first-message#create_a_firebase_project). +- [Register your app with Firebase](https://firebase.google.com/docs/cloud-messaging/android/first-message#register_your_app_with_firebase) +- [Add a Firebase configuration file](https://firebase.google.com/docs/cloud-messaging/android/first-message#add_a_firebase_configuration_file) + +Next, you will need to access your **ServerKey** (Referred to as **ApiKey** in the CLI setup): + +- Open the [Firebase console](https://console.firebase.google.com/). +- Choose your Firebase project. +- Select the gear icon located in the top left hand corner of your screen, then select **Project settings**. + +![image](/images/push-notifications/setup-fcm/project-settings.png) + +- Select the **Cloud Messaging** tab. +- Select the three vertical dots next to **Cloud Messaging API (Legacy)**, then select **Manage API in Google Cloud Console**. ![The three dot menu button is circled in the cloud messaging tab.](/images/push-notifications/firebaseconsole.png) + +![image](/images/push-notifications/setup-fcm/manage-api.png) + +- In the new tab, select the **Enable** button. +- Return to the previous page and refresh it. +- Copy the **Server key** token + +![image](/images/push-notifications/setup-fcm/server-id.png) + +Return to [Provisioning resources through CLI](/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/#provisioning-resources-through-cli) with the copied **Token** diff --git a/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx b/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx index c261a3b62e2..0aae320132d 100644 --- a/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx +++ b/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx @@ -9,8 +9,8 @@ The app badge count, when set, can be seen on your app's icon on a user's device Use `getBadgeCount` to get the current app badge count. You might need to do this to calculate the value when setting the badge count. -import flutterGetBadgeCount from '/src/fragments/lib/push-notifications/flutter/app_badge_count/get-badge-count.mdx'; -import reactNativeGetBadgeCount from '/src/fragments/lib/push-notifications/react-native/app_badge_count/get-badge-count.mdx'; +import flutterGetBadgeCount from '/src/fragments/lib-v1/push-notifications/flutter/app_badge_count/get-badge-count.mdx'; +import reactNativeGetBadgeCount from '/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/get-badge-count.mdx'; An application targeting at least iOS 13.0, using Xcode 14.1 or later. -import apnsPreReq from '/src/fragments/lib/push-notifications/ios/getting_started/apns-pre-req.mdx'; +import apnsPreReq from '/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-pre-req.mdx'; @@ -18,7 +18,7 @@ Using Amplify Push Notifications with APNs requires the following capabilities: To add these capabilities: -import iosSetEntitlements from '/src/fragments/lib/push-notifications/ios/getting_started/ios-set-entitlements.mdx'; +import iosSetEntitlements from '/src/fragments/lib-v1/push-notifications/ios/getting_started/ios-set-entitlements.mdx'; @@ -27,7 +27,7 @@ import iosSetEntitlements from '/src/fragments/lib/push-notifications/ios/gettin An application targeting at least Android SDK API level 24. -import fcmPreReq from '/src/fragments/lib/push-notifications/android/getting_started/fcm-pre-req.mdx'; +import fcmPreReq from '/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx'; diff --git a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx index c54174e4bd4..830a0163b2f 100644 --- a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx +++ b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx @@ -1,32 +1,20 @@ The Push Notifications category allows you to integrate push notifications in your app with Amazon Pinpoint targeting, campaign, and journey management support. You can segment your users, trigger push notifications to your app, and record metrics in Pinpoint when users receive or open notifications. Amazon Pinpoint helps you to create messaging campaigns and journeys targeted to specific user segments or demographics and collect interaction metrics with push notifications. -import reactNativeExpoCallout from '/src/fragments/lib/push-notifications/react-native/getting_started/expo-callout.mdx'; +import reactNativeExpoCallout from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/expo-callout.mdx'; ## Prerequisites -import androidPreReq from '/src/fragments/lib/push-notifications/android/getting_started/10_pre_req.mdx'; -import crossPlatformPreReq from '/src/fragments/lib/push-notifications/common/getting_started/cross-platform-prereq.mdx'; -import iosPreReq from '/src/fragments/lib/push-notifications/ios/getting_started/10_pre_req.mdx'; +import crossPlatformPreReq from '/src/fragments/lib-v1/push-notifications/common/getting_started/cross-platform-prereq.mdx'; -import iosSetEntitlements from '/src/fragments/lib/push-notifications/ios/getting_started/20_set_entitlements.mdx'; - - - -import iosCreateAppDelegate from '/src/fragments/lib/push-notifications/ios/getting_started/30_create_app_delegate.mdx'; - - - ## Set up backend resources To use Push Notifications with Amplify, you have the option to either have the Amplify CLI setup resources for you, or you can use an existing Amazon Pinpoint resource in your AWS account. @@ -34,7 +22,7 @@ To use Push Notifications with Amplify, you have the option to either have the A -> Prerequisite: [Install and configure the Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) +> Prerequisite: [Install and configure the Amplify CLI](/gen1/[platform]/prev/tools/cli/start/set-up-cli/) @@ -48,16 +36,12 @@ To start provisioning push notification resources in the backend, go to your pro amplify add notifications ``` -import androidCliResources from '/src/fragments/lib/push-notifications/android/getting_started/20_cli_resources.mdx'; -import iosCliResources from '/src/fragments/lib/push-notifications/ios/getting_started/40_cli_resources.mdx'; -import flutterCliResources from '/src/fragments/lib/push-notifications/flutter/getting_started/20_cli_resources.mdx'; -import reactNativeCliResources from '/src/fragments/lib/push-notifications/react-native/getting_started/20_cli_resources.mdx'; +import flutterCliResources from '/src/fragments/lib-v1/push-notifications/flutter/getting_started/20_cli_resources.mdx'; +import reactNativeCliResources from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/20_cli_resources.mdx'; @@ -65,16 +49,12 @@ import reactNativeCliResources from '/src/fragments/lib/push-notifications/react -import androidExistingResources from '/src/fragments/lib/push-notifications/android/getting_started/30_existing_resources.mdx'; -import flutterExistingResources from '/src/fragments/lib/push-notifications/flutter/getting_started/30_existing_resources.mdx'; -import iosExistingResources from '/src/fragments/lib/push-notifications/ios/getting_started/50_existing_resources.mdx'; -import reactNativeExistingResources from '/src/fragments/lib/push-notifications/react-native/getting_started/30_existing_resources.mdx'; +import flutterExistingResources from '/src/fragments/lib-v1/push-notifications/flutter/getting_started/30_existing_resources.mdx'; +import reactNativeExistingResources from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/30_existing_resources.mdx'; @@ -84,36 +64,28 @@ import reactNativeExistingResources from '/src/fragments/lib/push-notifications/ ## Install Amplify Libraries -import androidInstallLib from '/src/fragments/lib/push-notifications/android/getting_started/40_install_lib.mdx'; -import flutterInstallLib from '/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx'; -import iosInstallLib from '/src/fragments/lib/push-notifications/ios/getting_started/60_install_lib.mdx'; -import reactNativeInstallLib from '/src/fragments/lib/push-notifications/react-native/getting_started/40_install_lib.mdx'; +import flutterInstallLib from '/src/fragments/lib-v1/push-notifications/flutter/getting_started/40_install_lib.mdx'; +import reactNativeInstallLib from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/40_install_lib.mdx'; -import reactNativeIntegrateNativeModule from '/src/fragments/lib/push-notifications/react-native/getting_started/50_integrate_native_modules.mdx'; +import reactNativeIntegrateNativeModule from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/50_integrate_native_modules.mdx'; ## Initialize Amplify Push Notifications -import androidInitPN from '/src/fragments/lib/push-notifications/android/getting_started/50_init_push_notifications.mdx'; -import flutterInitPN from '/src/fragments/lib/push-notifications/flutter/getting_started/50_init_push_notifications.mdx'; -import iosInitPN from '/src/fragments/lib/push-notifications/ios/getting_started/70_init_push_notifications.mdx'; -import reactNativeInitPN from '/src/fragments/lib/push-notifications/react-native/getting_started/60_init_push_notifications.mdx'; +import flutterInitPN from '/src/fragments/lib-v1/push-notifications/flutter/getting_started/50_init_push_notifications.mdx'; +import reactNativeInitPN from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/60_init_push_notifications.mdx'; diff --git a/src/fragments/lib-v1/push-notifications/common/identify-user.mdx b/src/fragments/lib-v1/push-notifications/common/identify-user.mdx index 353c9d98eda..0eafd6a930d 100644 --- a/src/fragments/lib-v1/push-notifications/common/identify-user.mdx +++ b/src/fragments/lib-v1/push-notifications/common/identify-user.mdx @@ -2,16 +2,12 @@ This call identifies the current user (which could be unauthenticated or authent ## Get the user ID from Amplify Auth -import androidGetUser from '/src/fragments/lib/push-notifications/android/identify_user/10_get_auth_user.mdx'; -import flutterGetUser from '/src/fragments/lib/push-notifications/flutter/identify_user/10_get_auth_user.mdx'; -import iosGetUser from '/src/fragments/lib/push-notifications/ios/identify_user/10_get_auth_user.mdx'; -import reactNativeGetUser from '/src/fragments/lib/push-notifications/react-native/identify_user/10_get_auth_user.mdx'; +import flutterGetUser from '/src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx'; +import reactNativeGetUser from '/src/fragments/lib-v1/push-notifications/react-native/identify_user/10_get_auth_user.mdx'; @@ -20,23 +16,12 @@ import reactNativeGetUser from '/src/fragments/lib/push-notifications/react-nati Once you have a string that identifies the current user (either from the Auth category as shown above or through your own application logic), you can identify the user to Amazon Pinpoint with the following: -import androidSendToPinpoint from '/src/fragments/lib/push-notifications/android/identify_user/20_send_to_pinpoint.mdx'; -import flutterSendToPinpoint from '/src/fragments/lib/push-notifications/flutter/identify_user/20_send_to_pinpoint.mdx'; -import iosSendToPinpoint from '/src/fragments/lib/push-notifications/ios/identify_user/20_send_to_pinpoint.mdx'; -import reactNativeSendToPinpoint from '/src/fragments/lib/push-notifications/react-native/identify_user/20_send_to_pinpoint.mdx'; +import flutterSendToPinpoint from '/src/fragments/lib-v1/push-notifications/flutter/identify_user/20_send_to_pinpoint.mdx'; +import reactNativeSendToPinpoint from '/src/fragments/lib-v1/push-notifications/react-native/identify_user/20_send_to_pinpoint.mdx'; - -import androidSendWithProfile from '/src/fragments/lib/push-notifications/android/identify_user/30_send_to_pinpoint_profile.mdx'; -import iosSendWithProfile from '/src/fragments/lib/push-notifications/ios/identify_user/30_send_to_pinpoint_profile.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx index 9ac148c28b6..8c10c0a96ce 100644 --- a/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx +++ b/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx @@ -4,7 +4,7 @@ Push notifications are powerful engagement tools for your users as they can be d - **Background state**: Your app is still running but is not currently active and visible. The user is usually on the home screen or in another app. - **Terminated state**: Your app is no longer running, even in the background. The user can initiate this by swiping your app away in the app switcher. -import notificationLifecycle from '/src/fragments/lib/push-notifications/common/interact_with_notifications/notification-lifecycle.mdx'; +import notificationLifecycle from '/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-lifecycle.mdx'; -import notificationReceived from '/src/fragments/lib/push-notifications/common/interact_with_notifications/notification-received.mdx'; +import notificationReceived from '/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-received.mdx'; -import notificationOpened from '/src/fragments/lib/push-notifications/common/interact_with_notifications/notification-opened.mdx'; +import notificationOpened from '/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/notification-opened.mdx'; - -import ios1 from '/src/fragments/lib/push-notifications/ios/record_notifications/10_notification_received.mdx'; - - - -## Record Notification Opened - -import android3 from '/src/fragments/lib/push-notifications/android/record_notifications/30_record_notification_opened.mdx'; - - - -import ios4 from '/src/fragments/lib/push-notifications/ios/record_notifications/20_notification_opened.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/common/register-device.mdx b/src/fragments/lib-v1/push-notifications/common/register-device.mdx deleted file mode 100644 index e679d608c73..00000000000 --- a/src/fragments/lib-v1/push-notifications/common/register-device.mdx +++ /dev/null @@ -1,17 +0,0 @@ -import ios1 from '/src/fragments/lib/push-notifications/ios/register_device/10_register_intro.mdx'; - - - -import ios4 from '/src/fragments/lib/push-notifications/ios/register_device/20_register_with_apns.mdx'; - - - -## Register with Amazon Pinpoint - -import android0 from '/src/fragments/lib/push-notifications/android/register_device/10_register_with_pinpoint.mdx'; - - - -import ios7 from '/src/fragments/lib/push-notifications/ios/register_device/30_register_with_pinpoint.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/common/remote-media.mdx b/src/fragments/lib-v1/push-notifications/common/remote-media.mdx deleted file mode 100644 index dcdac08a5de..00000000000 --- a/src/fragments/lib-v1/push-notifications/common/remote-media.mdx +++ /dev/null @@ -1,3 +0,0 @@ -import ios0 from '/src/fragments/lib/push-notifications/ios/remote_media/10_remote_media.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx b/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx index 2890b948232..1c9a56ebf5b 100644 --- a/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx +++ b/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx @@ -11,8 +11,8 @@ The first step to request permissions from your user is to understand the curren - **Granted** - Permissions have been granted by the user. No further actions are needed and their app is ready to display notifications. - **Denied** - Permissions have been denied by the user. Further attempts to request permissions will no longer trigger a permission dialog. Your app should now either degrade gracefully or prompt your user to grant the permissions needed in their device settings. -import flutterGetPermissionStatus from '/src/fragments/lib/push-notifications/flutter/request_permissions/get-permission-status.mdx'; -import reactNativeGetPermissionStatus from '/src/fragments/lib/push-notifications/react-native/request_permissions/get-permission-status.mdx'; +import flutterGetPermissionStatus from '/src/fragments/lib-v1/push-notifications/flutter/request_permissions/get-permission-status.mdx'; +import reactNativeGetPermissionStatus from '/src/fragments/lib-v1/push-notifications/react-native/request_permissions/get-permission-status.mdx'; -import apnsSetup from '/src/fragments/lib/push-notifications/ios/setup_push_service/setup-apns.mdx'; +import apnsSetup from '/src/fragments/lib-v1/push-notifications/ios/setup_push_service/setup-apns.mdx'; -import fcmSetup from '/src/fragments/lib/push-notifications/android/setup_push_service/setup-fcm.mdx'; +import fcmSetup from '/src/fragments/lib-v1/push-notifications/android/setup_push_service/setup-fcm.mdx'; - \ No newline at end of file + diff --git a/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx b/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx index ba45aaf9eb1..9ebb560a581 100644 --- a/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx +++ b/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx @@ -1,16 +1,8 @@ -import androidSetupService from '/src/fragments/lib/push-notifications/android/setup_push_service/setup-fcm.mdx'; -import crossPlatformSetupService from '/src/fragments/lib/push-notifications/common/setup_push_service/cross-platform-setup.mdx'; -import iosSetupService from '/src/fragments/lib/push-notifications/ios/setup_push_service/setup-apns.mdx'; +import crossPlatformSetupService from '/src/fragments/lib-v1/push-notifications/common/setup_push_service/cross-platform-setup.mdx'; - -import androidHandlingActions from '/src/fragments/lib/push-notifications/android/setup_push_service/handling-actions.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/flutter/app_badge_count/get-badge-count.mdx b/src/fragments/lib-v1/push-notifications/flutter/app_badge_count/get-badge-count.mdx new file mode 100644 index 00000000000..caa63be458d --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/app_badge_count/get-badge-count.mdx @@ -0,0 +1,3 @@ +```dart +final count = await Amplify.Notifications.Push.getBadgeCount(); +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/app_badge_count/set-badge-count.mdx b/src/fragments/lib-v1/push-notifications/flutter/app_badge_count/set-badge-count.mdx new file mode 100644 index 00000000000..7ff9be6aac9 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/app_badge_count/set-badge-count.mdx @@ -0,0 +1,3 @@ +```dart +Amplify.Notifications.Push.setBadgeCount(42); +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/enable_rich_notifications/add-notifications-pod.mdx b/src/fragments/lib-v1/push-notifications/flutter/enable_rich_notifications/add-notifications-pod.mdx new file mode 100644 index 00000000000..3c26a9e9cf0 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/enable_rich_notifications/add-notifications-pod.mdx @@ -0,0 +1,19 @@ +```ruby +target 'MyNotificationServiceExtension' do # Replace with your service extension + use_frameworks! + + pod "AmplifyUtilsNotifications" +end +``` + +3. Install the new pods by running `pod install` in the `ios/` of your application project. + + + + Note: You will first need to [install Cocoapods](https://guides.cocoapods.org/using/getting-started.html) to enable the `pod` command. + + +```bash +cd ios +pod install +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/getting_started/20_cli_resources.mdx b/src/fragments/lib-v1/push-notifications/flutter/getting_started/20_cli_resources.mdx new file mode 100644 index 00000000000..2bbd0015fc0 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/getting_started/20_cli_resources.mdx @@ -0,0 +1,20 @@ + + + +import apnsCliResources from '/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-cli-resources.mdx'; + + + +Upon completion, `amplifyconfiguration.dart` will be updated to reference the newly provisioned backend push notifications resources. Note that this file should already be generated for you by the Amplify CLI as a part of your project if you followed the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/prerequisites/). + + + + +import fcmCliResources from '/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-cli-resources.mdx'; + + + +Upon completion, `amplifyconfiguration.dart` will be updated to reference the newly provisioned backend push notifications resources. Note that this file should already be generated for you by the Amplify CLI as a part of your project if you followed the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/prerequisites/). + + + diff --git a/src/fragments/lib-v1/push-notifications/flutter/getting_started/30_existing_resources.mdx b/src/fragments/lib-v1/push-notifications/flutter/getting_started/30_existing_resources.mdx new file mode 100644 index 00000000000..fadde219181 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/getting_started/30_existing_resources.mdx @@ -0,0 +1,17 @@ +Existing Amazon Pinpoint resources can be used with Amplify Push Notifications by configuring `amplifyconfiguration.dart` with **Application ID** and **Region**. + +```dart +{ + "notifications": { + "plugins": { + "awsPinpointPushNotificationsPlugin": { + "appId": "", + "region": "" + } + } + } +} +``` + +- **appId**: Amazon Pinpoint application ID +- **region**: AWS Region where the resources are provisioned (e.g. `us-east-1`) diff --git a/src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx b/src/fragments/lib-v1/push-notifications/flutter/getting_started/40_install_lib.mdx similarity index 100% rename from src/fragments/lib-v1/push-notifications/flutter/getting-started/40_install_lib.mdx rename to src/fragments/lib-v1/push-notifications/flutter/getting_started/40_install_lib.mdx diff --git a/src/fragments/lib-v1/push-notifications/flutter/getting_started/50_init_push_notifications.mdx b/src/fragments/lib-v1/push-notifications/flutter/getting_started/50_init_push_notifications.mdx new file mode 100644 index 00000000000..efc780e3857 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/getting_started/50_init_push_notifications.mdx @@ -0,0 +1,48 @@ +To initialize Amplify Push Notifications, you will need to configure Amplify add the Auth and Push Notifications plugins. To complete initialization, call `Amplify.configure` + +Your resulting code should look something like the following: + +```dart +// Example main.dart + +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_push_notifications_pinpoint/amplify_push_notifications_pinpoint.dart'; + +import 'amplifyconfiguration.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + _configureAmplify(); + } + + Future _configureAmplify() async { + try { + final authPlugin = AmplifyAuthCognito(); + final pushPlugin = AmplifyPushNotificationsPinpoint(); + await Amplify.addPlugins([authPlugin, pushPlugin]); + await Amplify.configure(amplifyconfig); + } on Exception catch (e) { + safePrint('An error occurred configuring Amplify: $e'); + } + } + + @override + Widget build(BuildContext context) { + // Your application UI + } +} +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx b/src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx new file mode 100644 index 00000000000..75fd531ef97 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx @@ -0,0 +1,7 @@ +If the user is signed in through [Amplify.Auth.signIn](/gen1prev/build-a-backend/auth/enable-sign-in/), then you can retrieve the current user's ID as shown below: + +```dart +final user = await Amplify.Auth.getCurrentUser(); + +final userId = user.userId +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/identify_user/20_send_to_pinpoint.mdx b/src/fragments/lib-v1/push-notifications/flutter/identify_user/20_send_to_pinpoint.mdx new file mode 100644 index 00000000000..c87bd36d22c --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/identify_user/20_send_to_pinpoint.mdx @@ -0,0 +1,12 @@ +```dart +final userProfile = AWSPinpointUserProfile( + userAttributes: { + 'hobbies': ['cooking', 'knitting'] + }, +); + +await Amplify.Notifications.Push.identifyUser( + userId: userId, + userProfile: userProfile, +); +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/get-launch-notification.mdx b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/get-launch-notification.mdx new file mode 100644 index 00000000000..bfb5c0bbea3 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/get-launch-notification.mdx @@ -0,0 +1,5 @@ +```dart +final launchNotification = Amplify.Notifications.Push.launchNotification; + +... // Take further action with the `launchNotification` +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-background-notification.mdx b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-background-notification.mdx new file mode 100644 index 00000000000..0092f590427 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-background-notification.mdx @@ -0,0 +1,33 @@ +1. You should add this listener to your app's `main` function but _before_ `runApp` and `Amplify.configure` to avoid missing events. +2. The listener function added should be a [top-level global](https://dart.dev/language/functions#lexical-scope) or [static method](https://dart.dev/language/classes#static-methods) so that it can be invoked even when the app is terminated. +3. Notifications received in a terminated state will be considered low priority by Android unless you increase the priority of the payload by setting the `priority` to `high` (you will need to create a _Raw Message_ in Amazon Pinpoint to set this priority). + +```dart +// Example main.dart + +// Note: This handler does not *need* to be async, but it can be! +Future myAsyncNotificationReceivedHandler( + PushNotificationMessage notification) async { + // Process the received push notification message in the background +} + +void main() { + // Needed to enable background API in the killed state. + WidgetsFlutterBinding.ensureInitialized(); + + final authPlugin = AmplifyAuthCognito(); + final notificationsPlugin = AmplifyPushNotificationsPinpoint(); + + // Should be added in the main function to avoid missing events. + notificationsPlugin.onNotificationReceivedInBackground( + myAsyncNotificationReceivedHandler + ); + + await Amplify.addPlugins([authPlugin, notificationsPlugin]); + if (!Amplify.isConfigured) { + await Amplify.configure(amplifyconfig); + } + + runApp(const MyApp()); +} +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-foreground-notification.mdx b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-foreground-notification.mdx new file mode 100644 index 00000000000..6b5c1e96ca0 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-foreground-notification.mdx @@ -0,0 +1,12 @@ +```dart +void myNotificationReceivedHandler(PushNotificationMessage notification) { + // Respond to the received push notification message in realtime +} + +final subscription = Amplify + .Notifications.Push.onNotificationReceivedInForeground + .listen(myNotificationReceivedHandler); + +// Remember to cancel the subscription when it is no longer needed +subscription.cancel(); +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-notification-opened.mdx b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-notification-opened.mdx new file mode 100644 index 00000000000..727f13ccdf4 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/on-notification-opened.mdx @@ -0,0 +1,11 @@ +```dart +void myNotificationOpenedHandler(PushNotificationMessage notification) { + // Take further action with the opened push notification message +} + +final subscription = Amplify.Notifications.Push.onNotificationOpened + .listen(myNotificationReceivedHandler); + +// Remember to cancel the subscription when it is no longer needed +subscription.cancel(); +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/terminology.mdx b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/terminology.mdx new file mode 100644 index 00000000000..232c986ed1a --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/interact_with_notifications/terminology.mdx @@ -0,0 +1,2 @@ +- **Native layer** - This is the native (e.g. iOS or Android) application layer on top of which Flutter apps are built. +- **Application layer** - This is your Flutter app. diff --git a/src/fragments/lib-v1/push-notifications/flutter/receive_device_token/on-token-received.mdx b/src/fragments/lib-v1/push-notifications/flutter/receive_device_token/on-token-received.mdx new file mode 100644 index 00000000000..414ce087d00 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/receive_device_token/on-token-received.mdx @@ -0,0 +1,11 @@ +```dart +void myTokenReceivedHandler(String token) { + // Do something with the received token +} + +final subscription = Amplify.Notifications.Push.onTokenReceived + .listen(myTokenReceivedHandler); + +// Remember to cancel the subscription when it is no longer needed +subscription.cancel(); +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/request_permissions/get-permission-status.mdx b/src/fragments/lib-v1/push-notifications/flutter/request_permissions/get-permission-status.mdx new file mode 100644 index 00000000000..5dbd90ef4aa --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/request_permissions/get-permission-status.mdx @@ -0,0 +1,7 @@ +```dart +final status = await Amplify.Notifications.Push.getPermissionStatus(); +// PushNotificationPermissionStatus.shouldRequest | +// PushNotificationPermissionStatus.shouldExplainThenRequest | +// PushNotificationPermissionStatus.granted | +// PushNotificationPermissionStatus.denied +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/request_permissions/request-permissions.mdx b/src/fragments/lib-v1/push-notifications/flutter/request_permissions/request-permissions.mdx new file mode 100644 index 00000000000..73ff8a187b3 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/request_permissions/request-permissions.mdx @@ -0,0 +1,9 @@ +```dart +final result = await Amplify.Notifications.Push.requestPermissions( + // permissions are true by default + // alert: true + badge: true, + sound: true, +); +// true if granted (or already granted), false otherwise +``` diff --git a/src/fragments/lib-v1/push-notifications/flutter/request_permissions/sample-permissions-flow.mdx b/src/fragments/lib-v1/push-notifications/flutter/request_permissions/sample-permissions-flow.mdx new file mode 100644 index 00000000000..f70a2885a17 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/flutter/request_permissions/sample-permissions-flow.mdx @@ -0,0 +1,21 @@ +```dart +void handlePermissions() async { + final status = await Amplify.Notifications.Push.getPermissionStatus(); + switch (status) { + case PushNotificationPermissionStatus.granted: + // no further action is required, user has already granted permissions + break; + case PushNotificationPermissionStatus.denied: + // further attempts to request permissions will no longer do anything + continueWithoutPushNotifications(); + case PushNotificationPermissionStatus.shouldRequest: + // go ahead and request permissions from the user + await Amplify.Notifications.Push.requestPermissions(); + case PushNotificationPermissionStatus.shouldExplainThenRequest: + // you should display some explanation to your user before requesting permissions + await explainUpcomingPermissionRequest(); + // then request permissions + await Amplify.Notifications.Push.requestPermissions(); + } +} +``` diff --git a/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-cli-resources.mdx b/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-cli-resources.mdx new file mode 100644 index 00000000000..d207f6c5a53 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-cli-resources.mdx @@ -0,0 +1,48 @@ +Choose _APNS_ when promoted: + +```console +? Choose the push notification channel to enable. +❯ APNS |  Apple Push Notifications + FCM | » Firebase Push Notifications + In-App Messaging + Email + SMS +``` + +Follow the setup prompts: + + + +Authorizing the app for analytics events is crucial for unauthenticated users, particularly if you intend to configure Amplify or send push notifications to your users before their authentication. If authorization is declined, please keep in mind that updating the Cognito user pool would become necessary, and any subsequent updates could potentially result in the deletion of the current user data. + + + +```console +? Provide your pinpoint resource name: › [pinpoint_resource_name] +? Apps need authorization to send analytics events. Do you want to allow guests + and unauthenticated users to send analytics events? (we recommend you allow this + when getting started) (Y/n) +``` + +Choose _Certificate_ when promoted: + +```console +? Choose authentication method used for APNs +> Certificate +Key +``` + +The CLI will prompt for your _p12 certificate path_, enter a path relative to the location where you ran the command. + + + + +Note: If you receive this error: +```console +🛑 Command failed: openssl pkcs12 -in [path_to_your_cert].p12 -out [output_path].pem -nodes -passin pass: +Error outputting keys and certificates +``` + +Please try using LibreSSL 3.3.6 instead of OpenSSL. [See issue #12969](https://github.com/aws-amplify/amplify-cli/issues/12969) + + \ No newline at end of file diff --git a/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-pre-req.mdx b/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-pre-req.mdx new file mode 100644 index 00000000000..d7f1d9b0e50 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-pre-req.mdx @@ -0,0 +1,7 @@ +Push Notifications are delivered via the Apple Push Notification service (APNs). In order to use APNs, you need to setup credentials (keys or certificates) and export your credentials to a p12 file. See [Setting up push notification services](/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/) for more information. + + + +Testing with APNs on the iOS simulator requires an Apple Silicon Mac running macOS 13+, Xcode 14+, and iOS simulator 16+. If your development environment does not meet all these requirements, then you must run on a real device to get an APNs token. + + diff --git a/src/fragments/lib-v1/push-notifications/ios/getting_started/ios-set-entitlements.mdx b/src/fragments/lib-v1/push-notifications/ios/getting_started/ios-set-entitlements.mdx new file mode 100644 index 00000000000..b98709a1ffd --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/ios/getting_started/ios-set-entitlements.mdx @@ -0,0 +1,25 @@ +1. Open your Xcode project, go to project settings, select your main application target, select the **Signing and Capabilities** tab, and click **+ Capability** in the upper left corner of the editor pane to bring up the Capabilities dialog. + +![Arrow pointing to the plus capability icon.](/images/push-notifications/xcode-entitlements/01_add-capability.png) + + + +Xcode can be a little finicky with this step. If the Capabilities dialog is empty, try switching to a different tab and then switching back to **Signing and Capabilities** + + + +2. Type **push** in the filter box and double-click **Push Notifications** to add the capability. + +![The word push typed into the search bar capabilities and push notifications is a result.](/images/push-notifications/xcode-entitlements/02_add-push.png) + +3. Repeat step 1 to open the Capabilities dialog and then type **back** in the filter box and double-click **Background Modes** to add the capability. + +![The word back typed into the search bar and background modes is a result.](/images/push-notifications/xcode-entitlements/03_add-background.png) + +4. Select **Change All** when prompted. + +5. Under **Background Modes**, select **Remote Notifications** + +![Remote notifications box is selected in the background modes section.](/images/push-notifications/xcode-entitlements/04_background-remote-notifications.png) + +6. Select **Change All** again when prompted. diff --git a/src/fragments/lib-v1/push-notifications/ios/setup_push_service/setup-apns.mdx b/src/fragments/lib-v1/push-notifications/ios/setup_push_service/setup-apns.mdx new file mode 100644 index 00000000000..e0eab422d15 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/ios/setup_push_service/setup-apns.mdx @@ -0,0 +1,160 @@ +## Setting Up APNs for Push Notifications + +Push notifications for iOS apps are sent using Apple Push Notification service (APNs). Before you can send push notifications to iOS devices, you must create an app ID on the Apple Developer portal, and you must create the required certificates. + +This section describes how to use the Apple Developer portal to obtain iOS and APNs credentials. These credentials enable you to create an iOS project that can receive push notifications. + +After completing the steps in this section, you will have the following items in your Apple Developer account: + +- An app ID. +- An SSL certificate, which authorizes you to send push notifications to your app through APNs. +- A registration for your test device, such as an iPhone, with your Apple Developer account. +- An iOS distribution certificate, which enables you to install your app on your test device. +- A provisioning profile, which allows your app to run on your test device. + +Before you begin, you must have an account with the Apple Developer Program as an individual or as part of an organization, and you must have agent or admin privileges in that account. + +**Topics** + +- [Step 1: Create an App ID](#step-1-create-an-app-id) +- [Step 2: Create an APNs SSL Certificate](#step-2-create-an-apns-ssl-certificate) +- [Step 3: Register a Test Device](#step-3-register-a-test-device) +- [Step 4: Create an iOS Distribution Certificate](#step-4-create-an-ios-distribution-certificate) +- [Step 5: Create a Provisioning Profile](#step-5-create-a-provisioning-profile) + +### Step 1: Create an App ID + +Create an app ID to identify your app in the Apple Developer portal. You need this ID when you create an SSL certificate for sending push notifications, when you create an iOS distribution certificate, and when you create a provisioning profile. + +If you already have an ID assigned to your app, you can skip this step. You can use an existing app ID provided it doesn't contain a wildcard character ("\*"). + +**To assign an App ID to your app** + +- Sign in to your [Apple Developer account](https://developer.apple.com/membercenter/index.action) and navigate to [Identifiers](https://developer.apple.com/account/resources/identifiers/list) +- In the **Identifiers** pane, click the **Register an App ID** button, or click the blue **(+)** button in the header. + +![image](/images/push-notifications/setup-apns/create-identifier--add.png) + +- In the **Register a new identifier** pane, Select **App IDs** and click **Continue**. + +![image](/images/push-notifications/setup-apns/create-identifier--app-id.png) + +- In the **Register a new identifier** pane, Select **App** and click **Continue**. + +![image](/images/push-notifications/setup-apns/create-identifier--app-type.png) + +- In the **Registering an App ID** pane, for **Description**, type a name for your app ID that makes it easy to recognize later. +- For **App ID Prefix**, select **Explicit**, and type a bundle ID for your app. If you already have an app, use the bundle ID assigned to it. You can find this ID in the app project in Xcode on your Mac. Otherwise, take note of the bundle ID because you will assign it to your app in Xcode later. + +![image](/images/push-notifications/setup-apns/create-identifier--bundle-id.png) + +- Under **Capabilities**, select **Push Notifications**. + +![image](/images/push-notifications/setup-apns/create-identifier--enable-PN.png) + +- Click **Continue**. In the **Confirm your App ID** pane, check that all values were entered correctly. +- Click **Register** to register the new app ID. + +### Step 2: Create an APNs SSL Certificate + +You will create and download a certificate from your Apple Developer account. Then, you will install the certificate in Keychain Access and export it as a `.p12` file. Which will allow Pinpoint to communicate with Apple's Push Notification Service (APNS). + +If you already have an SSL certificate for your app, you can skip this step. + +**To create an SSL certificate for push notifications** + +- Under **Certificates, Identifiers & Profiles**, navigate to [Identifiers](https://developer.apple.com/account/resources/identifiers/list) +- From the list of iOS Identifiers, click the app that you created in [Step 1: Create an App ID](#step-1-create-an-app-id). +- Under **Capabilities**, locate **Push Notifications**, click **Configure** + +![image](/images/push-notifications/setup-apns/create-certificate--configure.png) + +- In the **Production SSL Certificate** section, click **Create Certificate**. + +![image](/images/push-notifications/setup-apns/create-certificate--create.png) + +- Follow these [instructions](https://developer.apple.com/help/account/create-certificates/create-a-certificate-signing-request) for creating a local Certificate Signing Request (CSR) file using the Keychain Access application on your Mac. + +![image](/images/push-notifications/setup-apns/create-certificate--cert-assistant.png) + +- In the **Create a New Certificate** pane, click **Choose File**, and then select the CSR file you created and click **Continue**. + +![image](/images/push-notifications/setup-apns/create-certificate--upload-cert.png) + +- When the certificate is ready, click **Download** to save the certificate to your computer. It should be called `aps.cer`. +- Double-click the downloaded certificate to install it to the Keychain on your Mac. +- On your Mac, start the Keychain Access application. +- Click the **My Certificates** chip in the toolbar to find the certificate you just added. The certificate will be named "Apple Push Services:`com.my.app.id`", where `com.my.app.id` is the bundle ID you configured in the previous step. + +![image](/images/push-notifications/setup-apns/create-certificate--my-cert.png) + +- Right-click the push certificate and then select **Export "Apple Push Services:`com.my.app.id`"** from the context menu to export a file containing the certificate. + +![image](/images/push-notifications/setup-apns/create-certificate--export-cert.png) + +- Name the file `amplify` and ensure that `p12` is selected as the export format, then save it to your computer. Click **OK** to skip adding a password to the certificate. Then enter your computer password to complete the export. + + + +Note: The remaining steps are done automatically in Xcode when the "Automatically manage signing" box is checked. + + + +### Step 3: Register a Test Device + +Register a test device with your Apple Developer account so that you can test your app on that device. Later, you associate this test device with your provisioning profile, which allows your app to launch on your device. + +If you already have a registered device, you can skip this step. + +**To add a device** + +- Under **Certificates, Identifiers & Profiles**, navigate to [Devices](https://developer.apple.com/account/resources/devices/list) +- In the **Devices** pane, click the **Register a Device** button, or click the blue **(+)** button in the header. +- In the **Register Device** section, choose the platform of device that you want to add, such as **iOS**. +- For **Device Name**, type a name that is easy to recognize later. +- For **Device ID (UDID)**, type the unique device ID. For an iPhone, you can find the UDID by completing the following steps: + + - Connect your iPhone to your Mac with a USB cable. + - Open the Xcode app. + - In the Xcode menu bar navigate to **Window > Devices and Simulators** + - Xcode will display the summary page for your connected devices, select desired device. + - In the header, the summary page provides information about your iPhone. The value next to **Identifier: ** is your **UDID**. + - Select and copy your **UDID**. + - Paste your UDID into the **UDID** field in the Apple Developer website. + - Click **Continue**. + +- On the **Register a New Device** pane, verify the details for your device, and click **Register**. +- Your device name and identifier have been added to the list of devices. Click **Done**. + +### Step 4: Create an iOS Distribution Certificate + +An iOS distribution certificate enables you to install your app on a test device and deliver push notifications to that device. You specify your iOS distribution certificate later when you create a provisioning profile for your app. + +If you already have an iOS distribution certificate, you can skip this step. + +**To create an iOS distribution certificate** + +- Under **Certificates, Identifiers & Profiles**, navigate to [Certificates](https://developer.apple.com/account/resources/certificates/list) +- In the **Certificates** pane, click the blue **(+)** button in the header. The **Create a New Certificate** pane is shown. +- In the **Software** section, select **iOS Distribution (App Store and Ad Hoc)**, and then click **Continue**. +- Follow these [instructions](https://developer.apple.com/help/account/create-certificates/create-a-certificate-signing-request) for creating a Certificate Signing Request (CSR) file. You use the Keychain Access application on your Mac to create the request and save it on your local disk. +- In the **Create a New Certificate** pane, click **Choose File**, and then select the CSR file you created and click **Continue**. +- When the certificate is ready, click **Download** to save the certificate to your computer. +- Double-click the downloaded certificate to install it in Keychain on your Mac. + +### Step 5: Create a Provisioning Profile + +A provisioning profile allows your app to run on your test device. You create and download a provisioning profile from your Apple Developer account and then install the provisioning profile in Xcode. + +**To create a provisioning profile** + +- Under **Certificates, Identifiers & Profiles**, navigate to [Profiles](https://developer.apple.com/account/resources/profiles/list) +- In the **Profiles** pane, click the **Generate a profile** button, or click the blue **(+)** button in the header. The **Register a New Provisioning Profile** pane is shown. +- Under the **Distribution** section, select **Ad Hoc**, and then click **Continue**. +- For **Select an App ID**, select the app ID you created for your app, and then click **Continue**. +- Select your iOS Distribution certificate and then click **Continue**. +- For **Select devices**, select the device that you registered for testing, and click **Continue**. +- Type a name for this provisioning profile, such as `ApnsDistributionProfile`, and click **Generate**. +- Click **Download** to download the generated provisioning profile. +- Install the provisioning profile by double-clicking the downloaded file. The Xcode app opens in response. +- To verify that the provisioning profile is installed, check the list of installed provisioning profiles in Xcode by following this [guide](https://help.apple.com/xcode/mac/current/#/devaafd622d2). diff --git a/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/app-badge-count.mdx b/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/app-badge-count.mdx deleted file mode 100644 index 486f663c7bf..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/app-badge-count.mdx +++ /dev/null @@ -1,22 +0,0 @@ -The app badge count, when set, can be seen on your app's icon on a user's device. Amplify provides you with simple helpers to manipulate this number. - - - App badge count helpers are safe to call (but will be ignored) even when your - app is running on platforms where badges are not supported. - - -## Get the current badge count - -Use `getBadgeCount` to get the current app badge count. You might need to do this to calculate the value when setting the badge count. - -import getBadgeCount from '/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/get-badge-count.mdx'; - - - -## Update the badge count - -Use `setBadgeCount` to set the current app badge count. Setting the badge count to `0` (zero) will remove the badge from your app's icon. - -import setBadgeCount from '/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/set-badge-count.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/react-native/enable_rich_notifications/enable-rich-notifications.mdx b/src/fragments/lib-v1/push-notifications/react-native/enable_rich_notifications/enable-rich-notifications.mdx deleted file mode 100644 index 7d9466f5550..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/enable_rich_notifications/enable-rich-notifications.mdx +++ /dev/null @@ -1,42 +0,0 @@ - - On Android, Amplify Push Notifications should already be configured to handle - rich content for you. - - -Amplify currently supports adding images to your notifications, but there are some additional steps required. - -## Create a Notification Service Extension - -1. Open `.xcworkspace` located inside the `/ios` folder of your application project with Xcode. - -2. In the Xcode project, select **File > New > Target...** - -![The file menu is selected in the toolbar, then the new option is selected, then the target option.](/images/push-notifications/cross-platform-service-extension/01_new-target.png) - -3. Select **Notification Service Extension > Next**. - -![The notification service extension option is selected in the new target popup.](/images/push-notifications/cross-platform-service-extension/02_target-type.png) - -4. Enter a name for your service extension (e.g. MyNotificationServiceExtension) and select **Finish**. - -![Form for the new target options.](/images/push-notifications/cross-platform-service-extension/03_target-name.png) - -## Provide the extension with the Amplify service class - -1. Open `Podfile` located inside the `/ios` folder of your application project with a text editor. - -2. Add `AmplifyUtilsNotifications` to the extension you created above. - -import addNotificationsPod from '/src/fragments/lib-v1/push-notifications/react-native/enable_rich_notifications/add-notifications-pod.mdx'; - - - -4. Open `.xcworkspace` located inside the `/ios` folder of your application project with Xcode. - -5. Find your extension folder in the Project navigator pane and select the **Info** Property List. - -![Extension info](/images/push-notifications/cross-platform-service-extension/04_extension-info.png) - -6. Update the `NSExtensionPrincipalClass` property with the value `AmplifyUtilsNotifications.AUNotificationService`. - -![Extension class](/images/push-notifications/cross-platform-service-extension/05_extension-class.png) diff --git a/src/fragments/lib-v1/push-notifications/react-native/getting_started/20_cli_resources.mdx b/src/fragments/lib-v1/push-notifications/react-native/getting_started/20_cli_resources.mdx index 5119fcd55c4..3c8ce6fa6dd 100644 --- a/src/fragments/lib-v1/push-notifications/react-native/getting_started/20_cli_resources.mdx +++ b/src/fragments/lib-v1/push-notifications/react-native/getting_started/20_cli_resources.mdx @@ -1,7 +1,7 @@ -import apnsCliResources from '/src/fragments/lib/push-notifications/ios/getting_started/apns-cli-resources.mdx'; +import apnsCliResources from '/src/fragments/lib-v1/push-notifications/ios/getting_started/apns-cli-resources.mdx'; @@ -10,7 +10,7 @@ Upon completion, `aws-exports.js` will be updated to reference the newly provisi -import fcmCliResources from '/src/fragments/lib/push-notifications/android/getting_started/fcm-cli-resources.mdx'; +import fcmCliResources from '/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-cli-resources.mdx'; diff --git a/src/fragments/lib-v1/push-notifications/react-native/getting_started/cross-platform-prereq.mdx b/src/fragments/lib-v1/push-notifications/react-native/getting_started/cross-platform-prereq.mdx deleted file mode 100644 index 861dd81592e..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/getting_started/cross-platform-prereq.mdx +++ /dev/null @@ -1,45 +0,0 @@ -You should have [completed the CLI and project setup steps.](/gen1/[platform]/prev/start/project-setup/prerequisites/) - - - - -An application targeting at least iOS 13.0, using Xcode 14.1 or later. - -import apnsPreReq from '/src/fragments/lib/push-notifications/ios/getting_started/apns-pre-req.mdx'; - - - -### Set Entitlements - -Using Amplify Push Notifications with APNs requires the following capabilities: - -- Push Notifications -- Background Processing -> Remote Notifications - -To add these capabilities: - -import iosSetEntitlements from '/src/fragments/lib/push-notifications/ios/getting_started/ios-set-entitlements.mdx'; - - - - - - -An application targeting at least Android SDK API level 24. - -import fcmPreReq from '/src/fragments/lib/push-notifications/android/getting_started/fcm-pre-req.mdx'; - - - -### Applying the Google services plugin - -The Firebase documentation directs you to add the Google services plugin to your app `build.gradle` using the [Gradle plugins DSL](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block). However, we recommend you continue to use the [Legacy Plugin Application](https://docs.gradle.org/current/userguide/plugins.html#sec:old_plugin_application) instead: - -```groovy -apply plugin: 'com.google.gms.google-services' -``` - -If you prefer using the plugins DSL, you should add the `plugins` block to the very top of the file or you may experience issues when building your app for Android. - - - diff --git a/src/fragments/lib-v1/push-notifications/react-native/getting_started/getting-started.mdx b/src/fragments/lib-v1/push-notifications/react-native/getting_started/getting-started.mdx deleted file mode 100644 index a20ed1b4860..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/getting_started/getting-started.mdx +++ /dev/null @@ -1,62 +0,0 @@ -The Push Notifications category allows you to integrate push notifications in your app with Amazon Pinpoint targeting, campaign, and journey management support. You can segment your users, trigger push notifications to your app, and record metrics in Pinpoint when users receive or open notifications. Amazon Pinpoint helps you to create messaging campaigns and journeys targeted to specific user segments or demographics and collect interaction metrics with push notifications. - -import expoCallout from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/expo-callout.mdx'; - - - -## Prerequisites - -import crossPlatformPreReq from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/cross-platform-prereq.mdx'; - - - -## Set up backend resources - -To use Push Notifications with Amplify, you have the option to either have the Amplify CLI setup resources for you, or you can use an existing Amazon Pinpoint resource in your AWS account. - - - - -> Prerequisite: [Install and configure the Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - - - -Push Notifications requires version **10.8.0+** of the Amplify CLI. You can check your current version with `amplify -version` and upgrade to the latest version with `amplify upgrade`. - - - -To start provisioning push notification resources in the backend, go to your project directory and execute the command: - -```sh -amplify add notifications -``` - -import cliResources from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/20_cli_resources.mdx'; - - - - - - -import existingResources from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/30_existing_resources.mdx'; - - - - - - -## Install Amplify Libraries - -import installLib from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/40_install_lib.mdx'; - - - -import integrateNativeModule from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/50_integrate_native_modules.mdx'; - - - -## Initialize Amplify Push Notifications - -import initPN from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/60_init_push_notifications.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/react-native/identify_user/identify-user.mdx b/src/fragments/lib-v1/push-notifications/react-native/identify_user/identify-user.mdx deleted file mode 100644 index af229e891a6..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/identify_user/identify-user.mdx +++ /dev/null @@ -1,15 +0,0 @@ -This call identifies the current user (which could be unauthenticated or authenticated) to Amazon Pinpoint. The user ID can be any string which identifies the user in the context of your application. - -## Get the user ID from Amplify Auth - -import getUser from '/src/fragments/lib-v1/push-notifications/react-native/identify_user/10_get_auth_user.mdx'; - - - -## Identify the user to Amazon Pinpoint - -Once you have a string that identifies the current user (either from the Auth category as shown above or through your own application logic), you can identify the user to Amazon Pinpoint with the following: - -import sendToPinpoint from '/src/fragments/lib-v1/push-notifications/react-native/identify_user/20_send_to_pinpoint.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/interact-with-notifications.mdx b/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/interact-with-notifications.mdx deleted file mode 100644 index 90e56e17cf2..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/interact-with-notifications.mdx +++ /dev/null @@ -1,32 +0,0 @@ -Push notifications are powerful engagement tools for your users as they can be delivered even when your app is not in the active foreground. As a result, there are many cases where it is helpful (or perhaps necessary) to interact with notification events differently depending upon your app's state. - -- **Foreground state**: Your app is running, active and visible. -- **Background state**: Your app is still running but is not currently active and visible. The user is usually on the home screen or in another app. -- **Terminated state**: Your app is no longer running, even in the background. The user can initiate this by swiping your app away in the app switcher. - -import notificationLifecycle from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-lifecycle.mdx'; - - - -import notificationReceived from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-received.mdx'; - - - -import notificationOpened from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-opened.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-lifecycle.mdx b/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-lifecycle.mdx deleted file mode 100644 index de93ce05351..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-lifecycle.mdx +++ /dev/null @@ -1,31 +0,0 @@ -## Notification lifecycle - -Before delving into details about the various functions Amplify provides, it can be helpful to better understand the lifecycle of a notification as it moves through your app in its various states once you've integrated Amplify Push Notifications. - -For the purposes of this guide, we will simplify the terminology as follows - -import terminology from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/terminology.mdx'; - - - -### In a foreground state - -![Chart of the foreground state lifecycle](/images/push-notifications/cross-platform-lifecycle/pn-lifecycle-foreground.png) - -- Notifications are not displayed when received in foreground -- It is possible for notifications to arrive in your user's notification center but not be opened until a later time -- Since it is possible to interact with the notification center at any time on a device, your user could even open a notification while your app is in the active foreground - -### In a background state - -![Chart of the background state lifecycle](/images/push-notifications/cross-platform-lifecycle/pn-lifecycle-background.png) - -- _Notification A_ represents an example of a notification which was received but never interacted with -- Recall that, in a background state, your app is still running and therefore does not need to be launched — only brought back into the foreground - -### In a terminated state - -![Chart of the terminated state lifecycle](/images/push-notifications/cross-platform-lifecycle/pn-lifecycle-terminated.png) - -- _Notification A_ represents an example of a notification which was received but never interacted with -- Recall that, in a terminated state, your app is no longer running and therefore needs to be launched diff --git a/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-opened.mdx b/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-opened.mdx deleted file mode 100644 index b91abb22fc5..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-opened.mdx +++ /dev/null @@ -1,32 +0,0 @@ -## Respond to a notification being opened - -When a user taps on a notification displayed on their device, your app will be either launched or brought to the foreground. Knowing the contents of the notification the user interacted with can help you take further action (e.g. follow a deep link) or glean additional insight into your user engagement. - -To help you with this, Amplify provides two ways of handling notifications being opened. It is recommended that you handle both to ensure your users the most consistent and seamless experience. - -| App state | Handle with | -| :---------------------: | :---------------------: | -| Foreground / Background | `onNotificationOpened` | -| Terminated | `getLaunchNotification` | - -### onNotificationOpened - -Add `onNotificationOpened` listeners to respond to a push notification being opened while your app is in a foreground **or** background state. - -import onNotificationOpened from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/on-notification-opened.mdx'; - - - -### getLaunchNotification - -When your app is launched from a terminated state, you must call `getLaunchNotification` to obtain the notification which launched your app. - -Calling `getLaunchNotification` _consumes_ the launch notification and will yield a `null` result if: - -- You called it more than once (i.e. subsequent calls will be `null`) -- Another notification was opened while your app was running (either in foreground or background) -- Your app was brought back to the foreground by some other means (e.g. user tapped the app icon) - -import getLaunchNotification from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/get-launch-notification.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-received.mdx b/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-received.mdx deleted file mode 100644 index 9e38c559b18..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/notification-received.mdx +++ /dev/null @@ -1,32 +0,0 @@ -## Respond to a notification being received - -Push notifications received by your users are useful engagement tools but they also provide a data delivery mechanism to your app! - -Your app will likely need to respond to notifications being received in different ways depending on its state, namely while it is either actively in the foreground (where your app may respond by updating UI) or not (where your app may respond by performing tasks to ensure your app experience is up to date). - -| App state | Handle with | -| :---------------------: | :----------------------------------: | -| Foreground | `onNotificationReceivedInForeground` | -| Background / Terminated | `onNotificationReceivedInBackground` | - -### Notification received in foreground - -Notifications received while your app is in the foreground state do not get displayed. But their contents may be useful for updating your app (e.g. updating the UI to reflect a new inbox message). - -Add `onNotificationReceivedInForeground` listeners to respond to a push notification being received while your app is in a foreground state. - -import onForegroundNotification from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/on-foreground-notification.mdx'; - - - -### Notification received in background - -You may be able to improve your users' experience by having your app perform tasks in the background (e.g. fetching data) so they will have the most up-to-date experience when they next launch your app. - -Add `onNotificationReceivedInBackground` listeners to respond to a push notification being received while your app is in a background **or** terminated state. - -For background notifications to be handled while your app is terminated, it is important to note: - -import onBackgroundNotification from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/on-background-notification.mdx'; - - diff --git a/src/fragments/lib-v1/push-notifications/react-native/receive-device-token.mdx b/src/fragments/lib-v1/push-notifications/react-native/receive-device-token.mdx deleted file mode 100644 index 4306662f232..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/receive-device-token.mdx +++ /dev/null @@ -1,20 +0,0 @@ -Push notifications are delivered to your user's devices through a device token which uniquely identifies your app. Although Amplify will automatically register this token with Amazon Pinpoint, it can still be useful to have access to this token for your app's use cases (e.g. to send direct notifications to a specific device). - -### onTokenReceived - -Add `onTokenReceived` listeners to respond to a token being received by your app. - -A token will be received by your app: - -- On every app launch, including the first install -- When a token changes (this may happen if the service invalidates the token for any reason) - -```js -const myTokenReceivedHandler = (token) => { - // Do something with the received token -}; - -const listener = Notifications.Push.onTokenReceived(myTokenReceivedHandler); - -listener.remove(); // Remember to remove the listener when it is no longer needed -``` diff --git a/src/fragments/lib-v1/push-notifications/react-native/receive_device_token/on-token-received.mdx b/src/fragments/lib-v1/push-notifications/react-native/receive_device_token/on-token-received.mdx new file mode 100644 index 00000000000..35f763cb4fc --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/react-native/receive_device_token/on-token-received.mdx @@ -0,0 +1,9 @@ +```js +const myTokenReceivedHandler = (token) => { + // Do something with the received token +}; + +const listener = Notifications.Push.onTokenReceived(myTokenReceivedHandler); + +listener.remove(); // Remember to remove the listener when it is no longer needed +``` diff --git a/src/fragments/lib-v1/push-notifications/react-native/request-permissions.mdx b/src/fragments/lib-v1/push-notifications/react-native/request-permissions.mdx deleted file mode 100644 index b6eed9f6641..00000000000 --- a/src/fragments/lib-v1/push-notifications/react-native/request-permissions.mdx +++ /dev/null @@ -1,83 +0,0 @@ -Depending on your users' platform and operating system version, it is likely that you will need to request their permission to display push notifications. - -To learn more about the platform-specific guidances for requesting permissions, you can visit the respective documentations for [iOS](https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications) and [Android](https://developer.android.com/develop/ui/views/notifications/notification-permission). To best aid you in giving your users a good permission experience with platform idiomatic flows, Amplify provides the functionality below. - -## Get permission status - -The first step to request permissions from your user is to understand the current status of permissions. Your app may behave differently in response to these possible statuses below. - -- **Should Request** - No permissions have been requested yet. It is idiomatic at this time to simply request for permissions from the user. -- **Should Explain Then Request** - It is recommended at this time to provide some context or rationale to the user explaining why you want to send them push notifications before requesting for permissions. -- **Granted** - Permissions have been granted by the user. No further actions are needed and their app is ready to display notifications. -- **Denied** - Permissions have been denied by the user. Further attempts to request permissions will no longer trigger a permission dialog. Your app should now either degrade gracefully or prompt your user to grant the permissions needed in their device settings. - - - If you use TypeScript for your development, the string values returned can be - represented as PushNotificationPermissionStatus enum members as well. - - -```js -const status = await Notifications.Push.getPermissionStatus(); -// 'SHOULD_REQUEST' | 'SHOULD_EXPLAIN_THEN_REQUEST' | 'GRANTED' | 'DENIED' -``` - -## Request permissions - -Once you have determined if the current permission status requires you to request permissions from the user, you can call `requestPermissions()` to make that request. - -Amplify requests all supported notification permissions by default. But you can also choose not to request specific permissions. - - - It is recommended that you specify these permissions if needed but it is - important to note that they are ignored by Android - - -- **Alert**: When set to true, requests the ability to display notifications to the user. -- **Sound**: When set to true, requests the ability to play a sound in response to notifications. -- **Badge**: When set to true, requests the ability to update the app's badge. - -```js -const permissions = { - // permissions are true by default - // alert: true - sound: false, - badge: false -}; - -const result = await Notifications.Push.requestPermissions(permissions); -// true if granted (or already granted), false otherwise -``` - -## Sample permissions flow - -Use `getPermissionStatus()` and `requestPermissions()` together to handle permission request flows. Below is a sample implementation of the expected logic. - - - Remember, if you use TypeScript for your development, you can use the - PushNotificationPermissionStatus enum for comparison as well! - - -```js -async function handlePermissions() { - const status = await Notifications.Push.getPermissionStatus(); - if (status === 'GRANTED') { - // no further action is required, user has already granted permissions - return; - } - if (status === 'DENIED') { - // further attempts to request permissions will no longer do anything - myFunctionToGracefullyDegradeMyApp(); - return; - } - if (status === 'SHOULD_REQUEST') { - // go ahead and request permissions from the user - await Notifications.Push.requestPermissions(); - } - if (status === 'SHOULD_EXPLAIN_THEN_REQUEST') { - // you should display some explanation to your user before requesting permissions - await myFunctionExplainingPermissionsRequest(); - // then request permissions - await Notifications.Push.requestPermissions(); - } -} -``` diff --git a/src/fragments/lib-v1/push-notifications/react-native/request_permissions/get-permission-status.mdx b/src/fragments/lib-v1/push-notifications/react-native/request_permissions/get-permission-status.mdx new file mode 100644 index 00000000000..f5a63cf7320 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/react-native/request_permissions/get-permission-status.mdx @@ -0,0 +1,9 @@ + + If you use TypeScript for your development, the string values returned can be + represented as PushNotificationPermissionStatus enum members as well. + + +```js +const status = await Notifications.Push.getPermissionStatus(); +// 'SHOULD_REQUEST' | 'SHOULD_EXPLAIN_THEN_REQUEST' | 'GRANTED' | 'DENIED' +``` diff --git a/src/fragments/lib-v1/push-notifications/react-native/request_permissions/request-permissions.mdx b/src/fragments/lib-v1/push-notifications/react-native/request_permissions/request-permissions.mdx new file mode 100644 index 00000000000..216c3a505c1 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/react-native/request_permissions/request-permissions.mdx @@ -0,0 +1,11 @@ +```js +const permissions = { + // permissions are true by default + // alert: true + sound: false, + badge: false +}; + +const result = await Notifications.Push.requestPermissions(permissions); +// true if granted (or already granted), false otherwise +``` diff --git a/src/fragments/lib-v1/push-notifications/react-native/request_permissions/sample-permissions-flow.mdx b/src/fragments/lib-v1/push-notifications/react-native/request_permissions/sample-permissions-flow.mdx new file mode 100644 index 00000000000..241c0e40118 --- /dev/null +++ b/src/fragments/lib-v1/push-notifications/react-native/request_permissions/sample-permissions-flow.mdx @@ -0,0 +1,29 @@ + + Remember, if you use TypeScript for your development, you can use the + PushNotificationPermissionStatus enum for comparison as well! + + +```js +async function handlePermissions() { + const status = await Notifications.Push.getPermissionStatus(); + if (status === 'GRANTED') { + // no further action is required, user has already granted permissions + return; + } + if (status === 'DENIED') { + // further attempts to request permissions will no longer do anything + myFunctionToGracefullyDegradeMyApp(); + return; + } + if (status === 'SHOULD_REQUEST') { + // go ahead and request permissions from the user + await Notifications.Push.requestPermissions(); + } + if (status === 'SHOULD_EXPLAIN_THEN_REQUEST') { + // you should display some explanation to your user before requesting permissions + await myFunctionExplainingPermissionsRequest(); + // then request permissions + await Notifications.Push.requestPermissions(); + } +} +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx index 8030af50937..d9392cae89f 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/app-badge-count/index.mdx @@ -26,10 +26,6 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import appBadgeCount from '/src/fragments/lib-v1/push-notifications/react-native/app_badge_count/app-badge-count.mdx'; +import appBadgeCount from '/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx'; - - -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/app-badge-count.mdx'; - - + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx index 362441b4753..4504792ef18 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/enable-rich-notifications/index.mdx @@ -26,10 +26,6 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import enableRichNotifications from '/src/fragments/lib-v1/push-notifications/react-native/enable_rich_notifications/enable-rich-notifications.mdx'; +import enableRichNotifications from '/src/fragments/lib-v1/push-notifications/common/enable-rich-notifications.mdx'; - - -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/enable-rich-notifications.mdx'; - - + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx index b6bcfae599a..a9214feb6ac 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/identify-user/index.mdx @@ -26,14 +26,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import identifyUser from '/src/fragments/lib-v1/push-notifications/react-native/identify_user/identify-user.mdx'; +import identifyUser from '/src/fragments/lib-v1/push-notifications/common/identify-user.mdx'; - + -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/identify-user.mdx'; - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx index 67a3ef21677..a7141b86513 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/interact-with-notifications/index.mdx @@ -26,10 +26,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import interactWithNotifications from '/src/fragments/lib-v1/push-notifications/react-native/interact_with_notifications/interact-with-notifications.mdx'; +import interactWithNotifications from '/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx'; - + -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/interact_with_notifications/interact-with-notifications.mdx'; - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx index e936c035119..3febdb8e73c 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/receive-device-token/index.mdx @@ -26,10 +26,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import receiveDeviceToken from '/src/fragments/lib-v1/push-notifications/react-native/receive-device-token.mdx'; +import receiveDeviceToken from '/src/fragments/lib-v1/push-notifications/common/receive-device-token.mdx'; - + -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/receive-device-token.mdx'; - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx index 5c75784f9ef..96f828012c3 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/request-permissions/index.mdx @@ -26,10 +26,6 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import requestPermissions from '/src/fragments/lib-v1/push-notifications/react-native/request-permissions.mdx'; +import requestPermissions from '/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx'; - - -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/request-permissions.mdx'; - - + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx index e3ea256651c..38405e3352a 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-notifications/index.mdx @@ -26,14 +26,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import gettingStarted from '/src/fragments/lib-v1/push-notifications/react-native/getting_started/getting-started.mdx'; +import gettingStarted from '/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx'; - + -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx'; - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx index 687b586db07..47ea4590383 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/index.mdx @@ -27,14 +27,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import crossPlatformSetupService from '/src/fragments/lib/push-notifications/common/setup_push_service/cross-platform-setup.mdx'; +import crossPlatformSetupService from '/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx'; - + -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/setup_push_service/setup-push-service.mdx'; - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx index 07fdf7aff34..94612631f17 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/push-notifications/test-notifications/index.mdx @@ -26,14 +26,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; -import testing from '/src/fragments/lib/push-notifications/common/testing.mdx'; +import testing from '/src/fragments/lib-v1/push-notifications/common/testing.mdx'; - + -import commonRoute from '/src/fragments/lib-v1/push-notifications/common/testing.mdx'; - - From 03a0c6563682260816c3a549560677db19f3df91 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Mon, 13 May 2024 18:18:54 -0400 Subject: [PATCH 55/88] chore: update analytics enable example --- .../lib-v1/analytics/flutter/enable-disable/enable.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/lib-v1/analytics/flutter/enable-disable/enable.mdx b/src/fragments/lib-v1/analytics/flutter/enable-disable/enable.mdx index 6eb1405d78f..b4bea5bebd0 100644 --- a/src/fragments/lib-v1/analytics/flutter/enable-disable/enable.mdx +++ b/src/fragments/lib-v1/analytics/flutter/enable-disable/enable.mdx @@ -1,3 +1,3 @@ ```dart -Amplify.Analytics.enable(); +await Amplify.Analytics.enable(); ``` From 31a7cfcaa08764239baf0ea89b7f4f62c6e7d75a Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Mon, 13 May 2024 18:28:37 -0400 Subject: [PATCH 56/88] chore: remove references to min flutter version --- .../lib-v1/analytics/flutter/getting-started/10_preReq.mdx | 4 +--- .../lib-v1/auth/flutter/getting_started/10_preReq.mdx | 2 -- .../lib-v1/datastore/flutter/getting-started/10_preReq.mdx | 1 - .../lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx | 3 +-- .../lib-v1/restapi/flutter/getting-started/10_preReq.mdx | 3 +-- .../lib-v1/storage/flutter/getting-started/10_preReq.mdx | 3 +-- 6 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx index 167ff29e0f0..3f7febfd2b4 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx @@ -1,6 +1,4 @@ -- A Flutter application targeting Flutter SDK >=3.3.0 with Amplify libraries integrated - - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: - An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx index a679116b9c0..c961d8b8d05 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/10_preReq.mdx @@ -1,3 +1 @@ -A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated. - Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/prev/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx index 25eaf32221e..8710866de4b 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx @@ -1,6 +1,5 @@ - [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) -- A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - An iOS configuration targeting at least iOS 13.0 - An Android configuration targeting at least Android API level 24 (Android 7.0) or above - For a full example of please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) diff --git a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx index 5c43efbc7e6..ded735d6f00 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx @@ -1,7 +1,6 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) -* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 * An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx index eb0c1c4160c..2898b280a8a 100644 --- a/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx @@ -1,7 +1,6 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) -* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 * An Android configuration targeting at least Android API level 24 (Android 7.0) or above diff --git a/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx index 9338778fc30..234c7fdac2c 100644 --- a/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx @@ -1,6 +1,5 @@ -* A Flutter application targeting Flutter SDK >= 3.3.0 with Amplify libraries integrated - The following are also required, depending on which platforms you are targeting: + The following are required, depending on which platforms you are targeting: * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 * An Android configuration targeting at least Android API level 24 (Android 7.0) or above From b1fa2e3113d38a682fd67e971a47c5ee5db3dd61 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 14 May 2024 06:39:13 -0700 Subject: [PATCH 57/88] chore: fix path typos --- .../push-notifications/android/getting_started/fcm-pre-req.mdx | 2 +- .../flutter/identify_user/10_get_auth_user.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx b/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx index b562aafc0b2..807758da472 100644 --- a/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx +++ b/src/fragments/lib-v1/push-notifications/android/getting_started/fcm-pre-req.mdx @@ -1 +1 @@ -Push Notifications are delivered via Firebase Cloud Messaging (FCM). In order to use FCM, you need to register your app on the Firebase console. See [Setting up push notification services](/gen1/[platform/prev/build-a-backend/push-notifications/set-up-push-service/) for more information. +Push Notifications are delivered via Firebase Cloud Messaging (FCM). In order to use FCM, you need to register your app on the Firebase console. See [Setting up push notification services](/gen1/[platform]/prev/build-a-backend/push-notifications/set-up-push-service/) for more information. diff --git a/src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx b/src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx index 75fd531ef97..5888548398c 100644 --- a/src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx +++ b/src/fragments/lib-v1/push-notifications/flutter/identify_user/10_get_auth_user.mdx @@ -1,4 +1,4 @@ -If the user is signed in through [Amplify.Auth.signIn](/gen1prev/build-a-backend/auth/enable-sign-in/), then you can retrieve the current user's ID as shown below: +If the user is signed in through [Amplify.Auth.signIn](/gen1/flutter/prev/build-a-backend/auth/enable-sign-in/), then you can retrieve the current user's ID as shown below: ```dart final user = await Amplify.Auth.getCurrentUser(); From 2db33c070cf93c6043eab688a4d61d6c1f5c84d7 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 14 May 2024 06:53:30 -0700 Subject: [PATCH 58/88] chore: change existing resources link to the correct page --- .../auth/flutter/signin_with_custom_flow/10_cli_setup.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx index 308288ce93d..976db3220e8 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx @@ -212,4 +212,4 @@ Once finished, run `amplify push` to publish your changes. The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). -If you have already configured custom auth without the aid of the Amplify CLI, you can use the custom auth flow by changing the `authenticationFlowType` value in your [Amplify configuration](/gen1/[platform]/prev/build-a-backend/auth/existing-resources-no-cli/) to `CUSTOM_AUTH`. +If you have already configured custom auth without the aid of the Amplify CLI, you can use the custom auth flow by changing the `authenticationFlowType` value in your [Amplify configuration](/gen1/[platform]/prev/build-a-backend/auth/existing-resources/) to `CUSTOM_AUTH`. From fb552cabf495b8f657b39124586389e7aa57f256 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 14 May 2024 06:53:58 -0700 Subject: [PATCH 59/88] chore: move set up cli link to current since it doesn't exist on prev --- .../common/getting_started/getting-started.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx index 830a0163b2f..da8e0de1ea3 100644 --- a/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx +++ b/src/fragments/lib-v1/push-notifications/common/getting_started/getting-started.mdx @@ -22,7 +22,7 @@ To use Push Notifications with Amplify, you have the option to either have the A -> Prerequisite: [Install and configure the Amplify CLI](/gen1/[platform]/prev/tools/cli/start/set-up-cli/) +> Prerequisite: [Install and configure the Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) From 9f495fc31038f6c64386ac4c177d357c7b4db4de Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 14 May 2024 07:57:01 -0700 Subject: [PATCH 60/88] chore: add flutter back into platforms export in the under the hood and existing platform pages --- .../prev/build-a-backend/auth/existing-resources/index.mdx | 2 +- .../prev/build-a-backend/auth/under-the-hood/index.mdx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx index b0848fa20c4..73af5f8f12a 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx @@ -4,7 +4,7 @@ export const meta = { title: 'Use existing Amazon Cognito resources', description: 'Configure the Amplify Libraries to use existing Amazon Cognito resources by referencing them in your configuration.', - platforms: ['swift', 'android'] + platforms: ['swift', 'android', 'flutter'] }; export const getStaticPaths = async () => { diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/under-the-hood/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/under-the-hood/index.mdx index 70b76a13b90..d5f55550538 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/under-the-hood/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/under-the-hood/index.mdx @@ -7,6 +7,7 @@ export const meta = { 'javascript', 'android', 'swift', + 'flutter', 'react-native', 'angular', 'nextjs', From 7c799b650de53a32773b21db3650db9307ba8d29 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 15 May 2024 12:10:22 -0700 Subject: [PATCH 61/88] =?UTF-8?q?chore:=20addressed=20comments=20regarding?= =?UTF-8?q?=20removing=20split=20up=20context=20between=E2=80=A6=20(#7585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: addressed comments regarding removing split up context between platforms, changing headers, removing unnecessary fragments --- src/directory/directory.mjs | 3 -- .../getting-started/30_initAnalytics.mdx | 2 +- .../auth/common/device_features/common.mdx | 8 --- .../lib-v1/auth/common/sms/flows.mdx | 4 +- .../10_fetchAuthSession.mdx | 2 - .../device_features/20_forgetDevice.mdx | 4 +- .../getting_started/70_configureBackend.mdx | 8 --- .../flutter/signin_web_ui/10_cli_setup.mdx | 2 +- .../20_platform_specific_setup.mdx | 8 +-- .../signin_with_custom_flow/30_signin.mdx | 2 +- .../access_credentials/common.mdx | 2 - .../auth/native_common/signout/common.mdx | 36 ++----------- .../datastore/native_common/real-time.mdx | 2 - .../lib-v1/graphqlapi/existing-resources.mdx | 9 ---- .../lib-v1/graphqlapi/flutter/authz.mdx | 8 +-- .../graphqlapi/flutter/authz/21_oidc.mdx | 2 +- .../graphqlapi/flutter/authz/22_lambda.mdx | 2 +- .../advanced-workflows/common.mdx | 18 +------ .../native_common/getting-started/common.mdx | 2 - .../lib-v1/restapi/flutter/delete.mdx | 4 +- .../getting-started/30_initAnalytics.mdx | 2 +- .../20_platform_specific_setup.mdx | 8 +-- src/fragments/lib/flutter.mdx | 2 +- .../device_tracking/10_tracking_options.mdx | 4 +- .../flutter/platform-setup/macos.mdx | 2 +- .../analytics/set-up-analytics/index.mdx | 2 +- .../auth/existing-resources/index.mdx | 2 +- .../build-a-backend/auth/manage-mfa/index.mdx | 4 +- .../graphqlapi/advanced-workflows/index.mdx | 2 +- .../customize-authorization-modes/index.mdx | 53 ------------------- .../customize-authz-modes/index.mdx | 1 + 31 files changed, 39 insertions(+), 171 deletions(-) delete mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 1cba4a93edf..d2c9274d31f 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -2233,9 +2233,6 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/api-graphql-concepts/index.mdx' }, - { - path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx' - }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx' }, diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx index 282b63bbd91..62b1ed03765 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx @@ -18,7 +18,7 @@ Future _configureAmplify() async { -When running your app on MacOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/prev/start/project-setup/platform-setup/#enable-keychain). +When running your app on macOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/prev/start/project-setup/platform-setup/#enable-keychain). diff --git a/src/fragments/lib-v1/auth/common/device_features/common.mdx b/src/fragments/lib-v1/auth/common/device_features/common.mdx index be89a7351b8..7cecf6a0179 100644 --- a/src/fragments/lib-v1/auth/common/device_features/common.mdx +++ b/src/fragments/lib-v1/auth/common/device_features/common.mdx @@ -1,21 +1,13 @@ - - -The [device tracking and remembering](https://aws.amazon.com/blogs/mobile/tracking-and-remembering-devices-using-amazon-cognito-your-user-pools/) features are currently not available within the library when using the federated OAuth flow with Cognito User Pools or Hosted UI. - - - Remembering a device is useful in conjunction with Multi-Factor Authentication (MFA). If MFA is enabled for an Amazon Cognito user pool, end users have to type in a security code received via e-mail or SMS each time they want to sign in. This increases security but comes at the expense of the user's experience. Remembering a device allows the second factor requirement to be automatically met when the user signs in on that device, thereby reducing friction in the user experience. ## Configure Auth Category - Device remembering functionality does not work if you use one of the web UI sign in methods. - To enable remembered device functionality, open the Cognito User Pool console. To do this, **go to your project directory** and **issue the command**: diff --git a/src/fragments/lib-v1/auth/common/sms/flows.mdx b/src/fragments/lib-v1/auth/common/sms/flows.mdx index d91a800a4c5..7e35bf24ecf 100644 --- a/src/fragments/lib-v1/auth/common/sms/flows.mdx +++ b/src/fragments/lib-v1/auth/common/sms/flows.mdx @@ -84,7 +84,7 @@ import all1 from "/src/fragments/lib-v1/auth/common/sms/add_verification.mdx"; -### SMS MFA +### MFA import all2 from "/src/fragments/lib-v1/auth/common/sms/add_mfa.mdx"; @@ -108,7 +108,7 @@ import all4 from "/src/fragments/lib-v1/auth/common/sms/update_verification.mdx" -### SMS MFA +### MFA import all5 from "/src/fragments/lib-v1/auth/common/sms/update_mfa.mdx"; diff --git a/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx b/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx index 34434da6f25..882afe49c7c 100644 --- a/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx +++ b/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx @@ -1,5 +1,3 @@ -However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by calling fetchAuthSession on the Cognito Auth Plugin. This will return a `CognitoAuthSession`, which has additional attributes compared to `AuthSession`, which is typically returned by fetchAuthSession. See the example below: - ```dart Future fetchAuthSession() async { try { diff --git a/src/fragments/lib-v1/auth/flutter/device_features/20_forgetDevice.mdx b/src/fragments/lib-v1/auth/flutter/device_features/20_forgetDevice.mdx index be260d1d20f..253fb2c1ed4 100644 --- a/src/fragments/lib-v1/auth/flutter/device_features/20_forgetDevice.mdx +++ b/src/fragments/lib-v1/auth/flutter/device_features/20_forgetDevice.mdx @@ -18,9 +18,9 @@ Future forgetCurrentDevice() async { ```dart // A device that was fetched via Amplify.Auth.fetchDevices() -Future forgetSpecificDevice(AuthDevice myDevice) async { +Future forgetSpecificDevice(AuthDevice registeredDevice) async { try { - await Amplify.Auth.forgetDevice(myDevice); + await Amplify.Auth.forgetDevice(registeredDevice); safePrint('Forget device succeeded'); } on AuthException catch (e) { safePrint('Forget device failed with error: $e'); diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx index cf7c8988832..c2c99e7707f 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx @@ -28,14 +28,6 @@ To push your changes to the cloud, **execute the command**: amplify push ``` -import ios3 from '/src/fragments/lib/auth/ios/getting_started/12_amplifyConfig.mdx'; - - - -import android4 from '/src/fragments/lib/auth/android/getting_started/12_amplifyConfig.mdx'; - - - import flutter5 from '/src/fragments/lib/auth/flutter/getting_started/12_amplifyConfig.mdx'; diff --git a/src/fragments/lib-v1/auth/flutter/signin_web_ui/10_cli_setup.mdx b/src/fragments/lib-v1/auth/flutter/signin_web_ui/10_cli_setup.mdx index 7611f0f2d31..d82b75a9ac0 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_web_ui/10_cli_setup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_web_ui/10_cli_setup.mdx @@ -20,7 +20,7 @@ In terminal, navigate to your project, run `amplify add auth` (or if you've alre ? Enter your redirect signout URI: `myapp://` ? Do you want to add another redirect signout URI - `No` + `Yes` ? Enter your redirect signout URI: `http://localhost:3000/` ? Do you want to add another redirect signout URI diff --git a/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx b/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx index 8a9b656f5db..5ad4064fde8 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx @@ -4,11 +4,11 @@ Sign-in with web UI will display the sign-in UI inside a webview. After the sign ## Platform Setup -

Web

+### Web To use Hosted UI in your Flutter web application locally, you must run the app with the `--web-port=3000` argument (with the value being whichever port you assigned to localhost host when configuring your redirect URIs). -

Android

+### Android Add the following `queries` element to the `AndroidManifest.xml` file in your app's `android/app/src/main` directory, as well as the following `intent-filter` to the `MainActivity` in the same file. @@ -36,12 +36,12 @@ Replace `myapp` with your redirect URI scheme as necessary: ``` -

macOS

+### macOS Open XCode and enable the App Sandbox capability and then select "Incoming Connections (Server)" under "Network". ![Incoming Connections setting selected in the App Sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) -

iOS, Windows and Linux

+### iOS, Windows and Linux No specific platform configuration is required. diff --git a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/30_signin.mdx b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/30_signin.mdx index fa336a21b6d..46a9d82aad3 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/30_signin.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/30_signin.mdx @@ -1,6 +1,6 @@ ```dart // Create state variables for the sign in status -var isSignedIn = false; +bool isSignedIn = false; String? challengeHint; Future signInCustomFlow(String username) async { diff --git a/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx b/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx index 8182803b1e9..dd5f85a26df 100644 --- a/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx @@ -2,9 +2,7 @@ An intentional decision with Amplify Auth was to avoid any public methods exposi With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. - However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by casting the result of fetchAuthSession as follows: - import android0 from '/src/fragments/lib-v1/auth/android/access_credentials/10_fetchAuthSession.mdx'; diff --git a/src/fragments/lib-v1/auth/native_common/signout/common.mdx b/src/fragments/lib-v1/auth/native_common/signout/common.mdx index f20aa7668f5..6c64e468271 100644 --- a/src/fragments/lib-v1/auth/native_common/signout/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signout/common.mdx @@ -1,10 +1,4 @@ - Invoke the `signOut` api to sign out a user from the Auth category. You can only have one user signed in at a given time. - - - -Invoke the `signOut` api to sign out a user from the Auth category. You can only have one user signed in at a given time. Calling signOut without any options will delete the local cache and keychain of the user and revoke the token if enabled on Amazon Cognito User Pools. If you would like to sign out of all devices, invoke the signOut api with advanced options. - import android0 from '/src/fragments/lib-v1/auth/android/signout/10_local_signout.mdx'; @@ -18,35 +12,15 @@ import flutter2 from '/src/fragments/lib-v1/auth/flutter/signout/10_local_signou - Calling signOut without any options will just delete the local cache and keychain of the user. If you would like to sign out of all devices, invoke the signOut api with advanced options. [Amazon Cognito now supports token revocation](https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-cognito-now-supports-targeted-sign-out-through-refresh-token-revocation/) and the latest Amplify version will revoke Amazon Cognito tokens if the application is online. This means that the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. -Access and Id Tokens are short-lived (60 minutes by default but can be set from 5 minutes to 1 day). After revocation, these tokens cannot be used with Cognito User Pools anymore. However, they are still valid when used with other services like AppSync or API Gateway. - -For limiting subsequent calls to these other services after invalidating tokens, we recommend lowering token expiration time for your app client in the Cognito User Pools console. If you are using the Amplify CLI this can be accessed by running `amplify console auth`. - -Token revocation is enabled automatically on new Amazon Cognito User Pools, however existing User Pools must enable this feature, [using the Cognito Console or AWS CLI](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html). - - - -## Token Revocation - -[Amazon Cognito now supports token revocation](https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-cognito-now-supports-targeted-sign-out-through-refresh-token-revocation/). This means that the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. - -Access and Id Tokens are short-lived (60 minutes by default but can be set from 5 minutes to 1 day). After revocation, these tokens cannot be used with Cognito User Pools anymore. However, they are still valid when used with other services like AppSync or API Gateway. - -For limiting subsequent calls to these other services after invalidating tokens, we recommend lowering token expiration time for your app client in the Cognito User Pools console. If you are using the Amplify CLI this can be accessed by running `amplify console auth`. - -Token revocation is enabled automatically on new Amazon Cognito User Pools, however existing User Pools must enable this feature, [using the Cognito Console or AWS CLI](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html). - +Access and Id Tokens are short-lived (60 minutes by default but can be set from 5 minutes to 1 day). After revocation, these tokens cannot be used with Cognito user pool anymore. However, they are still valid when used with other services like AppSync or API Gateway. - -## Global Sign Out +For limiting subsequent calls to these other services after invalidating tokens, we recommend lowering token expiration time for your app client in the Cognito user pool console. If you are using the Amplify CLI this can be accessed by running `amplify console auth`. -Calling signout with `globalSignOut = true` will invalidate all the Cognito User Pool tokens of the signed in user. If the user is signed into a device, they won't be authorized to perform a task that requires a valid token when a global signout is called from some other device. They need to sign in again to get valid tokens. - +Token revocation is enabled automatically on new Amazon Cognito user pools, however existing user pools must enable this feature, [using the Cognito Console or AWS CLI](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html). import android3 from '/src/fragments/lib-v1/auth/android/signout/20_global_signout.mdx'; @@ -60,11 +34,9 @@ import flutter5 from '/src/fragments/lib-v1/auth/flutter/signout/20_global_signo - -Calling signout with `globalSignOut = true` will invalidate all the Cognito User Pool tokens of the signed in user. If the user is signed into a device, they won't be authorized to perform a task that requires a valid token when a global signout is called from some other device. They need to sign in again to get valid tokens. +Calling signout with `globalSignOut = true` will invalidate all the Cognito user pool tokens of the signed in user. If the user is signed into a device, they won't be authorized to perform a task that requires a valid token when a global signout is called from some other device. They need to sign in again to get valid tokens. Global signout functionality does not work if you use one of the web UI sign in methods. - diff --git a/src/fragments/lib-v1/datastore/native_common/real-time.mdx b/src/fragments/lib-v1/datastore/native_common/real-time.mdx index 26112dbea33..6cb54ebba04 100644 --- a/src/fragments/lib-v1/datastore/native_common/real-time.mdx +++ b/src/fragments/lib-v1/datastore/native_common/real-time.mdx @@ -2,7 +2,6 @@ You can subscribe to changes on your Models. This reacts dynamically to updates of data to the underlying Storage Engine, which could be the result of GraphQL Subscriptions as well as Queries or Mutations that run against the backing AppSync API if you are synchronizing with the cloud. - **NOTE:** AWS AppSync has an [adjustable limit of 100 subscriptions per connection](https://docs.aws.amazon.com/general/latest/gr/appsync.html). DataStore automatically subscribes to create, update, and delete mutations for all models. @@ -12,7 +11,6 @@ This means that GraphQL APIs with DataStore enabled are limited to 33 models and However, You can [request a service limit increase](https://console.aws.amazon.com/servicequotas/home/services/appsync/quotas/L-AA33EB36) from AWS AppSync to meet the real-time requirements of your application. - import js0 from '/src/fragments/lib-v1/datastore/js/real-time/observe-snippet.mdx'; diff --git a/src/fragments/lib-v1/graphqlapi/existing-resources.mdx b/src/fragments/lib-v1/graphqlapi/existing-resources.mdx index ec626f0a5d4..6b0ac176068 100644 --- a/src/fragments/lib-v1/graphqlapi/existing-resources.mdx +++ b/src/fragments/lib-v1/graphqlapi/existing-resources.mdx @@ -18,18 +18,9 @@ Existing AWS AppSync resources can be used with the Amplify Libraries by referen } ``` - - **API NAME**: Friendly name for the API (e.g., _api_) - **endpoint**: The HTTPS endpoint of the AWS AppSync API (e.g. `https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql`). [Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. `https://api.yourdomain.com/graphql`). Custom domain names can have any format, but must end with `/graphql` (see https://graphql.org/learn/serving-over-http/#uris-routes). - **region**: AWS Region where the resources are provisioned (e.g. _us-east-1_) - **authorizationType**: Authorization mode for accessing the API. This can be one of: `AMAZON_COGNITO_USER_POOLS`, `AWS_IAM`, `OPENID_CONNECT`, or `API_KEY`. Each mode requires additional configuration parameters. See [Configure authorization modes](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes) for details. - - - -- **API NAME**: Friendly name for the API (e.g., _api_) - - **endpoint**: The HTTPS endpoint of the AWS AppSync API (e.g. `https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql`). [Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. `https://api.yourdomain.com/graphql`). Custom domain names can have any format, but must end with `/graphql` (see https://graphql.org/learn/serving-over-http/#uris-routes). - - **region**: AWS Region where the resources are provisioned (e.g. _us-east-1_) - - **authorizationType**: Authorization mode for accessing the API. This can be one of: `AMAZON_COGNITO_USER_POOLS`, `AWS_IAM`, `OPENID_CONNECT`, or `API_KEY`. Each mode requires additional configuration parameters. See [Configure authorization modes](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes) for details. - Note that before you can add an AWS resource to your application, the application must have the Amplify libraries installed. If you need to perform this step, see [Install Amplify Libraries](/gen1/[platform]/prev/start/project-setup/create-application/#n2-install-amplify-libraries). diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz.mdx index f7e505dc2ba..4643acd1a8a 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz.mdx @@ -38,7 +38,7 @@ and under the `awsAPIPlugin` ``` -import flutter0 from "/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx"; +import flutter0 from "/src/fragments/lib-v1/graphqlapi/flutter/authz/10_userpool.mdx"; @@ -78,7 +78,7 @@ and under the `awsAPIPlugin` #### OIDC -import flutter1 from "/src/fragments/lib/graphqlapi/flutter/authz/20_oidc.mdx"; +import flutter1 from "/src/fragments/lib-v1/graphqlapi/flutter/authz/20_oidc.mdx"; @@ -108,7 +108,7 @@ The `friendly_name` illustrated here is created from Amplify CLI prompt. There a "authorizationType": "API_KEY", "apiKey": "[API_KEY]" }, - "[FRIENDLY-NAME-API-WITH-IAM": { + "[FRIENDLY-NAME-API-WITH-IAM"]: { "endpointType": "GraphQL", "endpoint": "[GRAPHQL-ENDPOINT]", "region": "[REGION]", @@ -134,6 +134,6 @@ The `friendly_name` illustrated here is created from Amplify CLI prompt. There a The `GRAPHQL-ENDPOINT` from AWS AppSync will look similar to `https://xyz.appsync-api.us-west-2.amazonaws.com/graphql`. -import flutter2 from "/src/fragments/lib/graphqlapi/flutter/authz/30_multi.mdx"; +import flutter2 from "/src/fragments/lib-v1/graphqlapi/flutter/authz/30_multi.mdx"; diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/21_oidc.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/21_oidc.mdx index 03dc4ca4b17..9576869702b 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/21_oidc.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/21_oidc.mdx @@ -15,6 +15,6 @@ class CustomOIDCProvider extends OIDCAuthProvider { } ``` -import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; +import warning from "/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx"; diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/22_lambda.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/22_lambda.mdx index cedc5024efe..6e5f0094abd 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/22_lambda.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/22_lambda.mdx @@ -9,6 +9,6 @@ class CustomFunctionProvider extends FunctionAuthProvider { } ``` -import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; +import warning from "/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx"; diff --git a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx index 34bec1704bf..ebb73108dd8 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx @@ -138,15 +138,9 @@ import flutter5 from '/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflow -## Combining multiple GraphQL operations in a single request +## Combining Multiple Operations - When you want to perform more than one operation in a single request, you can place them within the same document. For example, to retrieve a Post and a Todo - - - -GraphQL allows you to run multiple GraphQL operations (queries/mutations) as part of a single network request from the client code. To perform multiple operations in a single request, you can place them within the same GraphQL document. For example, to retrieve a Post and a Todo: - import ios6 from '/src/fragments/lib-v1/graphqlapi/ios/advanced-workflows/40_multiple.mdx'; @@ -160,25 +154,15 @@ import flutter7 from '/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflow - - - -Combining multiple GraphQL requests on the client-side is different than server-side transaction support. To run multiple transactions as a batch operation refer to the [Batch Put Custom Resolver](/gen1/[platform]/build-a-backend/graphqlapi/best-practice/batch-put-custom-resolver/) example. - - - - ## Adding Headers to Outgoing Requests By default, the API plugin includes appropriate authorization headers on your outgoing requests. However, you may have an advanced use case where you wish to send additional request headers to AppSync. - If your API does not require any authorization or if you would like manipulate the request yourself, please refer to the [Set authorization mode to NONE](/gen1/[platform]/build-a-backend/graphqlapi/customize-authz-modes/#none) - import ios8 from '/src/fragments/lib-v1/graphqlapi/ios/advanced-workflows/50_interceptor.mdx'; diff --git a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx index 5ced147f81d..5af5112b801 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx @@ -141,8 +141,6 @@ Congratulations! You've created a `Todo` object in your database. Check out the - [Update data](/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/) - [Subscribe to data](/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/) - [Concepts](/gen1/[platform]/prev/build-a-backend/graphqlapi/api-graphql-concepts/) - - [Configure authorization modes](/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes) - {/* TODO: * [Authorizing API calls with Cognito User Pool] */} diff --git a/src/fragments/lib-v1/restapi/flutter/delete.mdx b/src/fragments/lib-v1/restapi/flutter/delete.mdx index cb77bc8e9c1..98061be5ff8 100644 --- a/src/fragments/lib-v1/restapi/flutter/delete.mdx +++ b/src/fragments/lib-v1/restapi/flutter/delete.mdx @@ -5,9 +5,9 @@ Future deleteTodo() async { try { final restOperation = Amplify.API.delete('todo/1'); final response = await restOperation.response; - print('DELETE call succeeded: ${response.decodeBody()}'); + safePrint('DELETE call succeeded: ${response.decodeBody()}'); } on ApiException catch (e) { - print('DELETE call failed: $e'); + safePrint('DELETE call failed: $e'); } } ``` diff --git a/src/fragments/lib/analytics/flutter/getting-started/30_initAnalytics.mdx b/src/fragments/lib/analytics/flutter/getting-started/30_initAnalytics.mdx index 42ff7438720..5b4dfc96134 100644 --- a/src/fragments/lib/analytics/flutter/getting-started/30_initAnalytics.mdx +++ b/src/fragments/lib/analytics/flutter/getting-started/30_initAnalytics.mdx @@ -18,7 +18,7 @@ Future _configureAmplify() async { -When running your app on MacOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/start/project-setup/platform-setup/#enable-keychain). +When running your app on macOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/start/project-setup/platform-setup/#enable-keychain). diff --git a/src/fragments/lib/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx b/src/fragments/lib/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx index 8a9b656f5db..5ad4064fde8 100644 --- a/src/fragments/lib/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx +++ b/src/fragments/lib/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx @@ -4,11 +4,11 @@ Sign-in with web UI will display the sign-in UI inside a webview. After the sign ## Platform Setup -

Web

+### Web To use Hosted UI in your Flutter web application locally, you must run the app with the `--web-port=3000` argument (with the value being whichever port you assigned to localhost host when configuring your redirect URIs). -

Android

+### Android Add the following `queries` element to the `AndroidManifest.xml` file in your app's `android/app/src/main` directory, as well as the following `intent-filter` to the `MainActivity` in the same file. @@ -36,12 +36,12 @@ Replace `myapp` with your redirect URI scheme as necessary: ``` -

macOS

+### macOS Open XCode and enable the App Sandbox capability and then select "Incoming Connections (Server)" under "Network". ![Incoming Connections setting selected in the App Sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) -

iOS, Windows and Linux

+### iOS, Windows and Linux No specific platform configuration is required. diff --git a/src/fragments/lib/flutter.mdx b/src/fragments/lib/flutter.mdx index 7319007f8b2..0564e19914e 100644 --- a/src/fragments/lib/flutter.mdx +++ b/src/fragments/lib/flutter.mdx @@ -2,7 +2,7 @@ Welcome to the Amplify Flutter documentation. To stay up to date with the latest changes and provide feedback, please take a look at our [GitHub repo](https://github.com/aws-amplify/amplify-flutter) or join us on [Discord](https://discord.gg/jWVbPfC). -The stable release of Amplify Flutter currently supports iOS, Android, Web, Windows, MacOS, and Linux as target platforms. Currently Push Notifications and DataStore are supported for only iOS and Android. +The stable release of Amplify Flutter currently supports iOS, Android, Web, Windows, macOS, and Linux as target platforms. Currently Push Notifications and DataStore are supported for only iOS and Android. Get Started diff --git a/src/fragments/lib/geo/ios/device_tracking/10_tracking_options.mdx b/src/fragments/lib/geo/ios/device_tracking/10_tracking_options.mdx index 1afdf8b12ce..2e1a0ed6fbd 100644 --- a/src/fragments/lib/geo/ios/device_tracking/10_tracking_options.mdx +++ b/src/fragments/lib/geo/ios/device_tracking/10_tracking_options.mdx @@ -6,9 +6,9 @@ **`allowsBackgroundLocationUpdates`** | `false` | If true, location updates will be received from the OS when the app is in the background. **`pausesLocationUpdatesAutomatically`** | `true` | If true, allow the OS to pause location updates to optimize battery usages. **`activityType`** | `.automotive-` `Navigation` | If `pausesLocationUpdatesAutomatically` is true, the OS will decide appropriate times to pause location updates to improve battery life based on the `activityType`. -**`showsBackgroundLocationIndicator`** | `false` | If true and `requestAlwaysAuthorization` is true, the background location indicator is displayed and visible to the users. This option is not supported on MacOS. +**`showsBackgroundLocationIndicator`** | `false` | If true and `requestAlwaysAuthorization` is true, the background location indicator is displayed and visible to the users. This option is not supported on macOS. **`disregardLocationUpdatesWhenOffline`** | `false` | If true, the app will not store location updates when the app is offline. This is false by default; Amplify will store location updates locally due to loss of network connectivity and send location updates when app is online. **`wakeAppForSignificantLocationChanges`** | `false` | If true, the app will be woken up by significant location updates after an app has been force closed. In order to take advantage of this, you'll need to call `Amplify.Geo.startTracking()` in your apps launch lifecycle method. (e.g. `didFinishedLoading`) **`distanceFilter`** | `0` | If set, the minimum distance in meters at which the OS will update the app with a new location. **`trackUntil`** | `.distantFuture` | If set, the app will stop tracking when date is reached. By default, tracking will continue until user logOut or `stopTracking()` is called. -**`batchingOptions`** | `.none` | Custom defined behavior to send location updates in batches based on a specified threshold. \ No newline at end of file +**`batchingOptions`** | `.none` | Custom defined behavior to send location updates in batches based on a specified threshold. diff --git a/src/fragments/lib/project-setup/flutter/platform-setup/macos.mdx b/src/fragments/lib/project-setup/flutter/platform-setup/macos.mdx index 7656a345740..2e02deac857 100644 --- a/src/fragments/lib/project-setup/flutter/platform-setup/macos.mdx +++ b/src/fragments/lib/project-setup/flutter/platform-setup/macos.mdx @@ -14,7 +14,7 @@ Open your project in Xcode and select Runner, Targets -> Runner and then the "Ge Select Runner, Project -> Runner and then the "Info" tab. Update "macOS Deployment Target" to 10.15 or higher. -![Setting the macOS version to 10.15 or higher in the MacOS Deployment Target tab of the Runner info section.](/images/project-setup/flutter/mac/project-min-deployment-version.png) +![Setting the macOS version to 10.15 or higher in the macOS Deployment Target tab of the Runner info section.](/images/project-setup/flutter/mac/project-min-deployment-version.png) ### Enable Network Calls diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx index 78130859cd0..a50de0854cf 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx @@ -297,7 +297,7 @@ Future _configureAmplify() async { -When running your app on MacOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/start/project-setup/platform-setup/#enable-keychain). +When running your app on macOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/start/project-setup/platform-setup/#enable-keychain). diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx index 73af5f8f12a..b7bf6aa37e0 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/existing-resources/index.mdx @@ -4,7 +4,7 @@ export const meta = { title: 'Use existing Amazon Cognito resources', description: 'Configure the Amplify Libraries to use existing Amazon Cognito resources by referencing them in your configuration.', - platforms: ['swift', 'android', 'flutter'] + platforms: ['flutter', 'swift', 'android'] }; export const getStaticPaths = async () => { diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx index b855e9df6cf..aa180892631 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/auth/manage-mfa/index.mdx @@ -829,6 +829,6 @@ Now that you completed setting up multi-factor authentication you may also want -import flows from '/src/fragments/lib/auth/common/mfa/flows.mdx'; +import flows from '/src/fragments/lib-v1/auth/common/mfa/flows.mdx'; - + diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx index 0c42691fa7f..a630ae0f945 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/advanced-workflows/index.mdx @@ -1,7 +1,7 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { - title: 'Advanced Workflows', + title: 'Advanced workflows', description: "Learn more about advanced workflows in Amplify's API category", platforms: [ 'flutter', diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx deleted file mode 100644 index da491867f15..00000000000 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authorization-modes/index.mdx +++ /dev/null @@ -1,53 +0,0 @@ -import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; - -export const meta = { - title: 'Configure authorization modes', - description: - "Learn more about how to configure authorization modes in Amplify's API category", - platforms: ['flutter'] -}; - -export const getStaticPaths = async () => { - return getCustomStaticPath(meta.platforms); -}; - -export function getStaticProps(context) { - return { - props: { - platform: context.params.platform, - meta - } - }; -} - -import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; - - - -import ios0 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; - - - -import android1 from '/src/fragments/lib/graphqlapi/native_common/authz/common.mdx'; - - - -import js2 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; - - - -import reactnative0 from '/src/fragments/lib/graphqlapi/js/authz.mdx'; - - - -import flutter3 from '/src/fragments/lib-v1/graphqlapi/native_common/authz/common.mdx'; - - diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx index b395a0f5ca6..145cdc29265 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx @@ -8,6 +8,7 @@ export const meta = { 'react-native', 'swift', 'android', + 'flutter', 'angular', 'nextjs', 'react', From a8fa6183834cfdb5b8ce3bbcba8508043ffcbedc Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Thu, 16 May 2024 13:36:49 -0700 Subject: [PATCH 62/88] Flutter v2 gen2 comment fixes (#7598) * chore: remove callout that should be in migration guide * chore: remove unnecessary code snippet that originates from vCurrent * chore: fix incorrect references to vCurrent fragments inside vPrev pages * chore: remove unnecessary line for flutter custom challenge in both prev and current * chore: make function public in prev and current * chore: remove "suppports-only-mobile" * chore: remove unnecessary inline filters and put back an upload fragment * chore: remove unnecessary inline filter from graphql link * chore: remove unintentionally added line * chore: move flutter back to the top of the platforms list * chore: delete before you begin snippet for grapql vprev * chore: add picture back in to vprev flutter storage page * chore: revert flutter import changes back to original * chore: clean up preReq snippets * chore: add title="Terminal" showLineNumbers={false} to terminal snippets * chore: revert changes that are out of scope * chore: revert removal of inline filter and addition of white space * chore: revert changes that removed before you begin snippet * chore: revert change to wording --- .../flutter/getting-started/10_preReq.mdx | 10 ++-------- .../lib-v1/analytics/flutter/identifyuser.mdx | 13 ------------- src/fragments/lib-v1/auth/common/sms/flows.mdx | 2 +- .../getting_started/70_configureBackend.mdx | 2 +- .../40_custom_challenge.mdx | 2 -- .../user_attributes/50_custom_attributes.mdx | 4 ++-- .../flutter/getting-started/20_installLib.mdx | 2 +- .../flutter/getting-started/10_preReq.mdx | 10 +--------- .../native_common/getting-started/common.mdx | 4 ++-- .../restapi/flutter/getting-started/10_preReq.mdx | 10 +--------- .../storage/flutter/getting-started/10_preReq.mdx | 11 ++--------- src/fragments/lib-v1/storage/flutter/upload.mdx | 14 -------------- .../native_common/getting-started/common.mdx | 12 ++++++------ .../flutter/getting-started/10_preReq.mdx | 10 ++-------- .../lib/auth/flutter/getting_started/10_preReq.mdx | 4 +++- .../40_custom_challenge.mdx | 2 -- .../user_attributes/50_custom_attributes.mdx | 4 ++-- .../flutter/getting-started/20_installLib.mdx | 2 +- .../flutter/getting-started/10_preReq.mdx | 10 +--------- .../native_common/getting-started/common.mdx | 2 -- .../restapi/flutter/getting-started/10_preReq.mdx | 10 +--------- .../storage/flutter/getting-started/10_preReq.mdx | 10 ++-------- src/fragments/lib/storage/flutter/list.mdx | 1 - .../analytics/set-up-analytics/index.mdx | 10 ++-------- .../storage/set-up-storage/index.mdx | 12 +++--------- .../graphqlapi/customize-authz-modes/index.mdx | 2 +- .../prev/build-a-backend/storage/index.mdx | 11 ----------- 27 files changed, 37 insertions(+), 149 deletions(-) diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx index 3f7febfd2b4..16c98fc9109 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/10_preReq.mdx @@ -1,9 +1,3 @@ - The following are required, depending on which platforms you are targeting: +* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - - An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - - An Android configuration targeting at least Android API level 24 (Android 7.0) or above - - Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - - Any Windows OS meeting Flutter minimums - - macOS version 10.15 or higher - - Any Ubuntu Linux distribution meeting Flutter minimums - - For a full example please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/prev/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx b/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx index 63d0e75bda2..f43af9dc852 100644 --- a/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx +++ b/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx @@ -4,15 +4,6 @@ You can get the current user's ID from the Amplify Auth category as shown per th If you have asked for location access and received permission, you can also provide that in `UserProfileLocation` - - -Breaking changes from v0 to v1: - -The Analytics- prefix of the original `AnalyticsUserProfile` and `AnalyticsUserProfileLocation` classes is removed. Furthermore, `AnalyticsProperties` is renamed to `CustomProperties`. - - - - ```dart Future addAnalyticsWithLocation({ required String userId, @@ -43,7 +34,3 @@ Future addAnalyticsWithLocation({ ); } ``` - -import flutter0 from "/src/fragments/lib/analytics/native_common/identify-use-cases.mdx"; - - diff --git a/src/fragments/lib-v1/auth/common/sms/flows.mdx b/src/fragments/lib-v1/auth/common/sms/flows.mdx index 7e35bf24ecf..ecc8ee7feac 100644 --- a/src/fragments/lib-v1/auth/common/sms/flows.mdx +++ b/src/fragments/lib-v1/auth/common/sms/flows.mdx @@ -156,4 +156,4 @@ If MFA is **ON** or enabled for the user, you must call `confirmSignIn` with the import flutter9 from "/src/fragments/lib-v1/auth/flutter/sms/confirm_sign_in.mdx"; - + \ No newline at end of file diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx index c2c99e7707f..401c91aed23 100644 --- a/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx +++ b/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx @@ -1,7 +1,7 @@ -> Prerequisites: [Install and configure](/[platform]/start/project-setup/prerequisites/#install-and-configure-the-amplify-cli) the Amplify CLI in addition to the Amplify libraries and [necessary dependencies](/[platform]/build-a-backend/auth/set-up-auth/#install-amplify-libraries). +> Prerequisites: [Install and configure](/gen1/[platform]/prev/start/project-setup/prerequisites/#install-and-configure-the-amplify-cli) the Amplify CLI in addition to the Amplify libraries and [necessary dependencies](/gen1/[platform]/prev/build-a-backend/auth/set-up-auth/#install-amplify-libraries). To start provisioning auth resources in the backend, go to your project directory and **execute the command**: diff --git a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx index 25d5d2e5831..4bfbd82ef9f 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx @@ -19,6 +19,4 @@ Once the user provides the correct response, they should be authenticated in you Special Handling on ConfirmSignIn During a `confirmSignIn` call, if `failAuthentication: true` is returned by the Lambda, the session of the request gets invalidated by Cognito, and a `NotAuthorizedException` is thrown. To recover, the user must initiate a new sign in by calling `Amplify.Auth.signIn`. - -Exception: `NotAuthorizedException` with a message `Invalid session for the user.`
diff --git a/src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx b/src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx index db31a1933f5..28223df913b 100644 --- a/src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx +++ b/src/fragments/lib-v1/auth/flutter/user_attributes/50_custom_attributes.mdx @@ -2,7 +2,7 @@ Amplify Flutter supports [standard OIDC user attributes](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) as well as custom attributes. Custom attributes can be instantiated via the custom attribute constructor: ```dart -Future _signUp({ +Future signUp({ required String username, required String password, required String email, @@ -23,4 +23,4 @@ Future _signUp({ } ``` -When working with a Cognito UserPool, you can set up [custom attributes](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-custom-attributes) via the Cognito console or AWS CLI. Although Cognito prepends a "custom:" prefix on the attribute name, there is no need for you to add this in Amplify Flutter's custom attribute constructor. \ No newline at end of file +When working with a Cognito UserPool, you can set up [custom attributes](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-custom-attributes) via the Cognito console or AWS CLI. Although Cognito prepends a "custom:" prefix on the attribute name, there is no need for you to add this in Amplify Flutter's custom attribute constructor. diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx index 206bfaea652..f037d40c76f 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/20_installLib.mdx @@ -7,7 +7,7 @@ dependencies: flutter: sdk: flutter - amplify_datastore: ^1.0.0-supports-only-mobile + amplify_datastore: ^1.0.0 amplify_flutter: ^1.0.0 ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx index ded735d6f00..16c98fc9109 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/getting-started/10_preReq.mdx @@ -1,11 +1,3 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - - The following are required, depending on which platforms you are targeting: - * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - * An Android configuration targeting at least Android API level 24 (Android 7.0) or above - * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - * Any Windows OS meeting Flutter minimums - * macOS version 10.15 or higher - * Any Ubuntu Linux distribution meeting Flutter minimums - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/prev/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx index 5af5112b801..4f6f601aedb 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/getting-started/common.mdx @@ -83,9 +83,9 @@ import android8 from '/src/fragments/lib-v1/graphqlapi/android/getting-started/4 -import flutter18 from '/src/fragments/lib-v1/graphqlapi/flutter/getting-started/40_codegen.mdx'; +import flutter7 from '/src/fragments/lib-v1/graphqlapi/flutter/getting-started/40_codegen.mdx'; - + ## Install Amplify Libraries diff --git a/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx index 2898b280a8a..16c98fc9109 100644 --- a/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/restapi/flutter/getting-started/10_preReq.mdx @@ -1,11 +1,3 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - - The following are required, depending on which platforms you are targeting: - * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - * An Android configuration targeting at least Android API level 24 (Android 7.0) or above - * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - * Any Windows OS meeting Flutter minimums - * macOS version 10.15 or higher - * Any Ubuntu Linux distribution meeting Flutter minimums - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/prev/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx index 234c7fdac2c..16c98fc9109 100644 --- a/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/storage/flutter/getting-started/10_preReq.mdx @@ -1,10 +1,3 @@ +* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - The following are required, depending on which platforms you are targeting: - - * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - * An Android configuration targeting at least Android API level 24 (Android 7.0) or above - * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - * Any Windows OS meeting Flutter minimums - * macOS version 10.15 or higher - * Any Ubuntu Linux distribution meeting Flutter minimums - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/prev/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib-v1/storage/flutter/upload.mdx b/src/fragments/lib-v1/storage/flutter/upload.mdx index e185ed2b4a7..343d98e9fe9 100644 --- a/src/fragments/lib-v1/storage/flutter/upload.mdx +++ b/src/fragments/lib-v1/storage/flutter/upload.mdx @@ -1,22 +1,8 @@ - ## Upload File To upload to S3 from a file, specify the `key` to upload the file to and the `localFile` to be uploaded. `localFile` can be an instance of `AWSFile` created from either an OS platform `File` instance or the result of Flutter file picker plugins such as [file_picker](https://pub.dev/packages/file_picker). - - -## Upload File - -To upload to S3 from a file, specify the key and the local file to be uploaded. A file can be created locally, or retrieved from the user's device using a package such as [image_picker](https://pub.dev/packages/image_picker) or [file_picker](https://pub.dev/packages/file_picker). - - - -### Upload a local file - - - ### Upload platform `File` - diff --git a/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx b/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx index 1cbac772919..33d815c841f 100644 --- a/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx @@ -99,11 +99,9 @@ import flutter11 from '/src/fragments/lib-v1/storage/flutter/getting-started/30_ - ## Uploading data to your bucket To upload to S3 from a data object, specify the key and the data object to be uploaded. - import ios12 from '/src/fragments/lib-v1/storage/ios/getting-started/40_upload.mdx'; @@ -113,7 +111,10 @@ import android13 from '/src/fragments/lib-v1/storage/android/getting-started/40_ - +import flutter14 from '/src/fragments/lib-v1/storage/flutter/getting-started/40_upload.mdx'; + + + Upon successfully executing this code, you should see a new folder in your bucket, called `public`. It should contain a file called `ExampleKey`, whose contents is `Example file contents`. ## Next Steps @@ -126,8 +127,7 @@ Congratulations! You've uploaded a file to an s3 bucket. Check out the following - [Remove Files](/gen1/[platform]/prev/build-a-backend/storage/remove/) - [File Access Levels](/gen1/[platform]/prev/build-a-backend/storage/configure-access/) - [Using Lambda Triggers](/gen1/[platform]/prev/build-a-backend/storage/lambda-triggers/) - - - [Escape Hatch](/gen1/[platform]/prev/build-a-backend/storage/sdk/) - +- [Escape Hatch](/gen1/[platform]/prev/build-a-backend/storage/sdk/) + \ No newline at end of file diff --git a/src/fragments/lib/analytics/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/analytics/flutter/getting-started/10_preReq.mdx index 25b3facc2a9..1acefc397fd 100644 --- a/src/fragments/lib/analytics/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/analytics/flutter/getting-started/10_preReq.mdx @@ -1,9 +1,3 @@ - The following are required, depending on which platforms you are targeting: +* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - - An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - - An Android configuration targeting at least Android API level 24 (Android 7.0) or above - - Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - - Any Windows OS meeting Flutter minimums - - macOS version 10.15 or higher - - Any Ubuntu Linux distribution meeting Flutter minimums - - For a full example please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib/auth/flutter/getting_started/10_preReq.mdx b/src/fragments/lib/auth/flutter/getting_started/10_preReq.mdx index f5aea597eb7..1acefc397fd 100644 --- a/src/fragments/lib/auth/flutter/getting_started/10_preReq.mdx +++ b/src/fragments/lib/auth/flutter/getting_started/10_preReq.mdx @@ -1 +1,3 @@ -Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. +* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) + +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx b/src/fragments/lib/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx index 25d5d2e5831..4bfbd82ef9f 100644 --- a/src/fragments/lib/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx +++ b/src/fragments/lib/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx @@ -19,6 +19,4 @@ Once the user provides the correct response, they should be authenticated in you Special Handling on ConfirmSignIn During a `confirmSignIn` call, if `failAuthentication: true` is returned by the Lambda, the session of the request gets invalidated by Cognito, and a `NotAuthorizedException` is thrown. To recover, the user must initiate a new sign in by calling `Amplify.Auth.signIn`. - -Exception: `NotAuthorizedException` with a message `Invalid session for the user.` diff --git a/src/fragments/lib/auth/flutter/user_attributes/50_custom_attributes.mdx b/src/fragments/lib/auth/flutter/user_attributes/50_custom_attributes.mdx index db31a1933f5..28223df913b 100644 --- a/src/fragments/lib/auth/flutter/user_attributes/50_custom_attributes.mdx +++ b/src/fragments/lib/auth/flutter/user_attributes/50_custom_attributes.mdx @@ -2,7 +2,7 @@ Amplify Flutter supports [standard OIDC user attributes](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) as well as custom attributes. Custom attributes can be instantiated via the custom attribute constructor: ```dart -Future _signUp({ +Future signUp({ required String username, required String password, required String email, @@ -23,4 +23,4 @@ Future _signUp({ } ``` -When working with a Cognito UserPool, you can set up [custom attributes](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-custom-attributes) via the Cognito console or AWS CLI. Although Cognito prepends a "custom:" prefix on the attribute name, there is no need for you to add this in Amplify Flutter's custom attribute constructor. \ No newline at end of file +When working with a Cognito UserPool, you can set up [custom attributes](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-custom-attributes) via the Cognito console or AWS CLI. Although Cognito prepends a "custom:" prefix on the attribute name, there is no need for you to add this in Amplify Flutter's custom attribute constructor. diff --git a/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx b/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx index 08f767302e3..e10d4d2f91d 100644 --- a/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx +++ b/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx @@ -7,7 +7,7 @@ dependencies: flutter: sdk: flutter - amplify_datastore: 2.0.0-supports-only-mobile + amplify_datastore: 2.0.0 amplify_flutter: ^2.0.0 ``` diff --git a/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx index 2898b280a8a..1acefc397fd 100644 --- a/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx @@ -1,11 +1,3 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - - The following are required, depending on which platforms you are targeting: - * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - * An Android configuration targeting at least Android API level 24 (Android 7.0) or above - * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - * Any Windows OS meeting Flutter minimums - * macOS version 10.15 or higher - * Any Ubuntu Linux distribution meeting Flutter minimums - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx index fa7be102d72..873eff2a373 100644 --- a/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx +++ b/src/fragments/lib/graphqlapi/native_common/getting-started/common.mdx @@ -141,8 +141,6 @@ Congratulations! You've created a `Todo` object in your database. Check out the - [Update data](/gen1/[platform]/build-a-backend/graphqlapi/mutate-data/) - [Subscribe to data](/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/) - [Concepts](/gen1/[platform]/build-a-backend/graphqlapi/api-graphql-concepts/) - - [Configure authorization modes](/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules) - {/* TODO: * [Authorizing API calls with Cognito User Pool] */} diff --git a/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx index 2898b280a8a..1acefc397fd 100644 --- a/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx @@ -1,11 +1,3 @@ * [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - - The following are required, depending on which platforms you are targeting: - * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - * An Android configuration targeting at least Android API level 24 (Android 7.0) or above - * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - * Any Windows OS meeting Flutter minimums - * macOS version 10.15 or higher - * Any Ubuntu Linux distribution meeting Flutter minimums - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib/storage/flutter/getting-started/10_preReq.mdx b/src/fragments/lib/storage/flutter/getting-started/10_preReq.mdx index 094bbfa9d3d..1acefc397fd 100644 --- a/src/fragments/lib/storage/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib/storage/flutter/getting-started/10_preReq.mdx @@ -1,9 +1,3 @@ - The following are required, depending on which platforms you are targeting: +* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - * An Android configuration targeting at least Android API level 24 (Android 7.0) or above - * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - * Any Windows OS meeting Flutter minimums - * macOS version 10.15 or higher - * Any Ubuntu Linux distribution meeting Flutter minimums - * For a full example please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/project-setup/platform-setup/) guide for more details on platform specific setup. diff --git a/src/fragments/lib/storage/flutter/list.mdx b/src/fragments/lib/storage/flutter/list.mdx index 0d361e77f4a..149be23d0cb 100644 --- a/src/fragments/lib/storage/flutter/list.mdx +++ b/src/fragments/lib/storage/flutter/list.mdx @@ -54,4 +54,3 @@ Future listAll() async { } } ``` -import { delimiter } from "path" diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx index a50de0854cf..0789ac9b503 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx @@ -60,14 +60,8 @@ For more information on how to use the `visionos-preview` branch, see [Platform - The following are required, depending on which platforms you are targeting: - - - An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - - An Android configuration targeting at least Android API level 24 (Android 7.0) or above - - Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - - Any Windows OS meeting Flutter minimums - - macOS version 10.15 or higher - - Any Ubuntu Linux distribution meeting Flutter minimums +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/start/) guide for more details on platform specific setup. + diff --git a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx index 6477829867a..4cb0031afe7 100644 --- a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx @@ -410,15 +410,9 @@ Note that because the storage category requires auth, you will need to either co ### Prerequisites - The following are required, depending on which platforms you are targeting: - - * An iOS configuration targeting at least iOS 13.0 and XCode version >=13.2 - * An Android configuration targeting at least Android API level 24 (Android 7.0) or above - * Any browser supported by Flutter for Web (you can check the list of supported browsers [here](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter)) - * Any Windows OS meeting Flutter minimums - * macOS version 10.15 or higher - * Any Ubuntu Linux distribution meeting Flutter minimums - * For a full example please follow the [project setup walkthrough](/[platform]/start/quickstart/) +* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) + +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/) guide for more details on platform specific setup. ### Install Amplify library diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx index 145cdc29265..2cefbe378b8 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/customize-authz-modes/index.mdx @@ -4,11 +4,11 @@ export const meta = { title: 'Customize your auth rules', description: "Learn more about how to configure authorization modes in Amplify's API category", platforms: [ + 'flutter', 'javascript', 'react-native', 'swift', 'android', - 'flutter', 'angular', 'nextjs', 'react', diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/index.mdx index 55d9c346910..37b9a4b0a5b 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/index.mdx @@ -39,18 +39,7 @@ import flutter_maintenance from '/src/fragments/lib-v1/flutter-maintenance.mdx'; AWS Amplify storage module provides a simple mechanism for managing user content for your app in public, protected or private storage buckets. The storage category comes with built-in support for [Amazon S3 (Simple Storage Service)](https://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html). - ![Image](/images/s3_overview.jpg) - ## S3 Core Concepts From c7648b3f1cc09a05eee8313a69332a14cdb32845 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Thu, 16 May 2024 14:15:36 -0700 Subject: [PATCH 63/88] Apply suggestions from code review Co-authored-by: Jordan Nelson --- .../analytics/flutter/getting-started/30_initAnalytics.mdx | 2 +- .../lib-v1/datastore/flutter/sync/20-savePredicate.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx b/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx index 62b1ed03765..81521c53412 100644 --- a/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx +++ b/src/fragments/lib-v1/analytics/flutter/getting-started/30_initAnalytics.mdx @@ -22,7 +22,7 @@ When running your app on macOS you will need to enable keychain sharing in Xcode -Make sure that the amplifyconfiguration.dart file generated in the project setup is included and sent to Amplify.configure: +Make sure that the `amplifyconfiguration.dart` file generated in the project setup is included and sent to `Amplify.configure`: ```dart import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; diff --git a/src/fragments/lib-v1/datastore/flutter/sync/20-savePredicate.mdx b/src/fragments/lib-v1/datastore/flutter/sync/20-savePredicate.mdx index 6bc9700457e..87f79a62598 100644 --- a/src/fragments/lib-v1/datastore/flutter/sync/20-savePredicate.mdx +++ b/src/fragments/lib-v1/datastore/flutter/sync/20-savePredicate.mdx @@ -13,8 +13,8 @@ Future savePredicate(Post post) async { updatedPost, where: Post.TITLE.beginsWith("[Amplify]"), ); - } on DataStoreException { - safePrint('Could not update post, maybe the title has been changed?'); + } on DataStoreException catch (e) { + safePrint('Could not update post: $e'); } } ``` From b5a0829337c2b8a457e27ab009873f6e250e587b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Thu, 16 May 2024 14:31:50 -0700 Subject: [PATCH 64/88] chore: revert white space changes --- .../flutter/signin_web_ui/20_platform_specific_setup.mdx | 8 ++++---- .../flutter/data-access/observe-update-snippet.mdx | 2 +- .../lib-v1/datastore/flutter/datastore-events.mdx | 2 +- .../graphqlapi/flutter/advanced-workflows/20_custom.mdx | 8 ++++---- .../graphqlapi/flutter/advanced-workflows/30_nested.mdx | 8 ++++---- .../lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx | 7 +++---- src/fragments/lib-v1/restapi/flutter/fetch.mdx | 2 +- .../storage/native_common/getting-started/common.mdx | 2 +- 8 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx b/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx index 5ad4064fde8..4d35985db64 100644 --- a/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx +++ b/src/fragments/lib-v1/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx @@ -16,10 +16,10 @@ Replace `myapp` with your redirect URI scheme as necessary: ```xml - - - + + + ... diff --git a/src/fragments/lib-v1/datastore/flutter/data-access/observe-update-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/data-access/observe-update-snippet.mdx index a7954aa93f6..c09839f80f3 100644 --- a/src/fragments/lib-v1/datastore/flutter/data-access/observe-update-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/data-access/observe-update-snippet.mdx @@ -17,7 +17,7 @@ class _MyAppState extends State { super.initState(); _configure(); } - + // Initialize the Amplify libraries and call `observeQuery` Future _configure() async { // ... diff --git a/src/fragments/lib-v1/datastore/flutter/datastore-events.mdx b/src/fragments/lib-v1/datastore/flutter/datastore-events.mdx index 33ec3b44c20..d87727b807c 100644 --- a/src/fragments/lib-v1/datastore/flutter/datastore-events.mdx +++ b/src/fragments/lib-v1/datastore/flutter/datastore-events.mdx @@ -22,7 +22,7 @@ class _MyAppState extends State { void observeEvents() { hubSubscription = Amplify.Hub.listen(HubChannel.DataStore, (hubEvent) { if (hubEvent.eventName == 'networkStatus') { - final status = hubEvent.payload as NetworkStatusEvent?; + final status = hubEvent.payload as NetworkStatusEvent?; setState(() { networkIsUp = status?.active ?? false; }); diff --git a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/20_custom.mdx b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/20_custom.mdx index 0eaccb71b9e..ceadbaa8b2b 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/20_custom.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/20_custom.mdx @@ -7,10 +7,10 @@ const graphQLDocument = '''query GetTodo(\$id: ID!) { } }'''; final getTodoRequest = GraphQLRequest( - document: graphQLDocument, - modelType: Todo.classType, - variables: {'id': someTodoId}, - decodePath: getTodo, + document: graphQLDocument, + modelType: Todo.classType, + variables: {'id': someTodoId}, + decodePath: getTodo, ); ``` The `decodePath` specifies which part of the response to deserialize to the `modelType`. You'll need to specify the operation name (as `decodePath`) and `modelType` to deserialize the object at "data.getTodo" successfully into a `Todo` model. diff --git a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/30_nested.mdx b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/30_nested.mdx index 7693e6d9fdc..df65160c220 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/30_nested.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflows/30_nested.mdx @@ -16,10 +16,10 @@ const graphQLDocument = '''query GetPost(\$id: ID!) { } }'''; final getPostRequest = GraphQLRequest( - document: graphQLDocument, - modelType: Post.classType, - variables: {'id': somePostId}, - decodePath: getPost, + document: graphQLDocument, + modelType: Post.classType, + variables: {'id': somePostId}, + decodePath: getPost, ); ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx index 488197283df..0ac70d20d27 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx @@ -4,11 +4,10 @@ Then, include it, along with any other auth providers, in the call to `addPlugin await Amplify.addPlugin( AmplifyAPI( authProviders: const [ - CustomOIDCProvider(), - CustomFunctionProvider(), + CustomOIDCProvider(), + CustomFunctionProvider(), ], - ), -); +)); ``` diff --git a/src/fragments/lib-v1/restapi/flutter/fetch.mdx b/src/fragments/lib-v1/restapi/flutter/fetch.mdx index 4e6a7b3e073..51dff8cb570 100644 --- a/src/fragments/lib-v1/restapi/flutter/fetch.mdx +++ b/src/fragments/lib-v1/restapi/flutter/fetch.mdx @@ -9,7 +9,7 @@ Future getTodo() async { final response = await restOperation.response; print('GET call succeeded: ${response.decodeBody()}'); } on ApiException catch (e) { - print('GET call failed: $e'); + print('GET call failed: $e'); } } ``` diff --git a/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx b/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx index 33d815c841f..6851efeaca8 100644 --- a/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx +++ b/src/fragments/lib-v1/storage/native_common/getting-started/common.mdx @@ -129,5 +129,5 @@ Congratulations! You've uploaded a file to an s3 bucket. Check out the following - [Using Lambda Triggers](/gen1/[platform]/prev/build-a-backend/storage/lambda-triggers/) -- [Escape Hatch](/gen1/[platform]/prev/build-a-backend/storage/sdk/) + - [Escape Hatch](/gen1/[platform]/prev/build-a-backend/storage/sdk/) \ No newline at end of file From 7108c6be81e71ed606ca62d40d24c27a9bf3049b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:34:24 -0700 Subject: [PATCH 65/88] chore: revert bash back to console --- .../lib-v1/datastore/flutter/relational/save-many-snippet.mdx | 2 +- .../lib-v1/datastore/flutter/relational/updated-schema.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fragments/lib-v1/datastore/flutter/relational/save-many-snippet.mdx b/src/fragments/lib-v1/datastore/flutter/relational/save-many-snippet.mdx index 92e4584894c..6b5280d8a9f 100644 --- a/src/fragments/lib-v1/datastore/flutter/relational/save-many-snippet.mdx +++ b/src/fragments/lib-v1/datastore/flutter/relational/save-many-snippet.mdx @@ -1,6 +1,6 @@ Add the model above to the `schema.graphql` file located by default at `amplify/backend/{api_name}/` and regenerate the models again with the following command: -```bash +```console amplify codegen models ``` diff --git a/src/fragments/lib-v1/datastore/flutter/relational/updated-schema.mdx b/src/fragments/lib-v1/datastore/flutter/relational/updated-schema.mdx index 95a028c7d0d..4faa81ea568 100644 --- a/src/fragments/lib-v1/datastore/flutter/relational/updated-schema.mdx +++ b/src/fragments/lib-v1/datastore/flutter/relational/updated-schema.mdx @@ -26,6 +26,6 @@ type Comment @model { After that, regenerate the models with the following console command: -```bash +```console amplify codegen models ``` From fae2ccf13d3e277f15e72eb1227c5314d5bbc5ef Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:41:01 -0700 Subject: [PATCH 66/88] chore: remove inline filters and add fragment back in --- src/fragments/lib-v1/auth/common/device_features/common.mdx | 3 +-- src/fragments/lib-v1/auth/native_common/signin/common.mdx | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/fragments/lib-v1/auth/common/device_features/common.mdx b/src/fragments/lib-v1/auth/common/device_features/common.mdx index 7cecf6a0179..1832be445a6 100644 --- a/src/fragments/lib-v1/auth/common/device_features/common.mdx +++ b/src/fragments/lib-v1/auth/common/device_features/common.mdx @@ -97,9 +97,8 @@ import flutter11 from '/src/fragments/lib-v1/auth/flutter/device_features/30_fet import flutter13 from '/src/fragments/lib-v1/auth/flutter/device_features/40_trackDevice.mdx'; - ## Known Limitations When using the federated OAuth flow with Cognito User Pools, the [device tracking and remembering](https://aws.amazon.com/blogs/mobile/tracking-and-remembering-devices-using-amazon-cognito-your-user-pools/) features are currently not available within the library. - + \ No newline at end of file diff --git a/src/fragments/lib-v1/auth/native_common/signin/common.mdx b/src/fragments/lib-v1/auth/native_common/signin/common.mdx index cc12352a627..67ec2f0181e 100644 --- a/src/fragments/lib-v1/auth/native_common/signin/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signin/common.mdx @@ -44,13 +44,11 @@ import flutter8 from '/src/fragments/lib-v1/auth/flutter/signin/20_confirmSignUp - You will know the sign up flow is complete if you see the following in your console window: ```console Confirm signUp succeeded ``` - ## Sign in a user @@ -68,13 +66,11 @@ import flutter11 from '/src/fragments/lib-v1/auth/flutter/signin/30_signIn.mdx'; - You will know the sign in flow is complete if you see the following in your console window: ```console Sign in succeeded ``` - You have now successfully registered a user and authenticated with that user's username and password with Amplify. The Authentication category supports other mechanisms for authentication such as web UI based sign in, sign in using other providers etc that you can explore in the other sections. From 2a60bf8df5a0f69a19dddad0f9ce17829e5971de Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:43:10 -0700 Subject: [PATCH 67/88] chore: add statement about models back in --- .../lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx index 92d350cda64..5a59463031a 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/50_codegenCli.mdx @@ -1,3 +1,5 @@ +Models can also be generated using the Amplify CLI directly. + In your terminal, make sure you are in your project/root folder and **execute the codegen command**: ```console From a8645f8b8da7bb5a12d7d3d0df28bb13085b692d Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:45:57 -0700 Subject: [PATCH 68/88] chore: remove unused "datastore coming soon" file --- .../lib-v1/datastore/flutter/getting-started/40_codegen.mdx | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 src/fragments/lib-v1/datastore/flutter/getting-started/40_codegen.mdx diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/40_codegen.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/40_codegen.mdx deleted file mode 100644 index 6dd23646ca6..00000000000 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/40_codegen.mdx +++ /dev/null @@ -1,2 +0,0 @@ -Coming Soon. Please use [Amplify CLI](/gen1/[platform]/prev/build-a-backend/more-features/datastore/set-up-datastore/#code-generation-amplify-cli) for now. - From 923ec2d7f9f8659f1ed38c5baec4f801afd996b6 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:47:08 -0700 Subject: [PATCH 69/88] chore: remove callout --- .../graphqlapi/native_common/advanced-workflows/common.mdx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx index ebb73108dd8..04102c0d678 100644 --- a/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx +++ b/src/fragments/lib-v1/graphqlapi/native_common/advanced-workflows/common.mdx @@ -158,12 +158,6 @@ import flutter7 from '/src/fragments/lib-v1/graphqlapi/flutter/advanced-workflow By default, the API plugin includes appropriate authorization headers on your outgoing requests. However, you may have an advanced use case where you wish to send additional request headers to AppSync. - - -If your API does not require any authorization or if you would like manipulate the request yourself, please refer to the [Set authorization mode to NONE](/gen1/[platform]/build-a-backend/graphqlapi/customize-authz-modes/#none) - - - import ios8 from '/src/fragments/lib-v1/graphqlapi/ios/advanced-workflows/50_interceptor.mdx'; From 70b98204c8b0bcda6c41ecb8f5458e1569a39cf8 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:49:02 -0700 Subject: [PATCH 70/88] chore: revert user pool capitalization changes: out of scope --- .../lib-v1/auth/native_common/signout/common.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fragments/lib-v1/auth/native_common/signout/common.mdx b/src/fragments/lib-v1/auth/native_common/signout/common.mdx index 6c64e468271..43a57127aed 100644 --- a/src/fragments/lib-v1/auth/native_common/signout/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signout/common.mdx @@ -16,11 +16,11 @@ Calling signOut without any options will just delete the local cache and keychai [Amazon Cognito now supports token revocation](https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-cognito-now-supports-targeted-sign-out-through-refresh-token-revocation/) and the latest Amplify version will revoke Amazon Cognito tokens if the application is online. This means that the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. -Access and Id Tokens are short-lived (60 minutes by default but can be set from 5 minutes to 1 day). After revocation, these tokens cannot be used with Cognito user pool anymore. However, they are still valid when used with other services like AppSync or API Gateway. +Access and Id Tokens are short-lived (60 minutes by default but can be set from 5 minutes to 1 day). After revocation, these tokens cannot be used with Cognito User Pools anymore. However, they are still valid when used with other services like AppSync or API Gateway. -For limiting subsequent calls to these other services after invalidating tokens, we recommend lowering token expiration time for your app client in the Cognito user pool console. If you are using the Amplify CLI this can be accessed by running `amplify console auth`. +For limiting subsequent calls to these other services after invalidating tokens, we recommend lowering token expiration time for your app client in the Cognito User Pools console. If you are using the Amplify CLI this can be accessed by running `amplify console auth`. -Token revocation is enabled automatically on new Amazon Cognito user pools, however existing user pools must enable this feature, [using the Cognito Console or AWS CLI](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html). +Token revocation is enabled automatically on new Amazon Cognito user pools, however existing User Pools must enable this feature, [using the Cognito Console or AWS CLI](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html). import android3 from '/src/fragments/lib-v1/auth/android/signout/20_global_signout.mdx'; @@ -34,7 +34,7 @@ import flutter5 from '/src/fragments/lib-v1/auth/flutter/signout/20_global_signo -Calling signout with `globalSignOut = true` will invalidate all the Cognito user pool tokens of the signed in user. If the user is signed into a device, they won't be authorized to perform a task that requires a valid token when a global signout is called from some other device. They need to sign in again to get valid tokens. +Calling signout with `globalSignOut = true` will invalidate all the Cognito User Pool tokens of the signed in user. If the user is signed into a device, they won't be authorized to perform a task that requires a valid token when a global signout is called from some other device. They need to sign in again to get valid tokens. Global signout functionality does not work if you use one of the web UI sign From cf4f87d4a8b02594117920a14792e49a6240f211 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:50:54 -0700 Subject: [PATCH 71/88] chore: change the order of query data beginsWith back to original --- src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx b/src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx index cb426723cc1..8edf0a8a278 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/query-data.mdx @@ -80,8 +80,8 @@ Supported operators: - `ge` - greater than or equal - `lt` - less than - `le` - less than or equal -- `beginsWith` - Matches models where the given field begins with the provided value. - `between` - Matches models where the given field is between the provided start and end values. +- `beginsWith` - Matches models where the given field begins with the provided value. - `contains` - Matches models where the given field contains the provided value. ### Basic Equality Operator From 42f75f5ba250dd7bf7287a6bea79bf58fc42010e Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:52:31 -0700 Subject: [PATCH 72/88] chore: remove callouts --- .../lib-v1/datastore/native_common/real-time.mdx | 10 ---------- .../lib-v1/datastore/native_common/relational.mdx | 6 ------ 2 files changed, 16 deletions(-) diff --git a/src/fragments/lib-v1/datastore/native_common/real-time.mdx b/src/fragments/lib-v1/datastore/native_common/real-time.mdx index 6cb54ebba04..8b151c32650 100644 --- a/src/fragments/lib-v1/datastore/native_common/real-time.mdx +++ b/src/fragments/lib-v1/datastore/native_common/real-time.mdx @@ -2,16 +2,6 @@ You can subscribe to changes on your Models. This reacts dynamically to updates of data to the underlying Storage Engine, which could be the result of GraphQL Subscriptions as well as Queries or Mutations that run against the backing AppSync API if you are synchronizing with the cloud. - -**NOTE:** AWS AppSync has an [adjustable limit of 100 subscriptions per connection](https://docs.aws.amazon.com/general/latest/gr/appsync.html). DataStore automatically subscribes to create, update, and delete mutations for all models. - -This means that GraphQL APIs with DataStore enabled are limited to 33 models and DynamoDB tables. - -(3 **subscriptions** * 33 **models** = 99 **subscriptions per connection**). - -However, You can [request a service limit increase](https://console.aws.amazon.com/servicequotas/home/services/appsync/quotas/L-AA33EB36) from AWS AppSync to meet the real-time requirements of your application. - - import js0 from '/src/fragments/lib-v1/datastore/js/real-time/observe-snippet.mdx'; - -Join table records must be deleted prior to deleting the associated records. For example, for a many-to-many relationship between Posts and Tags, delete the PostTags join record prior to deleting a Post or Tag. - - - ```graphql enum PostStatus { ACTIVE From 60fbf78830584a89d5f3c9143bc3babcfd8540af Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 04:54:08 -0700 Subject: [PATCH 73/88] chore: revert spacing change --- .../lib-v1/datastore/native_common/setup-auth-rules.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/lib-v1/datastore/native_common/setup-auth-rules.mdx b/src/fragments/lib-v1/datastore/native_common/setup-auth-rules.mdx index b6c1c26a202..798e46eaf63 100644 --- a/src/fragments/lib-v1/datastore/native_common/setup-auth-rules.mdx +++ b/src/fragments/lib-v1/datastore/native_common/setup-auth-rules.mdx @@ -55,7 +55,7 @@ The following are commonly used patterns for static group authorization. For mor ```graphql type YourModel @model @auth(rules: [{ allow: groups, - groups: ["Admin"] }]) { + groups: ["Admin"] }]) { ... } ``` From 7d5bace58fcb2532bc0e59e576170e76e4ab85d1 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Fri, 17 May 2024 14:27:29 -0400 Subject: [PATCH 74/88] chore: fixes for auth category (#7612) --- .../auth/android/signin/20_confirmSignUp.mdx | 6 + .../lib-v1/auth/android/signin/30_signIn.mdx | 6 + .../lib-v1/auth/common/mfa/flows.mdx | 210 +++++++++++++++++- .../10_fetchAuthSession.mdx | 18 -- .../10_managing_credentials.mdx | 2 - .../auth/ios/signin/20_confirmSignUp.mdx | 6 + .../lib-v1/auth/ios/signin/30_signIn.mdx | 6 + .../access_credentials/common.mdx | 2 +- .../auth/native_common/signin/common.mdx | 12 - .../auth/native_common/signout/common.mdx | 6 + .../flutter/signin_web_ui/10_cli_setup.mdx | 2 +- 11 files changed, 240 insertions(+), 36 deletions(-) diff --git a/src/fragments/lib-v1/auth/android/signin/20_confirmSignUp.mdx b/src/fragments/lib-v1/auth/android/signin/20_confirmSignUp.mdx index be6a688b9e2..5a1a8195bbe 100644 --- a/src/fragments/lib-v1/auth/android/signin/20_confirmSignUp.mdx +++ b/src/fragments/lib-v1/auth/android/signin/20_confirmSignUp.mdx @@ -57,3 +57,9 @@ RxAmplify.Auth.confirmSignUp("username", "the code you received via email")
+ +You will know the sign up flow is complete if you see the following in your console window: + +```console +Confirm signUp succeeded +``` diff --git a/src/fragments/lib-v1/auth/android/signin/30_signIn.mdx b/src/fragments/lib-v1/auth/android/signin/30_signIn.mdx index 7437e3856a8..4d127003641 100644 --- a/src/fragments/lib-v1/auth/android/signin/30_signIn.mdx +++ b/src/fragments/lib-v1/auth/android/signin/30_signIn.mdx @@ -55,3 +55,9 @@ RxAmplify.Auth.signIn("username", "password")
+ +You will know the sign in flow is complete if you see the following in your console window: + +```console +Sign in succeeded +``` diff --git a/src/fragments/lib-v1/auth/common/mfa/flows.mdx b/src/fragments/lib-v1/auth/common/mfa/flows.mdx index 3ef933a4821..238e0f8e284 100644 --- a/src/fragments/lib-v1/auth/common/mfa/flows.mdx +++ b/src/fragments/lib-v1/auth/common/mfa/flows.mdx @@ -117,6 +117,27 @@ async function handleSignUp(username, password, phone_number, email) { + +```dart +Future signUpWithPhoneVerification( + String username, + String password, +) async { + await Amplify.Auth.signUp( + username: username, + password: password, + options: SignUpOptions( + userAttributes: { + // ... if required + AuthUserAttributeKey.email: 'test@example.com', + AuthUserAttributeKey.phoneNumber: '+18885551234', + }, + ), + ); +} +``` + + By default, you have to verify a user account after they sign up using the `confirmSignUp` API, which will send a one-time password to the user's phone number or email, depending on your Amazon Cognito configuration. @@ -159,6 +180,20 @@ async function handleSignUpConfirmation(username, confirmationCode) { + +```dart +Future confirmSignUpPhoneVerification( + String username, + String otpCode, +) async { + await Amplify.Auth.confirmSignUp( + username: username, + confirmationCode: otpCode, + ); +} +``` + + ### Handling SMS MFA challenge during Sign In After a user signs in, if they have MFA enabled for their account, a challenge will be returned that you would need to call the `confirmSignIn` API where the user provides their confirmation code sent to their phone number. @@ -200,6 +235,20 @@ async function handleSignIn(username, password) { + +```dart +Future signInWithPhoneVerification( + String username, + String password, +) async { + await Amplify.Auth.signIn( + username: username, + password: password, + ); +} +``` + + If MFA is **ON** or enabled for the user, you must call `confirmSignIn` with the OTP sent to their phone. @@ -240,6 +289,16 @@ async function handleSignInConfirmation(otpCode) { + +```dart +Future confirmSignInPhoneVerification(String otpCode) async { + await Amplify.Auth.confirmSignIn( + confirmationValue: otpCode, + ); +} +``` + + After a user has been signed in, call `updateMFAPreference` to record the MFA type as enabled for the user and optionally set it as preferred so that subsequent logins default to using this MFA type. @@ -258,6 +317,18 @@ async function handleUpdateMFAPreference() { + +```dart +Future updateMfaPreferences() async { + final cognitoPlugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); + + await cognitoPlugin.updateMfaPreference( + sms: MfaPreference.enabled, // or .preferred + ); +} +``` + + ## Multi-factor authentication with TOTP You can use Time-based One-Time Password (TOTP) for multi-factor authentication (MFA) in your web or mobile applications. The Amplify Auth category includes support for TOTP setup and verification using authenticator apps, offering an integrated solution and enhanced security for your users. These apps, such as Google Authenticator, Microsoft Authenticator, have the TOTP algorithm built-in and work by using a shared secret key and the current time to generate short-lived, six digit passwords. @@ -350,6 +421,33 @@ function handleSignInNextSteps(output) { + +```dart +Future signInUser(String username, String password) async { + try { + final result = await Amplify.Auth.signIn( + username: username, + password: password, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} + +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ··· + case AuthSignInStep.continueSignInWithTotpSetup: + final totpSetupDetails = result.nextStep.totpSetupDetails!; + final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); + safePrint('Open URI to complete setup: $setupUri'); + // ··· + } +} +``` + + The TOTP code can be obtained from the user via a text field or any other means. Once the user provides the TOTP code, call `confirmSignIn` with the TOTP code as the `challengeResponse` parameter. @@ -390,9 +488,25 @@ async function handleSignInConfirmation(totpCode) { + +```dart +Future confirmTotpUser(String totpCode) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: totpCode, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming TOTP code: ${e.message}'); + } +} +``` + + + + After a user has been signed in, call `updateMFAPreference` to record the MFA type as enabled for the user and optionally set it as preferred so that subsequent logins default to using this MFA type. - ```ts import { updateMFAPreference } from 'aws-amplify/auth'; @@ -440,6 +554,20 @@ async function handleTOTPSetup() { + +```dart +Future setUpTotp() async { + try { + final totpSetupDetails = await Amplify.Auth.setUpTotp(); + final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); + safePrint('Open URI to complete setup: $setupUri'); + } on AuthException catch (e) { + safePrint('An error occurred setting up TOTP: $e'); + } +} +``` + + Once the Authenticator app is set up, the user must generate a TOTP code and provide it to the library. Pass the code to `verifyTOTPSetup` to complete the TOTP setup process. @@ -480,10 +608,22 @@ async function handleTOTPVerification(totpCode) { -After TOTP setup is complete, call `updateMFAPreference` to record the MFA type as enabled for the user and optionally set it as preferred so that subsequent logins default to using this MFA type. + +```dart +Future verifyTotpSetup(String totpCode) async { + try { + await Amplify.Auth.verifyTotpSetup(totpCode); + } on AuthException catch (e) { + safePrint('An error occurred verifying TOTP: $e'); + } +} +``` + +After TOTP setup is complete, call `updateMFAPreference` to record the MFA type as enabled for the user and optionally set it as preferred so that subsequent logins default to using this MFA type. + ```ts import { updateMFAPreference } from 'aws-amplify/auth'; @@ -532,6 +672,18 @@ async function handleFetchMFAPreference() { + +```dart +Future getCurrentMfaPreference() async { + final cognitoPlugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); + + final currentPreference = await cognitoPlugin.fetchMfaPreference(); + safePrint('Enabled MFA types for user: ${currentPreference.enabled}'); + safePrint('Preferred MFA type for user: ${currentPreference.preferred}'); +} +``` + + ### Update the current user's MFA preferences Invoke the following API to update the MFA preference for the current user. @@ -558,7 +710,31 @@ async function handleUpdateMFAPreference() { + +```dart +Future updateMfaPreferences() async { + final cognitoPlugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); + + await cognitoPlugin.updateMfaPreference( + sms: MfaPreference.enabled, + totp: MfaPreference.preferred, + ); +} +``` + + + If multiple MFA methods are enabled for the user, the `signIn` API will return `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` as the next step in the auth flow. During this scenario, the user should be prompted to select the MFA method they want to use to sign in and their preference should be passed to `confirmSignIn`. + + + +If multiple MFA methods are enabled for the user, the signIn API will return continueSignInWithMFASelection as the next step in the auth flow. During this scenario, the user should be prompted to select the MFA method they want to use to sign in and their preference should be passed to confirmSignIn. + +The MFA types which are currently supported by Amplify Auth are: + +- `MfaType.sms` +- `MfaType.totp` + @@ -645,3 +821,33 @@ async function handleMFASelection(mfaType) { + + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ··· + case AuthSignInStep.continueSignInWithMfaSelection: + final allowedMfaTypes = result.nextStep.allowedMfaTypes!; + final selection = await _promptUserPreference(allowedMfaTypes); + return _handleMfaSelection(selection); + // ··· + } +} + +Future _promptUserPreference(Set allowedTypes) async { + // ··· +} + +Future _handleMfaSelection(MfaType selection) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: selection.confirmationValue, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error resending code: ${e.message}'); + } +} +``` + diff --git a/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx b/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx index 882afe49c7c..b660900491c 100644 --- a/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx +++ b/src/fragments/lib-v1/auth/flutter/access_credentials/10_fetchAuthSession.mdx @@ -1,21 +1,3 @@ -```dart -Future fetchAuthSession() async { - try { - final result = await Amplify.Auth.fetchAuthSession(); - safePrint('User is signed in: ${result.isSignedIn}'); - } on AuthException catch (e) { - safePrint('Error retrieving auth session: ${e.message}'); - } -} -``` - -### Retrieving AWS credentials - -Sometimes it can be helpful to retrieve the instance of the underlying plugin -which has more specific typing. In the case of Cognito, calling `fetchAuthSession` -on the Cognito plugin returns AWS-specific values such as the identity ID, -AWS credentials, and Cognito User Pool tokens. - ```dart Future fetchCognitoAuthSession() async { try { diff --git a/src/fragments/lib-v1/auth/flutter/managing_credentials/10_managing_credentials.mdx b/src/fragments/lib-v1/auth/flutter/managing_credentials/10_managing_credentials.mdx index 10a019e5ebc..48045383969 100644 --- a/src/fragments/lib-v1/auth/flutter/managing_credentials/10_managing_credentials.mdx +++ b/src/fragments/lib-v1/auth/flutter/managing_credentials/10_managing_credentials.mdx @@ -1,5 +1,3 @@ -The Amplify Auth category persists authentication-related information to make it available to other Amplify categories and to your application. - Amplify Flutter securely manages credentials and user identity information. You do not need to store, refresh, or delete credentials yourself. Amplify Flutter stores auth data on the device using platform capabilities such as [Keychain Services](https://developer.apple.com/documentation/security/keychain_services/) on iOS and macOS and [EncryptedSharedPreferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences) on Android. diff --git a/src/fragments/lib-v1/auth/ios/signin/20_confirmSignUp.mdx b/src/fragments/lib-v1/auth/ios/signin/20_confirmSignUp.mdx index 0d27983b0bf..d06485e78f8 100644 --- a/src/fragments/lib-v1/auth/ios/signin/20_confirmSignUp.mdx +++ b/src/fragments/lib-v1/auth/ios/signin/20_confirmSignUp.mdx @@ -37,3 +37,9 @@ func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCa
+ +You will know the sign up flow is complete if you see the following in your console window: + +```console +Confirm signUp succeeded +``` diff --git a/src/fragments/lib-v1/auth/ios/signin/30_signIn.mdx b/src/fragments/lib-v1/auth/ios/signin/30_signIn.mdx index 8a62595f166..c726a2df517 100644 --- a/src/fragments/lib-v1/auth/ios/signin/30_signIn.mdx +++ b/src/fragments/lib-v1/auth/ios/signin/30_signIn.mdx @@ -37,3 +37,9 @@ func signIn(username: String, password: String) -> AnyCancellable { + +You will know the sign in flow is complete if you see the following in your console window: + +```console +Sign in succeeded +``` diff --git a/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx b/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx index dd5f85a26df..7622a3aa205 100644 --- a/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/access_credentials/common.mdx @@ -2,7 +2,7 @@ An intentional decision with Amplify Auth was to avoid any public methods exposi With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. -However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by casting the result of fetchAuthSession as follows: +However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation by following the example below: import android0 from '/src/fragments/lib-v1/auth/android/access_credentials/10_fetchAuthSession.mdx'; diff --git a/src/fragments/lib-v1/auth/native_common/signin/common.mdx b/src/fragments/lib-v1/auth/native_common/signin/common.mdx index 67ec2f0181e..fb0e17af91d 100644 --- a/src/fragments/lib-v1/auth/native_common/signin/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signin/common.mdx @@ -44,12 +44,6 @@ import flutter8 from '/src/fragments/lib-v1/auth/flutter/signin/20_confirmSignUp -You will know the sign up flow is complete if you see the following in your console window: - -```console -Confirm signUp succeeded -``` - ## Sign in a user Implement a UI to get the username and password from the user. After the user enters the username and password you can start the sign in flow by calling the following method: @@ -66,12 +60,6 @@ import flutter11 from '/src/fragments/lib-v1/auth/flutter/signin/30_signIn.mdx'; -You will know the sign in flow is complete if you see the following in your console window: - -```console -Sign in succeeded -``` - You have now successfully registered a user and authenticated with that user's username and password with Amplify. The Authentication category supports other mechanisms for authentication such as web UI based sign in, sign in using other providers etc that you can explore in the other sections. import flutter12 from '/src/fragments/lib-v1/auth/flutter/signin/60_runtime_auth_flow.mdx'; diff --git a/src/fragments/lib-v1/auth/native_common/signout/common.mdx b/src/fragments/lib-v1/auth/native_common/signout/common.mdx index 43a57127aed..10d42b1ceb1 100644 --- a/src/fragments/lib-v1/auth/native_common/signout/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/signout/common.mdx @@ -12,9 +12,15 @@ import flutter2 from '/src/fragments/lib-v1/auth/flutter/signout/10_local_signou + Calling signOut without any options will just delete the local cache and keychain of the user. If you would like to sign out of all devices, invoke the signOut api with advanced options. [Amazon Cognito now supports token revocation](https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-cognito-now-supports-targeted-sign-out-through-refresh-token-revocation/) and the latest Amplify version will revoke Amazon Cognito tokens if the application is online. This means that the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. + + + +Calling signOut without any options will delete the local cache of user data and revoke the Amazon Cognito tokens if the application is online. This means that the Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. + Access and Id Tokens are short-lived (60 minutes by default but can be set from 5 minutes to 1 day). After revocation, these tokens cannot be used with Cognito User Pools anymore. However, they are still valid when used with other services like AppSync or API Gateway. diff --git a/src/fragments/lib/auth/flutter/signin_web_ui/10_cli_setup.mdx b/src/fragments/lib/auth/flutter/signin_web_ui/10_cli_setup.mdx index 7611f0f2d31..d82b75a9ac0 100644 --- a/src/fragments/lib/auth/flutter/signin_web_ui/10_cli_setup.mdx +++ b/src/fragments/lib/auth/flutter/signin_web_ui/10_cli_setup.mdx @@ -20,7 +20,7 @@ In terminal, navigate to your project, run `amplify add auth` (or if you've alre ? Enter your redirect signout URI: `myapp://` ? Do you want to add another redirect signout URI - `No` + `Yes` ? Enter your redirect signout URI: `http://localhost:3000/` ? Do you want to add another redirect signout URI From b1fc98450366188bff2fa60418673a2615ea7de4 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Fri, 17 May 2024 16:08:07 -0400 Subject: [PATCH 75/88] chore: flutter v2 storage fixes (#7615) * chore: add missing code snippets for storage * chore: filter out content for flutter --- .../native_common/getting-started.mdx | 4 +-- src/fragments/lib-v1/storage/flutter/copy.mdx | 28 +++++++++++++++++++ .../lib-v1/storage/flutter/get-properties.mdx | 18 ++++++++++++ .../build-a-backend/storage/copy/index.mdx | 2 +- .../storage/get-properties/index.mdx | 2 +- 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/fragments/lib-v1/storage/flutter/copy.mdx create mode 100644 src/fragments/lib-v1/storage/flutter/get-properties.mdx diff --git a/src/fragments/lib-v1/datastore/native_common/getting-started.mdx b/src/fragments/lib-v1/datastore/native_common/getting-started.mdx index c8651d3ca13..1601abb75a8 100644 --- a/src/fragments/lib-v1/datastore/native_common/getting-started.mdx +++ b/src/fragments/lib-v1/datastore/native_common/getting-started.mdx @@ -99,10 +99,10 @@ enum PostStatus { Now you will to convert the platform-agnostic `schema.graphql` into platform-specific data structures. DataStore relies on code generation to guarantee schemas are correctly converted to platform code. -Like the initial setup, models can be generated either using the IDE integration or Amplify CLI directly. - +Like the initial setup, models can be generated either using the IDE integration or Amplify CLI directly. + ### Code generation: Platform integration import ios12 from '/src/fragments/lib-v1/datastore/ios/getting-started/40_codegen.mdx'; diff --git a/src/fragments/lib-v1/storage/flutter/copy.mdx b/src/fragments/lib-v1/storage/flutter/copy.mdx new file mode 100644 index 00000000000..0b603a8f400 --- /dev/null +++ b/src/fragments/lib-v1/storage/flutter/copy.mdx @@ -0,0 +1,28 @@ +You can copy an existing file to a different location in your S3 bucket. User who initiates a copy operation should have read permission on the copy source file. + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future copy({ + required String sourceKey, + required String destinationKey, +}) async { + try { + final result = await Amplify.Storage.copy( + source: StorageItemWithAccessLevel( + storageItem: StorageItem(key: sourceKey), + accessLevel: StorageAccessLevel.guest, + ), + destination: StorageItemWithAccessLevel( + storageItem: StorageItem(key: destinationKey), + accessLevel: StorageAccessLevel.private, + ), + ).result; + + safePrint('Copied file: ${result.copiedItem.key}'); + } on StorageException catch (e) { + safePrint('Error copying file: ${e.message}'); + rethrow; + } +} +``` diff --git a/src/fragments/lib-v1/storage/flutter/get-properties.mdx b/src/fragments/lib-v1/storage/flutter/get-properties.mdx new file mode 100644 index 00000000000..a2d601a3778 --- /dev/null +++ b/src/fragments/lib-v1/storage/flutter/get-properties.mdx @@ -0,0 +1,18 @@ +You can get file properties and metadata without downloading the file using `Amplify.Storage.getProperties`. + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future getFileProperties() async { + try { + final result = await Amplify.Storage.getProperties( + key: 'example.txt', + ).result; + + safePrint('File size: ${result.storageItem.size}'); + } on StorageException catch (e) { + safePrint('Could not retrieve properties: ${e.message}'); + rethrow; + } +} +``` diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx index cd87d4d4d1e..c426c514fb5 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx @@ -60,6 +60,6 @@ import reactnative0 from '/src/fragments/lib-v1/storage/js/copy.mdx'; -import flutter0 from '/src/fragments/lib/storage/flutter/copy.mdx'; +import flutter0 from '/src/fragments/lib-v1/storage/flutter/copy.mdx'; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx index 50ed7db5495..2f69e336b04 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx @@ -55,6 +55,6 @@ import js0 from '/src/fragments/lib-v1/storage/js/get-properties.mdx'; }} /> -import flutter0 from '/src/fragments/lib/storage/flutter/get-properties.mdx'; +import flutter0 from '/src/fragments/lib-v1/storage/flutter/get-properties.mdx'; From d3e90b0feef5a861e4a912828a5a1e2ac798409f Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 14:01:35 -0700 Subject: [PATCH 76/88] Update src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx Co-authored-by: Jordan Nelson --- .../lib-v1/auth/native_common/user_attributes/common.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx b/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx index d57e6101c79..9290a1463ef 100644 --- a/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx +++ b/src/fragments/lib-v1/auth/native_common/user_attributes/common.mdx @@ -48,7 +48,7 @@ import flutter8 from '/src/fragments/lib-v1/auth/flutter/user_attributes/30_conf ## Send user attribute verification code -If an attribute needs to be verified while the user is authenticated, invoke the send api as shown below: +If an attribute needs to be verified while the user is authenticated, invoke the api as shown below: import ios9 from '/src/fragments/lib-v1/auth/ios/user_attributes/40_resend_code.mdx'; From 920b13378043f6804e9ab916e44465e5e35611d3 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 14:08:26 -0700 Subject: [PATCH 77/88] Flutter v2 fix analytics (#7618) * chore: add missing snippet in identify user * chore: remove flutter from javascript canonical objects * chore: delete unused fragment * chore: remove canonicalObjects that were added * chore: update a link in v1 to point to prev instead of vCurreent --- .../lib-v1/analytics/flutter/identifyuser.mdx | 8 + .../getting_started/70_configureBackend.mdx | 176 ------------------ .../flutter/getting-started/10_preReq.mdx | 2 +- .../build-a-backend/storage/copy/index.mdx | 13 -- .../storage/get-properties/index.mdx | 13 -- .../storage/transfer-acceleration/index.mdx | 14 -- 6 files changed, 9 insertions(+), 217 deletions(-) delete mode 100644 src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx diff --git a/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx b/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx index f43af9dc852..88dba5d80da 100644 --- a/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx +++ b/src/fragments/lib-v1/analytics/flutter/identifyuser.mdx @@ -34,3 +34,11 @@ Future addAnalyticsWithLocation({ ); } ``` + +Sending user information allows you to associate a user to their user profile and activities or actions in your app. The user's actions and attributes can also tracked across devices and platforms by using the same `userId`. + +Some scenarios for identifying a user and their associated app activities are: +* When a user completes app sign up +* When a user completes sign in process +* When a user launches your app +* When a user modifies or updates their user profile diff --git a/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx b/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx deleted file mode 100644 index 401c91aed23..00000000000 --- a/src/fragments/lib-v1/auth/flutter/getting_started/70_configureBackend.mdx +++ /dev/null @@ -1,176 +0,0 @@ - - - -> Prerequisites: [Install and configure](/gen1/[platform]/prev/start/project-setup/prerequisites/#install-and-configure-the-amplify-cli) the Amplify CLI in addition to the Amplify libraries and [necessary dependencies](/gen1/[platform]/prev/build-a-backend/auth/set-up-auth/#install-amplify-libraries). - -To start provisioning auth resources in the backend, go to your project directory and **execute the command**: - -```bash -amplify add auth -``` - -Enter the following when prompted: - -```console -? Do you want to use the default authentication and security configuration? - `Default configuration` -? How do you want users to be able to sign in? - `Username` -? Do you want to configure advanced settings? - `No, I am done.` -``` - -> If you have previously enabled an Amplify category that uses Auth behind the scenes (e.g. API category), you can run the `amplify update auth` command to edit your configuration if needed. - -To push your changes to the cloud, **execute the command**: - -```bash -amplify push -``` - -import flutter5 from '/src/fragments/lib/auth/flutter/getting_started/12_amplifyConfig.mdx'; - - - - - - - -> Prerequisites: [Install and configure](/[platform]/start/project-setup/prerequisites/#install-and-configure-the-amplify-cli) the Amplify CLI in addition to the Amplify libraries and [necessary dependencies](/[platform]/build-a-backend/auth/set-up-auth/#install-amplify-libraries). - -To import existing Amazon Cognito resources into your Amplify project, **execute the command**: - -```bash -amplify import auth -``` - -```console -? What type of auth resource do you want to import? - Cognito User Pool and Identity Pool - Cognito User Pool only -``` - -Once you've selected an option, you'll be able to search for and import an existing Cognito User Pool and Identity Pool (or User Pool only) within your AWS account. The `amplify import auth` command will also do the following: - -- Automatically populate your Amplify Library configuration file (`amplifyconfiguration.dart`) with your chosen Amazon Cognito resource information -- Provide your designated existing Cognito resource as the authentication & authorization mechanism for all auth-dependent categories (API, Storage and more) -- Enable Lambda functions to access the chosen Cognito resource if you permit it - -> If you have previously enabled an Amplify category that uses Auth behind the scenes (e.g. API category), you can run the `amplify update auth` command to edit your configuration if needed. - -After configuring your Authentication options, update your backend and deploy the service by running the `push` command: - -```bash -amplify push -``` - -Now, the authentication service has been deployed and you can start using it. To view the deployed services in your project at any time, go to Amplify Console by running the following command: - -```bash -amplify console -``` - -For more details, see how to [Use an existing Cognito User Pool and Identity Pool](/[platform]/tools/cli/commands/#auth-import). - - - - - -> Prerequisites: [Install and configure](/[platform]/start/project-setup/prerequisites/#install-and-configure-the-amplify-cli) the Amplify CLI in addition to the Amplify libraries and [necessary dependencies](/[platform]/build-a-backend/auth/set-up-auth/#install-amplify-libraries). - -Amplify Studio allows you create auth resources, set up authorization rules, implement Multi-factor authentication (MFA), and more via an intuitive UI. To set up Authentication through the Amplify Studio, take the following steps: - -1. **Sign in** to the [AWS Management Console](https://console.aws.amazon.com/console/home) and open AWS Amplify. -2. In the navigation pane, **choose an application**. -3. On the application information page, choose the **Backend environments** tab, then choose **Launch Studio**. -4. On the **Set up** menu, choose **Authentication**. -5. In the **Configure log in** section, choose a login mechanism to add from the **Add login mechanism** list. Valid options are _Username_, _Phone number_, _Facebook_, _Google_, _Amazon_, and _Sign in with Apple_. If you choose one of the social sign-in mechanisms (i.e. _Facebook_, _Google_, _Amazon_, or _Sign in with Apple_), you will also need to enter your _App ID_, _App Secret_, and redirect URLs. -6. (Optional) Add multi-factor authentication (MFA). MFA is set to **Off** by default. To turn on MFA, do the following in the **Multi-factor authentication** section: - -- Choose **Enforced** to require MFA for all users or choose **Optional** to allow individual users to enable MFA. -- (Optional) Choose **SMS**, and enter your SMS message. -- (Optional) Choose **Authenticator Application** if you want your app to load with an authentication flow that includes sign up and sign in. - -7. In the **Configure sign up** section, expand **Password protection settings** and customize the password policy settings to enforce. u6. Choose **Save and Deploy**. This starts a CloudFormation deployment with the progress displayed in the upper right corner of the page. -8. After creating and configuring your auth resources, you'll need to pull them down from Amplify Studio. To do so, simply click on "Local setup instructions" in the upper right hand corner of the Studio console and execute the CLI command it provides at the root directory of your app. - -> You can also [import](/[platform]/tools/console/auth/import/) existing Amazon Cognito resources and [manage users and groups](/[platform]/tools/console/auth/user-management/) through the Amplify Studio UI. - - - - - -Existing Authentication resources from AWS (e.g. Amazon Cognito UserPools or Identity Pools) can be used with the Amplify Libraries by calling the `Amplify.configure()` method. - -If you are not using the Amplify CLI, a Cognito User Pool and Identity Pool can be used by referencing them in your `amplifyconfiguration.dart` file: - -```dart -const amplifyconfig = ''' { - "UserAgent": "aws-amplify-cli/2.0", - "Version": "1.0", - "auth": { - "plugins": { - "awsCognitoAuthPlugin": { - "IdentityManager": { - "Default": {} - }, - "CredentialsProvider": { - "CognitoIdentity": { - "Default": { - "PoolId": "[COGNITO IDENTITY POOL ID]", - "Region": "[REGION]" - } - } - }, - "CognitoUserPool": { - "Default": { - "PoolId": "[COGNITO USER POOL ID]", - "AppClientId": "[COGNITO USER POOL APP CLIENT ID]", - "Region": "[REGION]" - } - }, - "Auth": { - "Default": { - "authenticationFlowType": "USER_SRP_AUTH", - "OAuth": { - "WebDomain": "[YOUR COGNITO DOMAIN ]", - "AppClientId": "[COGNITO USER POOL APP CLIENT ID]", - "SignInRedirectURI": "[CUSTOM REDIRECT SCHEME AFTER SIGN IN, e.g. myapp://]", - "SignOutRedirectURI": "[CUSTOM REDIRECT SCHEME AFTER SIGN OUT, e.g. myapp://]", - "Scopes": [ - "phone", - "email", - "openid", - "profile", - "aws.cognito.signin.user.admin" - ] - } - } - } - } - } - } -}'''; -``` - -- **CredentialsProvider**: - - **Cognito Identity**: - - **Default**: - - **PoolID**: ID of the Amazon Cognito Identity Pool (e.g. `us-east-1:123e4567-e89b-12d3-a456-426614174000`) - - **Region**: AWS Region where the resources are provisioned (e.g. `us-east-1`) -- **CognitoUserPool**: - - **Default**: - - **PoolId**: ID of the Amazon Cognito User Pool (e.g. `us-east-1_abcdefghi`) - - **AppClientId**: ID for the client used to authenticate against the user pool - - **Region**: AWS Region where the resources are provisioned (e.g. `us-east-1`) -- **Auth**: - - **Default**: - - **authenticationFlowType**: The authentication flow type, takes values `USER_SRP_AUTH`, `CUSTOM_AUTH`, and `USER_PASSWORD_AUTH`. Default is `USER_SRP_AUTH`. - - **OAuth**: Hosted UI Configuration (only include this if using the Hosted UI flow) - - **Scopes:** Scopes should match the scopes enables in Cognito under "App client settings" - -If you are using a Cognito User Pool without a Cognito Identity Pool, you can omit the **CredentialsProvider** section in the configuration. - - - - diff --git a/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx index 8710866de4b..abbff194563 100644 --- a/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx +++ b/src/fragments/lib-v1/datastore/flutter/getting-started/10_preReq.mdx @@ -2,4 +2,4 @@ - An iOS configuration targeting at least iOS 13.0 - An Android configuration targeting at least Android API level 24 (Android 7.0) or above - - For a full example of please follow the [project setup walkthrough](/gen1/[platform]/start/project-setup/create-application/) + - For a full example of please follow the [project setup walkthrough](/gen1/[platform]/prev/start/project-setup/create-application/) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx index c426c514fb5..c71654cc6bb 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx @@ -11,19 +11,6 @@ export const meta = { 'react', 'vue', 'flutter' - ], - canonicalObjects: [ - { - platforms: [ - 'angular', - 'nextjs', - 'javascript', - 'vue', - 'react', - 'flutter' - ], - canonicalPath: '/javascript/prev/build-a-backend/storage/copy/' - } ] }; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx index 2f69e336b04..b8030089e2b 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/get-properties/index.mdx @@ -10,19 +10,6 @@ export const meta = { 'react', 'vue', 'flutter' - ], - canonicalObjects: [ - { - platforms: [ - 'vue', - 'nextjs', - 'javascript', - 'react', - 'angular', - 'flutter' - ], - canonicalPath: '/javascript/prev/build-a-backend/storage/get-properties/' - } ] }; diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx index 1c63c960772..264f922e0fc 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/transfer-acceleration/index.mdx @@ -11,20 +11,6 @@ export const meta = { 'react', 'vue', 'flutter' - ], - canonicalObjects: [ - { - platforms: [ - 'angular', - 'vue', - 'react', - 'nextjs', - 'javascript', - 'react-native', - 'flutter' - ], - canonicalPath: '/javascript/prev/build-a-backend/storage/transfer-acceleration/' - } ] }; From 38ce2ec1d2bab4d413b98b47be23ab317c5c9a07 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 14:23:48 -0700 Subject: [PATCH 78/88] chore: revert changes in mutate-data file and fix formatting --- .../flutter/authz/2X_add_plugin.mdx | 7 ++-- .../lib-v1/graphqlapi/flutter/mutate-data.mdx | 38 ++----------------- 2 files changed, 8 insertions(+), 37 deletions(-) diff --git a/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx b/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx index 0ac70d20d27..488197283df 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/authz/2X_add_plugin.mdx @@ -4,10 +4,11 @@ Then, include it, along with any other auth providers, in the call to `addPlugin await Amplify.addPlugin( AmplifyAPI( authProviders: const [ - CustomOIDCProvider(), - CustomFunctionProvider(), + CustomOIDCProvider(), + CustomFunctionProvider(), ], -)); + ), +); ``` diff --git a/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx b/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx index ace9ee8ea40..f2be159778a 100644 --- a/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx +++ b/src/fragments/lib-v1/graphqlapi/flutter/mutate-data.mdx @@ -1,23 +1,11 @@ -In this guide, you will learn how to create, update, and delete your data using Amplify Libraries' GraphQL client. +## Run a mutation -Before you begin, you will need: +Now that the client is set up, you can run a GraphQL mutation with `Amplify.API.mutate` to create, update, and delete your data. -- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/) - -## Run mutations to create, update, and delete application data - -In GraphQL, mutations are APIs that are used to create, update, or delete data. This is different than queries, which are used to read the data but not change it. The following examples demonstrate how you can create, update, and delete items using the Amplify GraphQL client. - -### Create an item - -You can run a GraphQL mutation with `Amplify.API.mutate` to create an item. - -import createTodo from "/src/fragments/lib/graphqlapi/flutter/getting-started/50_createtodo.mdx"; +import createTodo from "/src/fragments/lib-v1/graphqlapi/flutter/getting-started/50_createtodo.mdx"; -### Update an item - To update the `Todo` with a new name: ```dart @@ -30,8 +18,6 @@ Future updateTodo(Todo originalTodo) async { } ``` -### Delete an item - To delete the `Todo`: ```dart @@ -42,27 +28,11 @@ Future deleteTodo(Todo todoToDelete) async { } ``` -Or you can delete by ID, which is ideal if you do not have the instance in memory yet: - ```dart // or delete by ID, ideal if you do not have the instance in memory, yet Future deleteTodoById(Todo todoToDelete) async { - final request = ModelMutations.deleteById( - Todo.classType, - TodoModelIdentifier(id: '8e0dd2fc-2f4a-4dc4-b47f-2052eda10775'), - ); + final request = ModelMutations.deleteById(Todo.classType, '8e0dd2fc-2f4a-4dc4-b47f-2052eda10775'); final response = await Amplify.API.mutate(request: request).response; safePrint('Response: $response'); } ``` - -## Conclusion - -Congratulations! You have finished the **Create, update, and delete application data** guide. In this guide, you created, updated, and deleted your app data through the GraphQL API. - -## Next steps - -Our recommended next steps include using the API to query data and subscribe to real-time events to look for mutations in your data. Some resources that will help with this work include: - -- [Read application data](/gen1/[platform]/build-a-backend/graphqlapi/query-data/) -- [Subscribe to real-time events](/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/) From 1204d15a06c55051d0414d3351d1c8c22a1ccf3d Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 14:28:17 -0700 Subject: [PATCH 79/88] Update src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx Co-authored-by: Jordan Nelson --- .../lib-v1/datastore/flutter/sync/50-selectiveSync.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx b/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx index b6291a7d6de..8f2b4223c90 100644 --- a/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx +++ b/src/fragments/lib-v1/datastore/flutter/sync/50-selectiveSync.mdx @@ -1,6 +1,6 @@ ## Selectively syncing a subset of your data -By default, DataStore fetches all the records that you’re authorized to access from your cloud data source to your local device. The maximum number of records that will be stored locally is configurable [here](/gen1/[platform]/build-a-backend/more-features/datastore/conflict-resolution/). +By default, DataStore fetches all the records that you’re authorized to access from your cloud data source to your local device. The maximum number of records that will be stored locally is configurable [here](/gen1/[platform]/prev/build-a-backend/more-features/datastore/conflict-resolution/). You can utilize selective sync to persist a subset of your data instead. From cdd75b43070eb56d26b59e45caccbaf65a8000d5 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 14:28:26 -0700 Subject: [PATCH 80/88] Update src/fragments/lib-v1/storage/existing-resources.mdx Co-authored-by: Jordan Nelson --- src/fragments/lib-v1/storage/existing-resources.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/lib-v1/storage/existing-resources.mdx b/src/fragments/lib-v1/storage/existing-resources.mdx index ec806533cfd..65a5a304967 100644 --- a/src/fragments/lib-v1/storage/existing-resources.mdx +++ b/src/fragments/lib-v1/storage/existing-resources.mdx @@ -6,7 +6,7 @@ amplify import storage For more details, see how to [Use an existing S3 bucket or DynamoDB table](/gen1/[platform]/build-a-backend/storage/import/). -If you are not using the Amplify CLI, an existing Amazon S3 bucket can be used by referencing it in your `amplifyconfiguration` file. +If you are not using the Amplify CLI, an existing Amazon S3 bucket can be used by referencing it in your Amplify configuration file. When you are not using Amplify CLI, adding existing Amazon S3 bucket to your application may require configuring bucket access permissions. e.g. Enabling read/write access to the cognito user pool that you are using with the Amplify Auth category. From 63b22614f08f2527413068a795363275e3f47ee4 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 14:29:35 -0700 Subject: [PATCH 81/88] chore: revert out of scope changes --- .../lib/geo/ios/device_tracking/10_tracking_options.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fragments/lib/geo/ios/device_tracking/10_tracking_options.mdx b/src/fragments/lib/geo/ios/device_tracking/10_tracking_options.mdx index 2e1a0ed6fbd..1afdf8b12ce 100644 --- a/src/fragments/lib/geo/ios/device_tracking/10_tracking_options.mdx +++ b/src/fragments/lib/geo/ios/device_tracking/10_tracking_options.mdx @@ -6,9 +6,9 @@ **`allowsBackgroundLocationUpdates`** | `false` | If true, location updates will be received from the OS when the app is in the background. **`pausesLocationUpdatesAutomatically`** | `true` | If true, allow the OS to pause location updates to optimize battery usages. **`activityType`** | `.automotive-` `Navigation` | If `pausesLocationUpdatesAutomatically` is true, the OS will decide appropriate times to pause location updates to improve battery life based on the `activityType`. -**`showsBackgroundLocationIndicator`** | `false` | If true and `requestAlwaysAuthorization` is true, the background location indicator is displayed and visible to the users. This option is not supported on macOS. +**`showsBackgroundLocationIndicator`** | `false` | If true and `requestAlwaysAuthorization` is true, the background location indicator is displayed and visible to the users. This option is not supported on MacOS. **`disregardLocationUpdatesWhenOffline`** | `false` | If true, the app will not store location updates when the app is offline. This is false by default; Amplify will store location updates locally due to loss of network connectivity and send location updates when app is online. **`wakeAppForSignificantLocationChanges`** | `false` | If true, the app will be woken up by significant location updates after an app has been force closed. In order to take advantage of this, you'll need to call `Amplify.Geo.startTracking()` in your apps launch lifecycle method. (e.g. `didFinishedLoading`) **`distanceFilter`** | `0` | If set, the minimum distance in meters at which the OS will update the app with a new location. **`trackUntil`** | `.distantFuture` | If set, the app will stop tracking when date is reached. By default, tracking will continue until user logOut or `stopTracking()` is called. -**`batchingOptions`** | `.none` | Custom defined behavior to send location updates in batches based on a specified threshold. +**`batchingOptions`** | `.none` | Custom defined behavior to send location updates in batches based on a specified threshold. \ No newline at end of file From e823368736faab0bc50ba9022adb8f6d1f16347e Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Fri, 17 May 2024 17:45:13 -0400 Subject: [PATCH 82/88] chore: update storage import existing resource --- .../build-a-backend/storage/import/index.mdx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/gen1/[platform]/build-a-backend/storage/import/index.mdx b/src/pages/gen1/[platform]/build-a-backend/storage/import/index.mdx index 9486eaddaa3..2c0bd3cd33e 100644 --- a/src/pages/gen1/[platform]/build-a-backend/storage/import/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/storage/import/index.mdx @@ -68,17 +68,25 @@ By default, Amplify Libraries assumes that S3 buckets are configured with the fo - `private/{user_identity_id}/` - Only accessible for the individual user -You can either configure your IAM role to use the Amplify-recommended policies or in your Amplify libraries configuration [overwrite the default storage path behavior](/gen1/[platform]/build-a-backend/storage/configure-access/#customize-object-key-path). +You can either configure your IAM role to use the Amplify-recommended policies or use [`path`](/gen1/[platform]/build-a-backend/storage/path/) to specify your own path. + + + + + +You can either configure your IAM role to use the Amplify-recommended policies or use [`StoragePath`](/gen1/[platform]/build-a-backend/storage/storagepath/) to specify your own path. From b54ac0719553c22459681f87c0e9e54503d3dc5d Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 17 May 2024 15:02:42 -0700 Subject: [PATCH 83/88] chore: remove changes to js only files --- .../prev/build-a-backend/graphqlapi/mutate-data/index.mdx | 2 +- .../prev/build-a-backend/graphqlapi/query-data/index.mdx | 2 +- .../prev/build-a-backend/graphqlapi/subscribe-data/index.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/index.mdx index 12007c02301..582f5f2ff25 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/mutate-data/index.mdx @@ -58,7 +58,7 @@ In this guide, you will learn how to create, update, and delete your data using Before you begin, you will need: -- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/) +- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/) ## Run mutations to create, update, and delete application data diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/index.mdx index 64e69379031..38274b8aabb 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/query-data/index.mdx @@ -35,7 +35,7 @@ You can read application data using the Amplify GraphQL client. In this guide, w Before you begin, you will need: -- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/) +- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/) - Data already created to view ## List and get your data diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx index 75864a69620..9fd2e89d703 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx @@ -35,7 +35,7 @@ In this guide, we will outline the benefits of enabling real-time data integrati Before you begin, you will need: -- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/set-up-graphql-api/) +- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/) - Data already created to modify ## Set up a real-time subscription From 58ece63bc01661a888eaaff89a534ef69462b4d9 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Mon, 20 May 2024 11:29:34 -0400 Subject: [PATCH 84/88] chore: clean up gen 2 pre-req --- .../add-aws-services/analytics/set-up-analytics/index.mdx | 2 -- .../[platform]/build-a-backend/storage/set-up-storage/index.mdx | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx index 0789ac9b503..f0e040c5fe9 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx @@ -62,8 +62,6 @@ For more information on how to use the `visionos-preview` branch, see [Platform Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/start/) guide for more details on platform specific setup. - - ## Set up Analytics backend diff --git a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx index 4cb0031afe7..7cab228ed88 100644 --- a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx @@ -410,8 +410,6 @@ Note that because the storage category requires auth, you will need to either co ### Prerequisites -* [Install and configure Amplify CLI](/gen1/[platform]/tools/cli/start/set-up-cli/) - Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/) guide for more details on platform specific setup. ### Install Amplify library From 16aa222a9fd408b7f56ff73005d3b19301b92707 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Mon, 20 May 2024 11:39:01 -0400 Subject: [PATCH 85/88] chore: remove link to platform setup guide in gen 2 --- .../add-aws-services/analytics/set-up-analytics/index.mdx | 2 +- .../[platform]/build-a-backend/storage/set-up-storage/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx index f0e040c5fe9..a184e161552 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/analytics/set-up-analytics/index.mdx @@ -60,7 +60,7 @@ For more information on how to use the `visionos-preview` branch, see [Platform -Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/start/) guide for more details on platform specific setup. +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. ## Set up Analytics backend diff --git a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx index 7cab228ed88..71a73ce718e 100644 --- a/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/set-up-storage/index.mdx @@ -410,7 +410,7 @@ Note that because the storage category requires auth, you will need to either co ### Prerequisites -Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/gen1/[platform]/start/) guide for more details on platform specific setup. +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. ### Install Amplify library From a3db3c8c5d119e8d26556a7fa8f020666c101bf4 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Mon, 20 May 2024 11:45:06 -0400 Subject: [PATCH 86/88] chore: update pre-reqs and getting started --- .../auth/customize-auth-lifecycle/custom-auth-flows/index.mdx | 2 +- src/pages/[platform]/start/quickstart/index.mdx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/index.mdx b/src/pages/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/index.mdx index 5d1d5bd8478..f244ce39645 100644 --- a/src/pages/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/index.mdx @@ -732,7 +732,7 @@ The Auth category can be configured to perform a [custom authentication flow](ht ## Prerequisites -Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#platform-setup) for more details on platform specific setup. +Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#platform-setup) for more details on platform specific setup. ## Configure Auth diff --git a/src/pages/[platform]/start/quickstart/index.mdx b/src/pages/[platform]/start/quickstart/index.mdx index aefef83ded8..8f4a9e87107 100644 --- a/src/pages/[platform]/start/quickstart/index.mdx +++ b/src/pages/[platform]/start/quickstart/index.mdx @@ -1296,6 +1296,7 @@ Before you get started, make sure you have the following installed: - [git](https://git-scm.com/) v2.14.1 or later - You will also need to [create an AWS Account](https://portal.aws.amazon.com/billing/signup). Note that AWS Amplify is part of the [AWS Free Tier](https://aws.amazon.com/amplify/pricing/). - Configure your AWS account to use with Amplify [instructions](/[platform]/start/account-setup/). +- A stable version of [Flutter](https://docs.flutter.dev/get-started/install). You can follow the [official documentation](https://flutter.dev/docs/get-started/install) to install Flutter on your machine and check the [editor documentation](https://docs.flutter.dev/get-started/editor) for setting up your editor. From bd2f45609909ac9099cf69865e4da7253fdaefd8 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 20 May 2024 13:27:05 -0700 Subject: [PATCH 87/88] chore: add move back into vPrevious docs --- src/directory/directory.mjs | 3 +++ src/fragments/lib-v1/storage/flutter/move.mdx | 26 +++++++++++++++++++ .../build-a-backend/storage/move/index.mdx | 26 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 src/fragments/lib-v1/storage/flutter/move.mdx create mode 100644 src/pages/gen1/[platform]/prev/build-a-backend/storage/move/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index d2c9274d31f..894a6dbb0b9 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -2315,6 +2315,9 @@ export const directory = { { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/copy/index.mdx' }, + { + path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/move/index.mdx' + }, { path: 'src/pages/gen1/[platform]/prev/build-a-backend/storage/remove/index.mdx' }, diff --git a/src/fragments/lib-v1/storage/flutter/move.mdx b/src/fragments/lib-v1/storage/flutter/move.mdx new file mode 100644 index 00000000000..a2b3572df3e --- /dev/null +++ b/src/fragments/lib-v1/storage/flutter/move.mdx @@ -0,0 +1,26 @@ +You can move an existing file to a different folder location in your S3 bucket. The user initiating the move operation must have read and write permission on both the source file and destination file. + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +Future movePrivateFile({ + required String sourceKey, + required String destinationKey, +}) async { + try { + final result = await Amplify.Storage.move( + source: StorageItemWithAccessLevel( + storageItem: StorageItem(key: sourceKey), + accessLevel: StorageAccessLevel.private, + ), + destination: StorageItemWithAccessLevel( + storageItem: StorageItem(key: destinationKey), + accessLevel: StorageAccessLevel.private, + ), + ).result; + safePrint('Moved file to: ${result.movedItem.key}'); + } on StorageException catch (e) { + safePrint('Something went wrong moving the file: ${e.message}'); + rethrow; + } +} +``` \ No newline at end of file diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/storage/move/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/storage/move/index.mdx new file mode 100644 index 00000000000..a3c648bc358 --- /dev/null +++ b/src/pages/gen1/[platform]/prev/build-a-backend/storage/move/index.mdx @@ -0,0 +1,26 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Move files', + description: "Learn more about how to move files using Amplify's storage category.", + platforms: ['flutter'] +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + + +import flutter0 from '/src/fragments/lib-v1/storage/flutter/move.mdx'; + + From 43f87c3151f7eb5d6d86c26f4e759b3cd9d560f6 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 20 May 2024 13:32:10 -0700 Subject: [PATCH 88/88] chore: add redirect from previous move page to the vPrevious move page --- redirects.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/redirects.json b/redirects.json index 582aa255a4f..b11790e727f 100644 --- a/redirects.json +++ b/redirects.json @@ -9673,5 +9673,10 @@ "source": "/gen2/", "target": "/", "status": "301" + }, + { + "source": "/gen1/flutter/build-a-backend/storage/move/", + "target": "/gen1/flutter/prev/build-a-backend/storage/move/", + "status": "301" } ]