From 4049bb943fc6af6b5b717f1b2cbedfe15bc76d47 Mon Sep 17 00:00:00 2001 From: akshbhu <39866697+akshbhu@users.noreply.github.com> Date: Wed, 27 Oct 2021 11:24:59 -0700 Subject: [PATCH] Rebase ext 2 (#8558) * chore(release): Publish [ci skip] - amplify-app@3.0.13 - amplify-category-analytics@2.21.22 - amplify-category-api@2.32.0 - amplify-category-auth@2.38.2 - amplify-category-function@2.35.0 - amplify-category-geo@1.0.1 - amplify-category-hosting@2.7.22 - amplify-category-interactions@2.6.6 - amplify-category-predictions@2.9.13 - amplify-category-storage@2.12.10 - amplify-category-xr@2.8.22 - amplify-cli-core@1.30.0 - @aws-amplify/cli@6.1.0 - amplify-console-hosting@1.9.13 - amplify-console-integration-tests@1.8.10 - amplify-container-hosting@1.3.24 - amplify-dotnet-function-template-provider@1.5.22 - amplify-dynamodb-simulator@1.19.13 - amplify-e2e-core@1.27.0 - amplify-e2e-tests@2.51.0 - amplify-frontend-ios@2.20.15 - amplify-frontend-javascript@2.24.2 - amplify-go-function-runtime-provider@1.9.5 - @aws-amplify/graphql-function-transformer@0.4.4 - @aws-amplify/graphql-http-transformer@0.5.4 - @aws-amplify/graphql-index-transformer@0.3.3 - @aws-amplify/graphql-model-transformer@0.6.3 - @aws-amplify/graphql-predictions-transformer@0.3.4 - @aws-amplify/graphql-relational-transformer@0.3.0 - @aws-amplify/graphql-searchable-transformer@0.6.1 - @aws-amplify/graphql-transformer-core@0.9.1 - @aws-amplify/graphql-transformer-interfaces@1.9.1 - amplify-java-function-runtime-provider@1.8.15 - amplify-migration-tests@3.1.10 - amplify-nodejs-function-runtime-provider@1.6.12 - amplify-nodejs-function-template-provider@1.6.22 - amplify-prompts@1.2.0 - amplify-provider-awscloudformation@4.61.0 - amplify-python-function-runtime-provider@1.9.12 - amplify-util-import@1.5.13 - amplify-util-mock@3.34.6 - graphql-auth-transformer@6.24.23 - graphql-connection-transformer@4.21.23 - graphql-dynamodb-transformer@6.22.23 - graphql-elasticsearch-transformer@4.12.2 - graphql-function-transformer@2.5.22 - graphql-http-transformer@4.18.10 - graphql-key-transformer@2.23.23 - graphql-predictions-transformer@2.5.22 - graphql-relational-schema-transformer@2.18.7 - graphql-transformer-common@4.19.10 - graphql-transformer-core@6.30.0 - graphql-transformers-e2e-tests@6.27.0 - graphql-versioned-transformer@4.17.23 * ci: improve parallelism, add windows for e2e tests (#8282) * chore: add additional instance types to @searchable directive (#8284) * ci: support multi-account e2e cleanup (#8082) * test: update pinpoint test region mapping (#8295) * ci: improve parallelization (#8292) * test: fix cci pipeline flow (#8296) * test: remove aws cli win install step (#8299) * fix: logic to display searchable instance warning (#8297) * test: dont run geo e2e tests on windows (#8300) * test: revert test timeout change (#8301) * Automatically retry CircleCi e2e tasks (#8306) * test: fix gql e2e split test logic, remove unnecessary inter-job requirements (#8309) * ci: remove retry wrapper on windows tests to prevent hanging (#8312) * ci: use powershell to run windows tests (#8314) * ci: skip rate limited windows tests (#8319) * chore(release): Publish [ci skip] - @aws-amplify/cli@6.1.1 - amplify-console-integration-tests@1.8.11 - amplify-e2e-core@1.27.1 - amplify-e2e-tests@2.51.1 - @aws-amplify/graphql-searchable-transformer@0.6.2 - amplify-migration-tests@3.1.11 - amplify-provider-awscloudformation@4.61.1 - amplify-util-mock@3.34.7 - graphql-auth-transformer@6.24.24 - graphql-elasticsearch-transformer@4.12.3 - graphql-transformers-e2e-tests@6.27.1 * chore: add cloud-e2e script (#8334) * ci: add automatic retry of failed tasks (#8338) * test: update snapshot file to match renamed test file (#8333) * build: run circleci validator on split-e2e-tests (#8313) * ci: fix retry function (#8343) * ci: track flaky tests in cloudwatch (#8339) * ci: fix cleanup script context (#8344) * feat: Custom policies IAM Policies for Lambda and Containers (#8068) * Custom policy implementation * feat: add custom policies file to function and API container add custom policies file to function and API container, merge the custom policies to CFN template, validation for regex of resources and actions in the custom policies file * feat: changes for first PR * feat: Some changes according to the PR comments 1. Add Json Schema to validate the customers input 2. some minor changes related to format issue 3. error handle * feat: replace env to current env in the resource when checkout and add env, and push * feat: e2e test and replacing env * feat: Minor changes for env replacement * feat: remove changing env between env * feat: Add cloudform type for type safety, move validation to provider-cloudformation, validation * feat: remove some unused function and import, change regex for resource * feat: Some changes according to the PR comment * feat: changes according to PR comments * feat: remove unused import * feat: remove previous unused code * feat: Changes according to PR comments * feat: some changes according to PR comments * feat: work on PR comments * feat: rebase for conflict * feat: rebase for failure of hooksmanager test failed * feat: unit test * feat: fix fail test * feat: change default template of custom policies * feat: fix failed test * feat: PR comments * feat: pr comments * feat: fix failed test * feat: PR comments from ED Co-authored-by: Lu Han * ci: split some migration tests, fix retry issues, remove per-job cleanup (#8340) * fix(graphql-model-transformer): fix open search instance check for v1 and v2 transformers (#8354) * chore(release): Publish [ci skip] - amplify-app@3.0.14 - amplify-category-analytics@2.21.23 - amplify-category-api@2.33.0 - amplify-category-auth@2.38.3 - amplify-category-function@2.36.0 - amplify-category-geo@1.0.2 - amplify-category-hosting@2.7.23 - amplify-category-interactions@2.6.7 - amplify-category-predictions@2.9.14 - amplify-category-storage@2.12.11 - amplify-category-xr@2.8.23 - amplify-cli-core@1.31.0 - @aws-amplify/cli@6.2.0 - amplify-console-hosting@1.9.14 - amplify-console-integration-tests@1.8.12 - amplify-container-hosting@1.3.25 - amplify-dotnet-function-template-provider@1.5.23 - amplify-dynamodb-simulator@1.19.14 - amplify-e2e-core@1.28.0 - amplify-e2e-tests@2.52.0 - amplify-frontend-ios@2.20.16 - amplify-frontend-javascript@2.24.3 - amplify-go-function-runtime-provider@1.9.6 - amplify-java-function-runtime-provider@1.8.16 - amplify-migration-tests@3.1.12 - amplify-nodejs-function-runtime-provider@1.6.13 - amplify-nodejs-function-template-provider@1.6.23 - amplify-provider-awscloudformation@4.62.0 - amplify-python-function-runtime-provider@1.9.13 - amplify-util-import@1.5.14 - amplify-util-mock@3.34.8 - graphql-auth-transformer@6.24.25 - graphql-connection-transformer@4.21.24 - graphql-dynamodb-transformer@6.22.24 - graphql-elasticsearch-transformer@4.12.4 - graphql-function-transformer@2.5.23 - graphql-http-transformer@4.18.11 - graphql-key-transformer@2.23.24 - graphql-predictions-transformer@2.5.23 - graphql-transformer-core@6.30.1 - graphql-transformers-e2e-tests@6.27.2 - graphql-versioned-transformer@4.17.24 * fix: opensearch warning, add optional chaining to get api category (#8371) * fix: opensearch warning, add optional chaining to get api category * fix: opensearch warning, add test with existing categories * chore(graphql-model-transformer): use lodash for accessing deep object property (#8374) * fix(amplify-category-api): custom policies attached to TaskRoleArn (#8376) * test(amplify-e2e-tests): ignore custom policies container tests for windows (#8378) * chore(release): Publish [ci skip] - @aws-amplify/cli@6.2.1 - amplify-provider-awscloudformation@4.62.1 - amplify-util-mock@3.34.9 * feat(graphql-default-value-transformer): implemented default value directive (#8291) * feat(graphql-model-transformer): index/primarykey datastore selective sync capability (#8240) * feat(amplify-provider-awscloudformation): merge user config with transform generated resolvers (#8262) * feat(amplify-provider-awscloudformation): enable custom resolvers for v2 transformer (#8332) * feat(cli): add post env add plugin event (#8220) * feat(cli): add post env add plugin event * chore(cli): run post env add hook on amplify init * chore: conditionally print update auth warning (#8207) * Revert "fix: fixes e2e bug" (#8397) * Revert "fix: fixes e2e bug (#7067)" This reverts commit 18c9a310fd6fbc25c01df40dfd7d0ad731da17d1. * Update packages/amplify-cli/src/context-manager.ts added ? for type checking * fix(amplify-category-api): fixed api to reference stack name and deployment bucket (#8145) * fix(amplify-category-api): fixed api to reference stack name and deployment bucket * fix: addressed pr comments * revert: temporarily setup new apps with old pluralization (#8401) * test(graphql-model-transformer): fixed plurality e2e test (#8406) * Revert custom override resolvers (#8409) * Revert "feat(amplify-provider-awscloudformation): enable custom resolvers for v2 transformer (#8332)" This reverts commit 1c730423085b09b4ba681193f020c30bc2a5e3cc. * Revert "feat(amplify-provider-awscloudformation): merge user config with transform generated resolvers (#8262)" This reverts commit f25abbf68f1e268f6e3dcb362685e7fe9ec760c8. * chore(amplify-e2e-tests): fix config.yml file (#8411) * chore(release): Publish [ci skip] - amplify-app@3.0.15 - amplify-category-analytics@2.21.24 - amplify-category-api@2.33.1 - amplify-category-auth@2.38.4 - amplify-category-function@2.36.1 - amplify-category-geo@1.0.3 - amplify-category-hosting@2.7.24 - amplify-category-interactions@2.6.8 - amplify-category-predictions@2.9.15 - amplify-category-storage@2.12.12 - amplify-category-xr@2.8.24 - amplify-cli-core@1.31.1 - @aws-amplify/cli@6.3.0 - amplify-console-hosting@1.9.15 - amplify-console-integration-tests@1.8.13 - amplify-container-hosting@1.3.26 - amplify-dotnet-function-template-provider@1.5.24 - amplify-dynamodb-simulator@1.19.15 - amplify-e2e-core@1.29.0 - amplify-e2e-tests@2.53.0 - amplify-frontend-ios@2.20.17 - amplify-frontend-javascript@2.24.4 - amplify-go-function-runtime-provider@1.9.7 - @aws-amplify/graphql-default-value-transformer@0.2.0 - @aws-amplify/graphql-function-transformer@0.4.5 - @aws-amplify/graphql-http-transformer@0.5.5 - @aws-amplify/graphql-index-transformer@0.4.0 - @aws-amplify/graphql-model-transformer@0.6.4 - @aws-amplify/graphql-predictions-transformer@0.3.5 - @aws-amplify/graphql-relational-transformer@0.3.1 - @aws-amplify/graphql-searchable-transformer@0.6.3 - @aws-amplify/graphql-transformer-core@0.9.2 - @aws-amplify/graphql-transformer-interfaces@1.10.0 - amplify-java-function-runtime-provider@1.8.17 - amplify-migration-tests@3.1.13 - amplify-nodejs-function-runtime-provider@1.6.14 - amplify-nodejs-function-template-provider@1.6.24 - amplify-provider-awscloudformation@4.63.0 - amplify-python-function-runtime-provider@1.9.14 - amplify-util-import@1.5.15 - amplify-util-mock@3.34.10 - graphql-auth-transformer@6.24.26 - graphql-connection-transformer@4.21.25 - graphql-dynamodb-transformer@6.22.25 - graphql-elasticsearch-transformer@4.12.5 - graphql-function-transformer@2.5.24 - graphql-http-transformer@4.18.12 - graphql-key-transformer@2.23.25 - graphql-predictions-transformer@2.5.24 - graphql-transformer-core@6.30.2 - graphql-transformers-e2e-tests@6.28.0 - graphql-versioned-transformer@4.17.25 * fix: api containers on repushing does not fail (#8416) * fix: api containers on repushing does not fail * feat: Geo category plugin - support for additional regions (#8373) * feat(amplify-category-geo): use custom resource constructs * fix(amplify-category-geo): remove pre-push hook * fix(amplify-category-geo): move lambdas to separate files * fix(amplify-category-geo): update stack test snapshots * fix: null check (#8429) * chore(release): Publish [ci skip] - amplify-app@3.0.16 - amplify-category-api@2.33.2 - amplify-category-geo@1.1.0 - @aws-amplify/cli@6.3.1 - amplify-console-integration-tests@1.8.14 - amplify-container-hosting@1.3.27 - amplify-e2e-core@1.30.0 - amplify-e2e-tests@2.54.0 - amplify-frontend-javascript@2.25.0 - amplify-migration-tests@3.1.14 - amplify-provider-awscloudformation@4.64.0 - amplify-util-mock@3.34.11 * Merge GraphQL v-next to master (#8287) * feat(cli-api): improve add and update api * Delete sam.schema.json * Delete cloudformation.schema.json * remove auto apply authmode code from update api workflow * remove unused import * fix lint issues * relative import * fix dependencies * fix lint comments * remove unused code * updated v2 templates * remove unused import * change to use executeProviderUtils * fixed formatting * several minor tweaks to add and update api workflow * update conflict detection label * remove app not deployed message * auto apply auth mode * auto apply authmode for v2 tranformer * add type amplify_global to v2 schemas * Update many-relationship-schema-v2.graphql * Update single-object-auth-schema-v2.graphql * Update single-object-schema-v2.graphql * feat: add @auth (#1) * feat: add @auth base package with Access Control * feat: graphql auth v2 add schemaChanges, iam policy generation, and query/read resolvers * feat: graphql auth v2 add auth on mutation and subscription resolvers * feat(amplify-category-api): add global sandbox mode directive on schema generation (#8074) * feat(amplify-category-api): add global sandbox mode directive on schema generation * test(amplify-e2e-tests): add e2e tests for sandbox mode * test(amplify-category-api): add unit test for generating sandbox mode directive; rm unused method * feat(cli): add sandbox mode warning to amplify status (#8078) * feat(amplify-category-api): prompt api key creation on amplify push (#8124) * feat(amplify-category-api): prompt api key create when invalid with sandbox mode * test(amplify-category-api): add unit tests for provider utils * test(amplify-category-api): fix test for adding api key prompt * refactor(cli): refactor api key prompt * refactor(amplify-category-api): add api key with gql compiled * feat: @model conflict resolution * auth directive support for index, searchable, predictions, functions, and relational directives (#8146) * feat: add support for index and updated unit and e2e tests * feat: directive suport for functions, predictions, searchable, and relational * test: updated unit tests for updated auth on directives * @auth support for datastore and add has auth flag (#8168) * feat: @auth v2 on datastore and updated unit tests * feat: add hasAuthFlag * feat(graphql-model-transformer): set up transformer for sandbox mode directive (#8138) * feat(graphql-model-transformer): add sandbox mode support to model transformer * refactor(graphql-transformer-core): do not persist sandbox mode meta data * fix: add command to show access control and field auth evaluation in access control (#8174) * fix: admin ui app state check and auth transformer index resolver name (#8175) * fix: has auth typo and qref on field conditions for private rule (#8180) * fix(graphql-model-transformer): use hasAuth flag when sandbox mode is disabled (#8179) * fix: update hasMany to use join table name, sync config warning, updated unit test * fix: add empty payload for sandbox mode * fix: snapshot test for @searchable * fix: udpated snapshot for index and relation directives * fix: use same none datasource name as resolver manager * fix: iam resolver check and relational payload (#8234) * fix: add datastore query in config for auth (#8246) * fix: auth filter expression (#8248) * fix: update iam auth to include roles in before template (#8259) * chore: rebase and update auth dependencies * fix(graphql-model-transformer): iam role name does not exceed 64 characters * fix: add base e2e tests with auth fixes Co-authored-by: Danielle Adams <6271256+danielleadams@users.noreply.github.com> Co-authored-by: lazpavel <85319655+lazpavel@users.noreply.github.com> * fix: update dependency versions * feat(amplify-provider-awscloudformation): match env directive field for sandbox mode (#3) * fix(amplify-provider-awscloudformation): invoke api function from invoker plugin (#8274) * fix(amplify-provider-awscloudformation): invoke api function from invoker plugin * fix(graphql-index-transformer): update snapshots for tests * test(amplify-provider-awscloudformation): fix tests for sandbox helpers * fix(amplify-provider-awscloudformation): remove sandbox mode directive from schema before transform (#8272) * chore(graphql-auth-transformer): update deps for auth transformer and api category * fix(graphql-model-transformer): revert code to master version * test(graphql-model-transformer): update the snapshot for the amplify/graphql-model-transformer test * chore: remove showacm as that was for testing purposes only * test(amplify-e2e-tests): update to use correct helpers * test(graphql-transformers-e2e-tests): enable sandbox mode on v2 transforms * test(amplify-e2e-tests): replace updateAPIResolution imports with new methods * fix: get item query for @model and relational directives * auto apply auth fix * auto apply auth modes v2 fix (#4) * test(amplify-e2e-tests): rm sandbox e2e test * fix(amplify-provider-awscloudformation): fix api key creation when sandbox mode enable * test(amplify-e2e-tests): create random app name generator for broken tests * fix api e2e workflow * test(amplify-e2e-core): add random app name generator, update snapshots and imports * chore(graphql-auth-transformer): upgrade deps in auth transformer * update e2e test to use new api workflow (#5) * feat(graphql-model-transformer): fix default value e2e test (#6) * test(amplify-e2e-tests): add missing helper * e2e fix for auth tests using new api workflow (#7) * update e2e test to use new api workflow * fix(test): update auth tests with new api workflow * test(amplify-e2e-tests): add missing helper and fix broken test Co-authored-by: Christopher Sundersingh Co-authored-by: Christopher Sundersingh <83315412+sundersc@users.noreply.github.com> Co-authored-by: Josue Ruiz <7465495+SwaySway@users.noreply.github.com> Co-authored-by: lazpavel <85319655+lazpavel@users.noreply.github.com> Co-authored-by: Colin Ihrig * fix: loosen @index validation (#8445) Previously, the validation was too strict, and did not match the v1 behavior. This commit updates the validationt to match v1. Co-authored-by: Colin Ihrig * feat(graphql-model-transformer): added transformer version feature flag (#8328) * feat(amplify-provider-awscloudformation): merge user config with transform generated resolvers (#8447) * fix(graphql-model-transformer): fixed schema template options check for transformer version (#8449) * fix(amplify-appsync-simulator): fixed app sync simulator util auth type mock (#8451) * fix: @auth fix relational auth, authv2 e2e with utils and fixes (#8450) * feat(amplify-provider-awscloudformation): enable custom resolvers for v2 transformer (#8454) * chore: update update-notifier dependency (#8457) * chore: update update-notifier dependency * Update config.yml * fix: auth on getting related model name and searchablevNext e2e (#8455) * test(amplify-e2e-tests): ignore resolvers tests for windows (#8458) * fix(graphql): correct api key type on auto apply auth mode (#8459) * test(graphql-model-transformer): added e2e test for query and mutation rename (#8461) * fix: add @manytomany join table auth (#8460) * fix: add @manyToMany join table auth This commit adds auth to the many to many join table. * fix: break dependency cycle This commit moves several tests from the @auth v2 package to the relational v2 package to break a dependency cycle. * fix: address lgtm bot comment Co-authored-by: Colin Ihrig * fix: @function vNext payload, remove unused code, and update common mapping tempalte function (#8462) * test: fix auth configuration for test file (#8463) Co-authored-by: Colin Ihrig * test: fix sign in for gql clients (#8464) * test: split up flaky test suite (#8468) Co-authored-by: Colin Ihrig * test: update split script for new suites (#8469) migration-api-key-migration2 was recently split into multiple suites. The original suite was special cased in the split-e2e-tests.ts script. This commit updates the new suites to have the same special casing. Co-authored-by: Colin Ihrig * feat(amplify-category-storage): headless support for S3 (#8423) Co-authored-by: Attila Hajdrik Co-authored-by: Edward Foyle * test: fix e2e failures for import_s3 (#8483) * test: fix some Windows e2e tests (#8372) * fix: use proper singleselect for windows e2e tests * ci: prevent jest from hanging aftetr completion * ci: do not use node for pkg binary * ci: try running all windows to see which pass * ci: use multi-account retries on windows * ci: use correct env var * ci: pare down list of failing windows tests * chore: skip additional tests * chore: skip necessary windows tests * chore: fix test lint * fix(amplify-category-storage): add auth by default (#8498) * test: fix post-test account cleanup (#8489) * test: fix post-test account cleanup * test: clarify error message * build(deps): bump vm2 from 3.9.3 to 3.9.5 (#8493) Bumps [vm2](https://github.com/patriksimek/vm2) from 3.9.3 to 3.9.5. - [Release notes](https://github.com/patriksimek/vm2/releases) - [Changelog](https://github.com/patriksimek/vm2/blob/master/CHANGELOG.md) - [Commits](https://github.com/patriksimek/vm2/compare/3.9.3...3.9.5) --- updated-dependencies: - dependency-name: vm2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: function comments to use os.EOL constant, ref #8177 (#8327) * test: add test for get-project-details.ts (#8390) * test: add test for fet-project-details.ts * chore: fixed the description * fix: update appsync simulators ip address validation (#8375) * fix: update appsync simulators ip address validation fix #8359 * address review comment * test: port @http e2e tests to v2 (#8478) * test: port @http e2e tests to v2 * fixup! test: port @http e2e tests to v2 Co-authored-by: Colin Ihrig * fix(import-auth): headless interface test (#8402) Co-authored-by: Abhishek Raj * fix(graphql): searchable default sort direction (#8481) * fix(graphql): searchable default sort direction * update snapshots * fix(graphql): change aggregate field type to enum (#8484) * fix(graphql): change aggregate field type to enum * update E2E test * chore(feature-req form): remove auto label and adjust wording to reflect form type (#8434) * fix mismatch ajv version between amplify-cli-core and amplify-provider-awscloudformation (#8415) * fix: improve trigger update error handling, ref #8280 (#8329) * fix(amplify-nodejs-function-runtime-provider): catch yarn 2 error on lambda build (#8263) * fix: #8254 invalid url is shown for branches with dash (#8260) * feat: add byValue and byValues helper functions to select pick options by value (#8253) * test(cli): add test for compare-plugins plugin-helpers (#8064) * fix: pinpoint feature flag read (#8496) * feat: flag to allow destructive schema changes (#8273) * chore: dumping table changes * chore: drop table POC * feat: plumb destructive-updates flag * feat: plumb rebuild * chore: fix rebuild prompts * test: adding unit and e2e tests * feat: sweet jesus it works * fix: iterative push lambda with updates * chore: organize code better * test: add unit and e2e tests * chore: fit and finish * chore: didn't save all the files * test: fix some tests and add a couple more * chore: address PR comments * test: fix e2e failures * chore: fix prompts dep version * test: exclude new api tests from windows * test: update test schema * chore: address PR comments * fix: messed up merge * test: fix e2e failures * test(graphql-predictions-transformer): added e2e tests for v2 predictions transformer (#8477) * test(graphql-predictions-transformer): added e2e tests for v2 predictions transformer * test(graphql-predictions-transformer): added e2e tests for v2 predictions transformer * feat: version blocking for CLI (#8512) * feat: version blocking for CLI * chore: run split-tests * ci: autogen cci config (#8510) * test: remove quotes from enum in @searchable v2 test (#8523) Refs: https://github.com/aws-amplify/amplify-cli/pull/8484 Co-authored-by: Colin Ihrig * Revert "feat: version blocking for CLI (#8512)" (#8522) This reverts commit 52edf2b58508c96e78184aba1f77c06c021cc9b1. Co-authored-by: Colin Ihrig * fix: add schema directives for sync operation when conflict resolution is enabled (#8521) * ci: reenable nightly jobs (#8529) * Revert "ci: reenable nightly jobs" (#8530) This reverts commit 0bbcd9e47f8f9c6215957caf2db5efdae7c2c944. * fix(cli): amplify plugin scan command print correct version of inactive hosting plugins (#8130) * fix(cli): amplify plugin scan command print correct version of inactive hosting plugins Fix missing plugin version case of exists multiple plugins in same category fix #8127 * refactor(cli): using `const` instead of `let` * fix(amplify-dynamodb-simulator):callback revoked within time-limit (#7843) * fix(amplify-dynamodb-simulator):callback revoked within time-limit * fix(amplify-dynamodb-simulator):merged all beforeEach()/afterEach() into one * try...catch added to clean up * fix(graphql): add defensive check on getTablesRequiringReplacement (#8528) * fix(graphql): add defensive check on getTablesRequiringReplacement * correct return statement * feat: generate list types as non-null (#8166) * feat: generate list types as non-null * test: update more snapshots * fix: make corresponding change in v2 transformer * fix: non null lists in v2 transformer * chore: force cci rerun * test: update one more snapshot * test: who knew there were so many snapshots to update * fix: add field auth on aggregation queries (#8508) * fix: add a validation check for aws environment variables (#7933) * fix: Move credential validation to the top function call getProfileCredentials isn't always pulling from a file with an access key/secret key, so validation can error out in the case of source profiles. Moving this to the top function getProfiledAwsConfig and performing the validation on the AWS config instead means we can accommodate source profiles. * Fix: add a validation check for aws environment variables Co-authored-by: Vandenberg * fix: broken docs link in error message (#8436) * ci: prevent setup from timing out during yarn installation (#8542) * ci: prevent setup from timing out during yarn installation * ci: use new package.json inside scripts dir * ci: extract repoRoot to variable * ci: re-enable nightly workflows (#8543) * Revert "Revert "ci: reenable nightly jobs" (#8530)" This reverts commit 5fead3ed7d57699a1f4438ae042a06701a5e4c28. * ci: re-enable nightly workflows * ci: use pipeline params to trigger workflows * ci: allow schedule to be determined by nightly-jobs branch * feat: FF for override stacks (#8228) * feat: root stack override (#8276) * feat: added root stack transformation * feat: adding root-stack-builder * feat: added e2e and migration tests * fix: minor fixes * fix: fixes unit tests * fix: address comments * feat: adding rootstack types to overrides helper package (#8298) * feat: ddb overrides and flow refactor * feat: ddb overrides * feat: add migration logic * fix: remove older files post migration * fix: address PR comments * fix: remove inquirer and use amplify prompts instead Co-authored-by: Ghosh * chore: ddb walkthrough refactor and override tests (#8364) * chore: overrides ddb and walthrough refactor tests * chore: address pr comments Co-authored-by: Ghosh * feat: Auth refactor to use cdk, eliminate EJS, overrides functionality (#8355) * feat: add auth override * fix: auth state unit tests working * feat: auth e2e fixes and migration e2e included * fix: fixes package name * fix: unit tests fix * fix: unit tests * fix: fixing overwritten files * fix: added vm2 and addressed comments * fix: minor fixes and e2e test for overrides * fix: cli-core unit test * fix: userPool group unit test * fix: minor refractor and comments * chore: lgtm warnings * fix: minor fixes * fix: unit tests * (feat) Override for S3 - ( includues refactor s3-walkthrough and migrate) (#8383) Co-authored-by: Sachin Panemangalore * fix: fixes dependsOn parameter and auth migration test (#8480) * fix: dependsOn fix * chore: address comments * chore: remove JSON parsing * fix: default migration set to true * fix: added public access to scoped packages (#8485) * fix: adds typescript json dependency (#8487) * fix: enable scoped packages in plugin platform (#8492) * fix: adds userPool resourceName instead of authResource (#8497) * fix: adds userPool resourceName instead of authResource * fix: roleName in userPool groups * (bug-fix/reconcile-headless) remove groupList from userInputs, fix single-group, deploy errors (#8501) Co-authored-by: Sachin Panemangalore * fix: userPool group template fixes (#8515) * fix: template and naming fixed * fix: addressed comments * (fix) Call Auth migration from S3 migration for all auth resources in S3 (#8511) * (fix) Call Auth migration from S3 migration for all auth resources used by S3 * (fix) use invokePluginMethod instead of direct category calls * (fix) unit-test for s3 migration Co-authored-by: Sachin Panemangalore * (fix) s3 add with no-auth resource should succeed (#8520) Co-authored-by: Sachin Panemangalore * (fix) amplify override storage fails with missing tsconfig.json (#8556) Co-authored-by: Sachin Panemangalore * fix: rebase extOverrides with master * fix: getSupportedServices function * fix: rebase leftovers Co-authored-by: aws-amplify-bot Co-authored-by: John Corser Co-authored-by: Yathi <511386+yuth@users.noreply.github.com> Co-authored-by: Edward Foyle Co-authored-by: josef Co-authored-by: Ammar <56042290+ammarkarachi@users.noreply.github.com> Co-authored-by: Lu Han Co-authored-by: Pavel Lazar <85319655+lazpavel@users.noreply.github.com> Co-authored-by: Danielle Adams <6271256+danielleadams@users.noreply.github.com> Co-authored-by: Phani Srikar Edupuganti <55896475+phani-srikar@users.noreply.github.com> Co-authored-by: Christopher Sundersingh Co-authored-by: Christopher Sundersingh <83315412+sundersc@users.noreply.github.com> Co-authored-by: Josue Ruiz <7465495+SwaySway@users.noreply.github.com> Co-authored-by: Colin Ihrig Co-authored-by: Colin Ihrig Co-authored-by: Attila Hajdrik Co-authored-by: John Hockett Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Yoshiaki Togami <62130798+togami2864@users.noreply.github.com> Co-authored-by: Abhishek Raj Co-authored-by: Abhishek Raj Co-authored-by: siegerts Co-authored-by: osddeitf <37999210+osddeitf@users.noreply.github.com> Co-authored-by: MURAKAMI Masahiko Co-authored-by: Stuti Prasad <43947328+studpeps@users.noreply.github.com> Co-authored-by: Marc VandenBerg Co-authored-by: Vandenberg Co-authored-by: Kaustav Ghosh Co-authored-by: Ghosh Co-authored-by: Sachin Panemangalore <83682223+sachscode@users.noreply.github.com> Co-authored-by: Sachin Panemangalore --- .circleci/config.base.yml | 5 +- .circleci/config.yml | 16638 +--------------- .github/ISSUE_TEMPLATE/2.feature_request.yaml | 3 +- .gitignore | 1 + package.json | 4 +- .../__tests__/scalars/AWSIPAddress.test.ts | 15 + .../velocity/util/auth-utils.test.ts | 57 + .../velocity/util/general-utils.test.ts | 11 +- .../velocity/util/list-utils.test.ts | 10 +- .../src/__tests__/velocity/util/math.test.ts | 10 +- .../src/__tests__/velocity/util/rds.test.ts | 10 +- .../src/__tests__/velocity/util/str.test.ts | 10 +- .../src/schema/appsync-scalars/index.ts | 16 +- .../src/velocity/index.ts | 2 +- .../src/velocity/util/auth-utils.ts | 17 + .../src/velocity/util/index.ts | 5 +- .../amplify-category-api/amplify-plugin.json | 25 +- packages/amplify-category-api/package.json | 2 + .../graphql-schemas/blank-schema.graphql | 0 .../many-relationship-schema-v2.graphql | 18 + .../single-object-auth-schema-v2.graphql | 18 + .../single-object-schema-v2.graphql | 5 + .../__tests__/commands/api/rebuild.test.ts | 64 + .../cfn-api-artifact-handler.test.ts.snap | 1 + .../cfn-api-artifact-handler.test.ts | 2 +- .../prompt-to-add-api-key.test.ts | 44 + .../appSync-walkthrough.test.ts | 15 +- ...p-sync-auth-type-bi-di-mapper.test.ts.snap | 2 + .../utils/global-sandbox-mode.test.ts | 21 + .../amplify-category-api/src/commands/api.js | 5 + .../src/commands/api/rebuild.ts | 40 + packages/amplify-category-api/src/index.ts | 33 + .../provider-utils/api-artifact-handler.ts | 6 +- .../cfn-api-artifact-handler.ts | 17 +- .../prompt-to-add-api-key.ts | 27 + .../appSync-walkthrough.ts | 628 +- ...nfig-to-app-sync-auth-type-bi-di-mapper.ts | 2 + .../utils/edit-schema-flow.ts | 2 +- .../utils/global-sandbox-mode.ts | 10 + .../src/provider-utils/supported-services.ts | 4 + packages/amplify-category-api/tsconfig.json | 2 + packages/amplify-category-auth/src/index.js | 4 +- .../auth-inputs-manager/auth-input-state.ts | 4 + .../handlers/resource-handlers.ts | 118 +- .../provider-utils/awscloudformation/index.js | 23 +- .../utils/message-printer.ts | 13 +- .../src/provider-utils/supported-services.ts | 13 + .../amplify-category-function/src/index.ts | 1 + .../utils/updateTopLevelComment.ts | 7 +- .../amplify-category-geo/amplify-plugin.json | 23 +- .../resources/custom-map-resource-handler.js | 90 +- .../custom-place-index-resource-handler.js | 98 +- .../src/__tests__/commands/geo/add.test.ts | 117 +- .../src/__tests__/commands/geo/update.test.ts | 119 +- .../__snapshots__/mapStack.test.ts.snap | 180 +- .../placeIndexStack.test.ts.snap | 196 +- .../__tests__/service-stacks/mapStack.test.ts | 56 +- .../service-stacks/placeIndexStack.test.ts | 56 +- .../src/commands/geo/add.ts | 10 +- .../src/commands/geo/console.ts | 2 +- .../src/commands/geo/update.ts | 12 +- packages/amplify-category-geo/src/index.ts | 83 +- .../src/provider-controllers/index.ts | 41 +- .../src/service-stacks/baseStack.ts | 48 +- .../src/service-stacks/mapStack.ts | 232 +- .../src/service-stacks/placeIndexStack.ts | 52 +- .../src/service-utils/constants.ts | 10 +- .../src/service-utils/mapUtils.ts | 78 +- .../src/service-utils/placeIndexUtils.ts | 81 +- .../amplify-category-storage/package.json | 3 +- .../ddb-stack-transform.test.ts | 4 - .../src/commands/storage/import.ts | 2 +- .../amplify-category-storage/src/constants.ts | 17 +- .../amplify-category-storage/src/index.ts | 42 +- .../default-values/dynamoDb-defaults.ts | 2 +- .../default-values/s3-defaults.ts | 8 +- .../awscloudformation/import/import-s3.ts | 11 +- .../awscloudformation/provider-constants.ts | 15 + .../awscloudformation/s3-trigger-helpers.ts | 515 + .../service-walkthroughs/s3-walkthrough.ts | 12 +- .../storage-configuration-helpers.ts | 621 + .../storage-state-management.ts | 17 - .../amplify-category-storage/tsconfig.json | 1 + .../src/__tests__/featureFlags.test.ts | 44 +- .../testFiles/project-with-features/cli.json | 2 +- .../amplify/cli.dev.json | 2 +- .../testProject-both-files/amplify/cli.json | 2 +- .../testProject-initialize-1/amplify/cli.json | 2 +- .../amplify/cli.prod.json | 2 +- .../amplify/cli.dev.json | 2 +- .../testProject-initialize-2/amplify/cli.json | 2 +- .../amplify/cli.prod.json | 2 +- .../testFiles/testProject-initialize-3/.env | 4 +- .../amplify/cli.dev.json | 2 +- .../testProject-initialize-3/amplify/cli.json | 2 +- .../amplify/cli.prod.json | 2 +- .../testFiles/testProject-initialize-4/.env | 4 +- .../amplify/cli.dev.json | 2 +- .../testProject-initialize-4/amplify/cli.json | 2 +- .../amplify/cli.prod.json | 2 +- .../testProject-initialize-env-number/.env | 2 +- .../amplify/cli.json | 2 +- .../testFiles/testProject-initialize/.env | 4 +- .../amplify/cli.dev.json | 2 +- .../testProject-initialize/amplify/cli.json | 2 +- .../amplify/cli.prod.json | 2 +- .../testProject-no-env/amplify/cli.json | 2 +- .../amplify/cli.dev.json | 2 +- .../src/feature-flags/featureFlags.ts | 6 + packages/amplify-cli-core/src/index.ts | 4 +- .../src/state-manager/pathManager.ts | 1 + packages/amplify-cli/package.json | 4 +- .../src/__tests__/commands/status.test.ts | 15 +- .../get-all-category-pluginInfos.test.ts | 2 +- .../get-project-details.test.ts | 78 + .../amplify-helpers/remove-resource.test.ts | 45 +- .../mockLocalCloud/amplify-meta-2.json | 25 + .../mockLocalCloud/amplify-meta-3.json | 16 + .../testData/mockLocalCloud/amplify-meta.json | 28 + .../plugin-helpers/compare-plugins.test.ts | 56 + .../platform-health-check.test.ts | 167 + packages/amplify-cli/src/commands/status.ts | 25 +- .../amplify-cli/src/domain/amplify-toolkit.ts | 3 +- .../amplify-helpers/push-resources.ts | 107 +- .../amplify-helpers/remove-resource.ts | 69 +- .../amplify-helpers/trigger-flow.ts | 10 +- packages/amplify-cli/src/index.ts | 2 +- .../plugin-helpers/platform-health-check.ts | 21 +- .../utils/table-utils.js | 5 +- .../__test__/index.test.js | 22 +- .../src/asciinema-recorder.ts | 4 +- .../amplify-e2e-core/src/categories/api.ts | 203 +- .../amplify-e2e-core/src/categories/auth.ts | 104 +- .../amplify-e2e-core/src/categories/geo.ts | 71 +- .../src/categories/storage.ts | 1 - .../amplify-e2e-core/src/cli-test-runner.js | 36 +- packages/amplify-e2e-core/src/index.ts | 13 +- .../amplify-e2e-core/src/init/amplifyPush.ts | 52 +- .../src/init/initProjectHelper.ts | 9 + packages/amplify-e2e-core/src/utils/api.ts | 12 + .../amplify-e2e-core/src/utils/headless.ts | 97 +- .../amplify-e2e-core/src/utils/nexpect.ts | 20 +- .../amplify-e2e-core/src/utils/sdk-calls.ts | 10 + .../schemas/custom_query.graphql | 21 + .../change-model-name/initial-schema.graphql | 2 +- .../schemas/model_with_sandbox_mode.graphql | 6 + .../simple_model_new_primary_key.graphql | 4 + .../src/__tests__/api_1.test.ts | 47 +- .../src/__tests__/api_2.test.ts | 62 +- .../src/__tests__/api_3.test.ts | 11 +- .../src/__tests__/api_4.test.ts | 6 +- .../src/__tests__/api_5.test.ts | 15 +- .../src/__tests__/api_6.test.ts | 84 + .../src/__tests__/feature-flags.test.ts | 14 +- .../src/__tests__/function_1.test.ts | 18 +- .../src/__tests__/function_2.test.ts | 28 +- .../src/__tests__/function_5.test.ts | 12 +- .../src/__tests__/function_9.test.ts | 20 +- .../src/__tests__/geo-add.test.ts | 4 +- .../src/__tests__/geo-remove.test.ts | 4 +- .../src/__tests__/geo-update.test.ts | 4 +- .../src/__tests__/import_s3_1.test.ts | 4 - .../src/__tests__/import_s3_2.test.ts | 24 +- .../src/__tests__/import_s3_3.test.ts | 228 + .../api.connection.migration.test.ts | 8 +- .../api.connection.migration2.test.ts | 8 +- .../migration/api.key.migration1.test.ts | 14 +- .../migration/api.key.migration2.test.ts | 120 +- .../migration/api.key.migration3.test.ts | 57 + .../migration/api.key.migration4.test.ts | 48 + .../migration/api.key.migration5.test.ts | 59 + .../src/__tests__/pull.test.ts | 11 +- .../src/__tests__/resolvers.test.ts | 137 + .../schema-iterative-rollback-1.test.ts | 17 +- .../schema-iterative-rollback-2.test.ts | 17 +- .../schema-iterative-update-1.test.ts | 18 +- .../schema-iterative-update-3.test.ts | 9 +- .../schema-iterative-update-4.test.ts | 9 +- .../schema-iterative-update-locking.test.ts | 6 +- .../src/__tests__/schema-key.test.ts | 8 +- .../src/__tests__/storage-1.test.ts | 194 +- .../src/__tests__/storage-2.test.ts | 19 +- .../src/__tests__/storage-3.test.ts | 39 +- .../src/__tests__/storage-4.test.ts | 143 + .../src/__tests__/storage-5.test.ts | 203 + .../src/cleanup-e2e-resources.ts | 27 +- .../amplify-e2e-tests/src/configure_tests.ts | 1 + .../src/schema-api-directives/index.ts | 4 +- .../schema-api-directives/tests/key-howTo4.ts | 7 +- .../lib/frontend-config-creator.js | 29 +- .../.npmignore | 5 + .../CHANGELOG.md | 0 .../package.json | 66 + .../conflict-resolution.test.ts.snap | 52 + .../field-auth-argument.test.ts.snap | 40 + .../src/__tests__/accesscontrol.test.ts | 112 + .../src/__tests__/amplify-admin-auth.test.ts | 337 + .../src/__tests__/conflict-resolution.test.ts | 70 + .../src/__tests__/field-auth-argument.test.ts | 79 + .../src/__tests__/group-auth.test.ts | 97 + .../src/__tests__/multi-auth.test.ts | 601 + .../src/__tests__/owner-auth.test.ts | 230 + .../src/__tests__/searchable-auth.test.ts | 124 + .../src/__tests__/test-helpers.ts | 10 + .../src/accesscontrol/acm.ts | 197 + .../src/accesscontrol/index.ts | 1 + .../src/graphql-auth-transformer.ts | 1083 + .../src/index.ts | 4 + .../src/resolvers/field.ts | 162 + .../src/resolvers/helpers.ts | 152 + .../src/resolvers/index.ts | 8 + .../src/resolvers/mutation.create.ts | 274 + .../src/resolvers/mutation.delete.ts | 189 + .../src/resolvers/mutation.update.ts | 318 + .../src/resolvers/query.ts | 347 + .../src/resolvers/search.ts | 271 + .../src/resolvers/subscriptions.ts | 71 + .../src/utils/constants.ts | 41 + .../src/utils/definitions.ts | 99 + .../src/utils/iam.ts | 70 + .../src/utils/index.ts | 105 + .../src/utils/schema.ts | 337 + .../src/utils/validations.ts | 123 + .../tsconfig.json | 15 + ...hql-default-value-transformer.test.ts.snap | 2 +- ...-graphql-function-transformer.test.ts.snap | 9 +- ...plify-graphql-function-transformer.test.ts | 4 +- .../src/graphql-function-transformer.ts | 55 +- ...ify-graphql-index-transformer.test.ts.snap | 3452 +++- ...aphql-primary-key-transformer.test.ts.snap | 1350 +- .../amplify-graphql-index-transformer.test.ts | 17 + .../src/graphql-index-transformer.ts | 5 +- .../src/resolvers.ts | 40 +- .../src/schema.ts | 14 +- .../model-transformer.test.ts.snap | 4372 +++- .../src/__tests__/model-transformer.test.ts | 4 +- .../src/definitions.ts | 2 +- .../src/graphql-model-transformer.ts | 86 +- .../src/graphql-types/query.ts | 2 +- .../src/index.ts | 3 +- .../src/resolvers/common.ts | 30 +- .../src/resolvers/mutation.ts | 3 - .../src/resolvers/query.ts | 108 +- .../src/wrappers/object-definition-wrapper.ts | 2 +- .../src/graphql-predictions-transformer.ts | 59 +- .../package.json | 1 + ...-graphql-has-many-transformer.test.ts.snap | 4887 +++-- ...phql-many-to-many-transformer.test.ts.snap | 1088 +- ...y-graphql-many-to-many-transformer.test.ts | 130 +- .../src/__tests__/relational-auth.test.ts | 192 + .../src/graphql-many-to-many-transformer.ts | 42 +- .../src/resolvers.ts | 132 +- .../src/schema.ts | 2 +- ...aphql-searchable-transformer.tests.ts.snap | 389 +- .../src/definitions.ts | 24 +- .../src/generate-resolver-vtl.ts | 73 +- .../src/graphql-searchable-transformer.ts | 22 +- .../src/config/index.ts | 21 +- .../src/config/transformer-config.ts | 58 +- .../src/graphql-api.ts | 19 +- .../src/index.ts | 28 +- .../src/transform-host.ts | 14 +- .../src/transformation/sync-utils.ts | 2 +- .../src/transformation/transform.ts | 20 +- .../transformation/transformer-plugin-base.ts | 7 + .../src/transformation/utils.ts | 1 + .../src/transformation/validation.ts | 22 + .../src/transformer-context/datasource.ts | 4 + .../src/transformer-context/index.ts | 26 + .../src/transformer-context/resolver.ts | 50 +- .../src/transformer-context/stack-manager.ts | 1 + .../src/utils/authType.ts | 12 +- .../src/utils/index.ts | 1 + .../src/graphql-api-provider.ts | 47 +- .../src/index.ts | 12 +- .../src/transform-host-provider.ts | 3 + .../stack-manager-provider.ts | 1 + .../transformer-context-provider.ts | 31 +- .../transformer-datasource-provider.ts | 1 + .../transformer-resolver-provider.ts | 1 + .../src/transformer-model-provider.ts | 2 + .../src/transformer-plugin-provider.ts | 1 + .../storage/1/AddStorageRequest.schema.json | 44 +- .../1/ImportStorageRequest.schema.json | 43 + .../1/RemoveStorageRequest.schema.json | 47 + .../1/UpdateStorageRequest.schema.json | 124 + .../scripts/generateSchemas.ts | 23 +- .../amplify-headless-interface/src/index.ts | 2 +- .../src/interface/api/add.ts | 1 + .../src/interface/storage/add.ts | 63 +- .../src/interface/storage/base.ts | 53 + .../src/interface/storage/import.ts | 19 + .../src/interface/storage/index.ts | 5 + .../src/interface/storage/remove.ts | 24 + .../src/interface/storage/update.ts | 29 + .../api.key.migration-2.test.ts | 5 +- .../api.key.migration.test.ts | 5 +- .../api.searchable.migration.test.ts | 5 +- .../update_tests/api_migration_update.test.ts | 17 +- .../function_migration_update.test.ts | 12 +- .../src/utils/legacyBuild.ts | 2 + .../src/__tests__/prompter.test.ts | 58 +- packages/amplify-prompts/src/demo/demo.ts | 5 +- packages/amplify-prompts/src/prompter.ts | 74 +- packages/amplify-prompts/src/validators.ts | 73 +- .../package.json | 2 + .../__snapshots__/utils.test.ts.snap | 114 + .../utils.test.ts | 236 + .../graphql-transformer/utils.test.ts | 240 + .../src/__tests__/initializer.test.ts | 4 +- .../__tests__/utils/api-key-helpers.test.ts | 91 + .../utils/sandbox-mode-helpers.test.ts | 117 + .../src/aws-utils/S3Service.ts | 26 +- .../src/aws-utils/aws-location.ts | 12 +- .../src/aws-utils/aws-pinpoint.js | 49 +- .../src/constants.js | 1 + .../disconnect-dependent-resources/index.ts | 64 + .../disconnect-dependent-resources/utils.ts | 183 + .../amplify-graphql-resource-manager.ts | 68 +- .../transform-graphql-schema.ts | 91 +- .../src/graphql-transformer/utils.ts | 122 +- .../src/index.ts | 6 +- .../deployment-manager.ts | 6 + .../cfn-pre-processor.ts | 2 +- .../src/push-resources.ts | 53 +- .../src/system-config-manager.ts | 4 +- .../src/transform-graphql-schema.ts | 66 +- .../src/utils/admin-helpers.ts | 5 +- .../src/utils/api-key-helpers.ts | 51 + .../src/utils/sandbox-mode-helpers.ts | 43 + .../add/invalidRequest.invalid.version.json | 11 + .../invalidRequest.missing.permissions.json | 12 + ...dRequest.missing.serviceConfiguration.json | 3 + .../invalidRequest.missing.serviceName.json | 15 + .../add/invalidRequest.missing.version.json | 10 + .../add/invalidRequest.string.version.json | 11 + .../storage/add/validAddStorageRequest.json | 19 + .../invalidRequest.missing.bucketName.json | 6 + ...dRequest.missing.serviceConfiguration.json | 3 + .../invalidRequest.missing.serviceName.json | 6 + .../invalidRequest.version.invalid.json | 3 + .../invalidRequest.version.missing.json | 1 + .../import/invalidRequest.version.string.json | 3 + .../import/valid.importStorageRequest.json | 7 + .../invalidRequest.invalid.version.json | 7 + .../invalidRequest.missing.resourceName.json} | 6 +- ...dRequest.missing.serviceConfiguration.json | 3 + .../invalidRequest.missing.serviceName.json | 18 + .../invalidRequest.missing.version.json | 18 + .../remove/invalidRequest.string.version.json | 7 + .../remove/validRemoveStorageRequest.json | 7 + .../invalidRequest.invalid.version.json | 10 + .../invalidRequest.missing.permissions.json | 12 + .../invalidRequest.missing.resourceName.json | 18 + ...idRequest.missing.serviceModification.json | 3 + .../invalidRequest.missing.serviceName.json | 18 + .../invalidRequest.missing.version.json | 18 + .../update/invalidRequest.string.version.json | 10 + .../update/validUpdateStorageRequest.json | 21 + .../import/__snapshots__/index.test.ts.snap | 1 - .../src/__tests__/auth/import/index.test.ts | 4 +- .../src/__tests__/index.test.ts | 18 - .../add}/__snapshots__/index.test.ts.snap | 9 +- .../src/__tests__/storage/add/index.test.ts | 51 + .../import/__snapshots__/index.test.ts.snap | 11 + .../__tests__/storage/import/index.test.ts | 49 + .../remove/__snapshots__/index.test.ts.snap | 11 + .../__tests__/storage/remove/index.test.ts | 50 + .../update/__snapshots__/index.test.ts.snap | 39 + .../__tests__/storage/update/index.test.ts | 52 + .../amplify-util-headless-input/src/index.ts | 42 +- .../src/schemaSuppliers.ts | 12 + .../src/ModelAuthTransformer.ts | 11 +- .../SearchableAuthTransformer.test.ts.snap | 4 +- .../graphql-auth-transformer/src/resources.ts | 8 +- .../ModelConnectionTransformer.test.ts.snap | 4 +- .../NewConnectionTransformer.test.ts.snap | 12 +- .../DynamoDBModelTransformer.test.ts.snap | 16 +- .../src/definitions.ts | 5 +- .../src/SearchableModelTransformer.ts | 3 +- .../SearchableModelTransformer.test.ts.snap | 24 +- .../graphql-mapping-template/src/print.ts | 2 +- .../src/ModelResourceIDs.ts | 6 + .../src/definition.ts | 2 +- .../__snapshots__/amplifyUtils.test.ts.snap | 28 + .../src/__tests__/util/amplifyUtils.test.ts | 20 + .../src/collectDirectives.ts | 3 + .../graphql-transformer-core/src/errors.ts | 33 +- .../src/util/amplifyUtils.ts | 55 +- .../src/util/sanity-check.ts | 222 +- .../package.json | 1 + .../src/IAMHelper.ts | 42 + .../__tests__/AuthV2Transformer.e2e.test.ts | 3329 ++++ .../DefaultValueTransformer.e2e.test.ts | 3 +- .../__tests__/HttpTransformerV2.e2e.test.ts | 443 + .../__tests__/IndexTransformer.e2e.test.ts | 1 + .../src/__tests__/IndexWithAuthV2.e2e.test.ts | 292 + .../__tests__/ModelTransformer.e2e.test.ts | 79 +- .../MultiAuthV2Transformer.e2e.test.ts | 1064 + .../NonModelAuthV2Function.e2e.test.ts | 243 + .../PerFieldAuthV2Transformer.e2e.test.ts | 624 + .../PredictionsTransformerV2Tests.e2e.test.ts | 184 + .../RelationalTransformers.e2e.test.ts | 14 +- .../RelationalWithAuthV2.e2e.test.ts | 511 + .../SearchableModelTransformerV2.e2e.test.ts | 15 +- .../SearchableWithAuthV2.e2e.test.ts | 925 + .../SubscriptionsWithAuthV2.e2e.test.ts | 1110 ++ .../src/__tests__/test-data/amazon.png | Bin 0 -> 42713 bytes .../src/__tests__/test-data/dogs.png | Bin 0 -> 195285 bytes .../src/cognitoUtils.ts | 41 +- .../src/deployNestedStacks.ts | 13 +- scripts/package.json | 23 + scripts/split-e2e-tests.ts | 132 +- scripts/yarn.lock | 337 + yarn.lock | 539 +- 415 files changed, 36658 insertions(+), 23580 deletions(-) create mode 100644 packages/amplify-appsync-simulator/src/__tests__/scalars/AWSIPAddress.test.ts create mode 100644 packages/amplify-appsync-simulator/src/__tests__/velocity/util/auth-utils.test.ts create mode 100644 packages/amplify-appsync-simulator/src/velocity/util/auth-utils.ts create mode 100644 packages/amplify-category-api/resources/awscloudformation/graphql-schemas/blank-schema.graphql create mode 100644 packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema-v2.graphql create mode 100644 packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema-v2.graphql create mode 100644 packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema-v2.graphql create mode 100644 packages/amplify-category-api/src/__tests__/commands/api/rebuild.test.ts create mode 100644 packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/prompt-to-add-api-key.test.ts create mode 100644 packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/global-sandbox-mode.test.ts create mode 100644 packages/amplify-category-api/src/commands/api/rebuild.ts create mode 100644 packages/amplify-category-api/src/provider-utils/awscloudformation/prompt-to-add-api-key.ts create mode 100644 packages/amplify-category-api/src/provider-utils/awscloudformation/utils/global-sandbox-mode.ts create mode 100644 packages/amplify-category-storage/src/provider-utils/awscloudformation/provider-constants.ts create mode 100644 packages/amplify-category-storage/src/provider-utils/awscloudformation/s3-trigger-helpers.ts create mode 100644 packages/amplify-category-storage/src/provider-utils/awscloudformation/storage-configuration-helpers.ts delete mode 100644 packages/amplify-category-storage/src/provider-utils/awscloudformation/storage-state-management.ts create mode 100644 packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-project-details.test.ts create mode 100644 packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta-2.json create mode 100644 packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta-3.json create mode 100644 packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta.json create mode 100644 packages/amplify-cli/src/__tests__/plugin-helpers/compare-plugins.test.ts create mode 100644 packages/amplify-cli/src/__tests__/plugin-helpers/platform-health-check.test.ts create mode 100644 packages/amplify-e2e-tests/schemas/custom_query.graphql create mode 100644 packages/amplify-e2e-tests/schemas/model_with_sandbox_mode.graphql create mode 100644 packages/amplify-e2e-tests/schemas/simple_model_new_primary_key.graphql create mode 100644 packages/amplify-e2e-tests/src/__tests__/api_6.test.ts create mode 100644 packages/amplify-e2e-tests/src/__tests__/import_s3_3.test.ts create mode 100644 packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration3.test.ts create mode 100644 packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration4.test.ts create mode 100644 packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration5.test.ts create mode 100644 packages/amplify-e2e-tests/src/__tests__/resolvers.test.ts create mode 100644 packages/amplify-e2e-tests/src/__tests__/storage-4.test.ts create mode 100644 packages/amplify-e2e-tests/src/__tests__/storage-5.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/.npmignore create mode 100644 packages/amplify-graphql-auth-transformer/CHANGELOG.md create mode 100644 packages/amplify-graphql-auth-transformer/package.json create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/__snapshots__/conflict-resolution.test.ts.snap create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/__snapshots__/field-auth-argument.test.ts.snap create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/accesscontrol.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/amplify-admin-auth.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/conflict-resolution.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/field-auth-argument.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/group-auth.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/multi-auth.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/owner-auth.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/searchable-auth.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/test-helpers.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/accesscontrol/acm.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/accesscontrol/index.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/index.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/field.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/helpers.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/index.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/mutation.create.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/mutation.delete.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/mutation.update.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/query.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/search.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/resolvers/subscriptions.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/utils/constants.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/utils/definitions.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/utils/iam.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/utils/index.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/utils/schema.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/utils/validations.ts create mode 100644 packages/amplify-graphql-auth-transformer/tsconfig.json create mode 100644 packages/amplify-graphql-relational-transformer/src/__tests__/relational-auth.test.ts create mode 100644 packages/amplify-headless-interface/schemas/storage/1/ImportStorageRequest.schema.json create mode 100644 packages/amplify-headless-interface/schemas/storage/1/RemoveStorageRequest.schema.json create mode 100644 packages/amplify-headless-interface/schemas/storage/1/UpdateStorageRequest.schema.json create mode 100644 packages/amplify-headless-interface/src/interface/storage/base.ts create mode 100644 packages/amplify-headless-interface/src/interface/storage/import.ts create mode 100644 packages/amplify-headless-interface/src/interface/storage/index.ts create mode 100644 packages/amplify-headless-interface/src/interface/storage/remove.ts create mode 100644 packages/amplify-headless-interface/src/interface/storage/update.ts create mode 100644 packages/amplify-provider-awscloudformation/src/__tests__/disconnect-dependent-resources/__snapshots__/utils.test.ts.snap create mode 100644 packages/amplify-provider-awscloudformation/src/__tests__/disconnect-dependent-resources/utils.test.ts create mode 100644 packages/amplify-provider-awscloudformation/src/__tests__/graphql-transformer/utils.test.ts create mode 100644 packages/amplify-provider-awscloudformation/src/__tests__/utils/api-key-helpers.test.ts create mode 100644 packages/amplify-provider-awscloudformation/src/__tests__/utils/sandbox-mode-helpers.test.ts create mode 100644 packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/index.ts create mode 100644 packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/utils.ts create mode 100644 packages/amplify-provider-awscloudformation/src/utils/api-key-helpers.ts create mode 100644 packages/amplify-provider-awscloudformation/src/utils/sandbox-mode-helpers.ts create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.invalid.version.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.permissions.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.serviceConfiguration.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.serviceName.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.version.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.string.version.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/add/validAddStorageRequest.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.bucketName.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.serviceConfiguration.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.serviceName.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.invalid.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.missing.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.string.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/import/valid.importStorageRequest.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.invalid.version.json rename packages/amplify-util-headless-input/src/__tests__/assets/{validAddStorageRequest.json => storage/remove/invalidRequest.missing.resourceName.json} (67%) create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.serviceConfiguration.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.serviceName.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.version.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.string.version.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/validRemoveStorageRequest.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.invalid.version.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.permissions.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.resourceName.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.serviceModification.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.serviceName.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.version.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.string.version.json create mode 100644 packages/amplify-util-headless-input/src/__tests__/assets/storage/update/validUpdateStorageRequest.json delete mode 100644 packages/amplify-util-headless-input/src/__tests__/index.test.ts rename packages/amplify-util-headless-input/src/__tests__/{ => storage/add}/__snapshots__/index.test.ts.snap (81%) create mode 100644 packages/amplify-util-headless-input/src/__tests__/storage/add/index.test.ts create mode 100644 packages/amplify-util-headless-input/src/__tests__/storage/import/__snapshots__/index.test.ts.snap create mode 100644 packages/amplify-util-headless-input/src/__tests__/storage/import/index.test.ts create mode 100644 packages/amplify-util-headless-input/src/__tests__/storage/remove/__snapshots__/index.test.ts.snap create mode 100644 packages/amplify-util-headless-input/src/__tests__/storage/remove/index.test.ts create mode 100644 packages/amplify-util-headless-input/src/__tests__/storage/update/__snapshots__/index.test.ts.snap create mode 100644 packages/amplify-util-headless-input/src/__tests__/storage/update/index.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/AuthV2Transformer.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/HttpTransformerV2.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/IndexWithAuthV2.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/MultiAuthV2Transformer.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/NonModelAuthV2Function.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/PerFieldAuthV2Transformer.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/PredictionsTransformerV2Tests.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/RelationalWithAuthV2.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/SearchableWithAuthV2.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/SubscriptionsWithAuthV2.e2e.test.ts create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/test-data/amazon.png create mode 100644 packages/graphql-transformers-e2e-tests/src/__tests__/test-data/dogs.png create mode 100644 scripts/package.json create mode 100644 scripts/yarn.lock diff --git a/.circleci/config.base.yml b/.circleci/config.base.yml index cfbce655195..e5aee02f167 100644 --- a/.circleci/config.base.yml +++ b/.circleci/config.base.yml @@ -1064,10 +1064,11 @@ commands: steps: - run: name: Run E2e Tests - shell: powershell.exe + shell: bash.exe command: | + source .circleci/local_publish_helpers.sh cd packages/amplify-e2e-tests - yarn run e2e --detectOpenHandles --maxWorkers=3 $env:TEST_SUITE + retry yarn run e2e --detectOpenHandles --maxWorkers=3 $TEST_SUITE no_output_timeout: 90m - when: condition: diff --git a/.circleci/config.yml b/.circleci/config.yml index 9ee5d3d79e0..c73e22f9fdd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,21 +1,25 @@ -# auto generated file. Edit config.base.yaml if you want to change version: 2.1 + +# this allows you to use CircleCI's dynamic configuration feature +setup: true + +# the continuation orb is required in order to use dynamic configuration orbs: - aws-ecr: circleci/aws-ecr@6.15.3 -machine: - environment: - PATH: ${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin + continuation: circleci/continuation@0.1.2 + +parameters: + nightly_console_integration_tests: + type: boolean + default: false + e2e_resource_cleanup: + type: boolean + default: false + setup: + type: boolean + default: true + executors: - windows: - machine: - image: windows-server-2019-vs2019:stable - resource_class: windows.large - shell: bash.exe - working_directory: ~/repo - environment: - AMPLIFY_DIR: C:/home/circleci/repo/out - AMPLIFY_PATH: C:/home/circleci/repo/out/amplify.exe - linux: + linux: &linux-e2e-executor docker: - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest working_directory: ~/repo @@ -23,40 +27,21 @@ executors: environment: AMPLIFY_DIR: /home/circleci/repo/out AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux -defaults: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> -clean_e2e_resources: - name: Cleanup resources - command: | - pwd - cd packages/amplify-e2e-tests - yarn clean-e2e-resources job ${CIRCLE_BUILD_NUM} - working_directory: ~/repo -scan_e2e_test_artifacts: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always -install_cli_from_local_registery: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl + +# our defined job, and its steps jobs: + setup: + executor: 'linux' + steps: + - checkout # checkout code + - run: # run a command + name: Generate config + command: | + cd scripts + yarn + yarn split-e2e-tests + - continuation/continue: + configuration_path: .circleci/generated_config.yml # use newly generated config to continue build: parameters: os: @@ -92,62 +77,6 @@ jobs: - persist_to_workspace: root: . paths: . - test: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Install Java - command: sudo apt-get update && sudo apt-get install default-jdk - - run: - name: Lint - command: yarn lint - - run: - name: Run tests - command: yarn test-ci - - run: - name: Collect code coverage - command: yarn coverage - mock_e2e_tests: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Install Java - command: sudo apt-get update && sudo apt-get install default-jdk - - run: - name: Run Transformer end-to-end tests with mock server - command: | - source .circleci/local_publish_helpers.sh - cd packages/amplify-util-mock/ - yarn e2e - no_output_timeout: 90m - environment: - JEST_JUNIT_OUTPUT: reports/junit/js-test-results.xml - - store_test_results: - path: packages/amplify-util-mock/ publish_to_local_registry: docker: - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest @@ -195,89 +124,7 @@ jobs: key: amplfiy-pkg-tag-{{ .Branch }}-{{ .Revision }} paths: - ~/repo/.amplify-pkg-version - build_pkg_binaries: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio and package CLI - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - yarn pkg-all - unsetNpmRegistryUrl - - save_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - paths: - - ~/repo/out - graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - amplify_sudo_install_test: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Update OS Packages - command: sudo apt-get update - - run: - name: Start verdaccio and Install Amplify CLI as sudo - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setSudoNpmRegistryUrlToLocal - changeSudoNpmGlobalPath - sudo npm install -g @aws-amplify/cli - unsetSudoNpmRegistryUrl - amplify version - amplify_e2e_tests: + amplify_console_integration_tests: working_directory: ~/repo parameters: os: @@ -287,14 +134,8 @@ jobs: steps: - attach_workspace: at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - restore_cache: key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - run: name: Start verdaccio, install node CLI and amplify-app command: | @@ -306,122 +147,14 @@ jobs: npm install -g amplify-app unsetNpmRegistryUrl - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - amplify_migration_tests_non_multi_env_layers: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - environment: - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run tests migrating from CLI v4.28.2 - command: > + echo "export PATH=~/.npm-global/bin:$PATH" >> $BASH_ENV + source $BASH_ENV source .circleci/local_publish_helpers.sh - - changeNpmGlobalPath - - cd packages/amplify-migration-tests - - retry yarn run migration_v4.28.2_nonmultienv_layers --maxWorkers=3 - $TEST_SUITE + amplify -v + cd packages/amplify-console-integration-tests + retry yarn run console-integration --maxWorkers=3 + name: Run Amplify Console integration tests no_output_timeout: 90m - run: name: Scan And Cleanup E2E Test Artifacts @@ -433,18 +166,18 @@ jobs: fi when: always - store_test_results: - path: packages/amplify-migration-tests/ + path: packages/amplify-console-integration-tests/ - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - amplify_migration_tests_multi_env_layers: + path: >- + ~/repo/packages/amplify-console-integration-tests/console-integration-reports + cleanup_resources: + docker: + - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> + resource_class: large environment: - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify + AMPLIFY_DIR: /home/circleci/repo/out + AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux steps: - attach_workspace: at: ./ @@ -452,54 +185,11 @@ jobs: key: >- amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.52.0 - command: > - source .circleci/local_publish_helpers.sh - - changeNpmGlobalPath - - cd packages/amplify-migration-tests - - retry yarn run migration_v4.52.0_multienv_layers --maxWorkers=3 - $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - amplify_migration_tests_overrides: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - environment: - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }} - run: - name: Run tests migrating from CLI v6.0.1 + name: Run cleanup script command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - yarn run migration_v6.0.1 --maxWorkers=3 $TEST_SUITE + cd packages/amplify-e2e-tests + yarn clean-e2e-resources no_output_timeout: 90m - run: name: Scan And Cleanup E2E Test Artifacts @@ -510,16220 +200,38 @@ jobs: exit 1 fi when: always - - store_test_results: - path: packages/amplify-migration-tests/ - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - amplify_migration_tests_v4_30_0: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - environment: - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Update OS Packages - command: sudo apt-get update - - run: - name: Run tests migrating from CLI v4.30.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.30.0_auth --maxWorkers=3 - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - amplify_migration_tests_latest: - environment: - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - amplify_console_integration_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - command: | - echo "export PATH=~/.npm-global/bin:$PATH" >> $BASH_ENV - source $BASH_ENV - source .circleci/local_publish_helpers.sh - amplify -v - cd packages/amplify-console-integration-tests - retry yarn run console-integration --maxWorkers=3 - name: Run Amplify Console integration tests - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-console-integration-tests/ - - store_artifacts: - path: >- - ~/repo/packages/amplify-console-integration-tests/console-integration-reports - integration_test: - working_directory: ~/repo - resource_class: large - docker: - - image: cypress/base:12 - environment: - TERM: dumb - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - run: - name: Setup Dependencies - command: | - apt-get update - apt-get install -y sudo - sudo apt-get install -y tcl - sudo apt-get install -y expect - sudo apt-get install -y zip - sudo apt-get install -y lsof - sudo apt-get install -y python python-pip libpython-dev - sudo apt-get install -y jq - pip install awscli - - run: cd .circleci/ && chmod +x aws.sh - - run: expect .circleci/aws_configure.exp - - run: - name: Configure Amplify CLI - command: > - yarn rm-dev-link && yarn link-dev && yarn rm-aa-dev-link && yarn - link-aa-dev - - echo 'export PATH="$(yarn global bin):$PATH"' >> $BASH_ENV - - amplify-dev - - run: - name: Clone auth test package - command: | - cd .. - git clone $AUTH_CLONE_URL - cd aws-amplify-cypress-auth - yarn --cache-folder ~/.cache/yarn - - run: cd .circleci/ && chmod +x auth.sh - - run: cd .circleci/ && chmod +x amplify_init.sh - - run: cd .circleci/ && chmod +x amplify_init.exp - - run: expect .circleci/amplify_init.exp ../aws-amplify-cypress-auth - - run: expect .circleci/enable_auth.exp - - run: cd ../aws-amplify-cypress-auth - - run: yarn --frozen-lockfile --cache-folder ~/.cache/yarn - - run: >- - cd ../aws-amplify-cypress-auth/src && cat $(find . -type f -name - 'aws-exports*') - - run: - name: Start Auth test server in background - command: | - cd ../aws-amplify-cypress-auth - pwd - yarn start - background: true - - run: cat $(find ../repo -type f -name 'auth_spec*') - - run: - name: Run cypress tests for auth - command: | - cd ../aws-amplify-cypress-auth - yarn add cypress@6.8.0 --save - cp ../repo/cypress.json . - cp -R ../repo/cypress . - yarn cypress run --spec $(find . -type f -name 'auth_spec*') - - run: sudo kill -9 $(lsof -t -i:3000) - - run: cd .circleci/ && chmod +x delete_auth.sh - - run: expect .circleci/delete_auth.exp - - run: - name: Clone API test package - command: | - cd .. - git clone $API_CLONE_URL - cd aws-amplify-cypress-api - yarn --cache-folder ~/.cache/yarn - - run: cd .circleci/ && chmod +x api.sh - - run: expect .circleci/amplify_init.exp ../aws-amplify-cypress-api - - run: expect .circleci/enable_api.exp - - run: cd ../aws-amplify-cypress-api - - run: yarn --frozen-lockfile --cache-folder ~/.cache/yarn - - run: >- - cd ../aws-amplify-cypress-api/src && cat $(find . -type f -name - 'aws-exports*') - - run: - name: Start API test server in background - command: | - cd ../aws-amplify-cypress-api - pwd - yarn start - background: true - - run: - name: Run cypress tests for api - command: | - cd ../aws-amplify-cypress-api - yarn add cypress@6.8.0 --save - cp ../repo/cypress.json . - cp -R ../repo/cypress . - yarn cypress run --spec $(find . -type f -name 'api_spec*') - - run: cd .circleci/ && chmod +x delete_api.sh - - run: expect .circleci/delete_api.exp - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_artifacts: - path: /root/aws-amplify-cypress-auth/cypress/videos - - store_artifacts: - path: /root/aws-amplify-cypress-auth/cypress/screenshots - - store_artifacts: - path: /root/aws-amplify-cypress-api/cypress/videos - - store_artifacts: - path: /root/aws-amplify-cypress-api/cypress/screenshots - deploy: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - attach_workspace: - at: ./ - - restore_cache: - keys: - - amplify-cli-ssh-deps-{{ .Branch }} - - run: - name: Authenticate with npm - command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc - - run: - name: Publish Amplify CLI - command: | - bash ./.circleci/publish.sh - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - github_prerelease: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: amplify-unified-changelog-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplfiy-pkg-tag-{{ .Branch }}-{{ .Revision }} - - run: - name: Compress binaries - command: | - cd out - tar zcvf amplify-pkg-macos.tgz amplify-pkg-macos - tar zcvf amplify-pkg-linux.tgz amplify-pkg-linux - tar zcvf amplify-pkg-win.exe.tgz amplify-pkg-win.exe - - run: - name: Publish Amplify CLI GitHub prerelease - command: | - version=$(cat .amplify-pkg-version) - yarn ts-node scripts/github-prerelease.ts $version - github_prerelease_install_sanity_check: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - restore_cache: - key: amplfiy-pkg-tag-{{ .Branch }}-{{ .Revision }} - - run: - name: Install packaged Amplify CLI - command: > - version=$(cat .amplify-pkg-version) - - curl -sL https://aws-amplify.github.io/amplify-cli/install | - version=v$version bash - - echo "export PATH=$PATH:$HOME/.amplify/bin" >> $BASH_ENV - - run: - name: Sanity check install - command: | - amplify version - github_release: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplfiy-pkg-tag-{{ .Branch }}-{{ .Revision }} - - run: - name: Publish Amplify CLI GitHub release - command: | - version=$(cat .amplify-pkg-version) - yarn ts-node scripts/github-release.ts $version - cleanup_resources: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run cleanup script - command: | - cd packages/amplify-e2e-tests - yarn clean-e2e-resources - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - cleanup_resources_after_e2e_runs: - docker: - - image: public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run cleanup script - command: | - cd packages/amplify-e2e-tests - yarn clean-e2e-resources workflow ${CIRCLE_WORKFLOW_ID} - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - api_4-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/api_4.test.ts - CLI_REGION: us-east-2 - auth_6-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/auth_6.test.ts - CLI_REGION: us-west-2 - auth_7-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/auth_7.test.ts - CLI_REGION: eu-west-2 - auth_8-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/auth_8.test.ts - CLI_REGION: eu-central-1 - configure-project-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/configure-project.test.ts - CLI_REGION: ap-northeast-1 - container-hosting-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/container-hosting.test.ts - CLI_REGION: ap-southeast-1 - custom_policies_container-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/custom_policies_container.test.ts - CLI_REGION: ap-southeast-2 - custom_policies_function-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/custom_policies_function.test.ts - CLI_REGION: us-east-2 - frontend_config_drift-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/frontend_config_drift.test.ts - CLI_REGION: us-west-2 - function_5-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_5.test.ts - CLI_REGION: eu-west-2 - function_6-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_6.test.ts - CLI_REGION: eu-central-1 - function_7-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_7.test.ts - CLI_REGION: ap-northeast-1 - function_8-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_8.test.ts - CLI_REGION: ap-southeast-1 - function_9-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_9.test.ts - CLI_REGION: ap-southeast-2 - hooks-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/hooks.test.ts - CLI_REGION: us-east-2 - iam-permissions-boundary-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/iam-permissions-boundary.test.ts - CLI_REGION: us-west-2 - import_auth_3-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/import_auth_3.test.ts - CLI_REGION: eu-west-2 - import_dynamodb_2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/import_dynamodb_2.test.ts - CLI_REGION: eu-central-1 - import_s3_2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/import_s3_2.test.ts - CLI_REGION: ap-northeast-1 - layer-1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/layer-1.test.ts - CLI_REGION: ap-southeast-1 - layer-2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/layer-2.test.ts - CLI_REGION: ap-southeast-2 - layer-3-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/layer-3.test.ts - CLI_REGION: us-east-2 - layer-4-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/layer-4.test.ts - CLI_REGION: us-west-2 - migration-api-connection-migration2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/migration/api.connection.migration2.test.ts - CLI_REGION: eu-west-2 - migration-node-function-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/migration/node.function.test.ts - CLI_REGION: eu-central-1 - pull-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/pull.test.ts - CLI_REGION: ap-northeast-1 - s3-sse-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/s3-sse.test.ts - CLI_REGION: ap-southeast-1 - schema-auth-12-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-12.test.ts - CLI_REGION: ap-southeast-2 - schema-auth-13-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-13.test.ts - CLI_REGION: us-east-2 - schema-function-1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-function-1.test.ts - CLI_REGION: us-west-2 - schema-function-2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-function-2.test.ts - CLI_REGION: eu-west-2 - schema-iterative-update-locking-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-iterative-update-locking.test.ts - CLI_REGION: eu-central-1 - storage-1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/storage-1.test.ts - CLI_REGION: ap-northeast-1 - storage-2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/storage-2.test.ts - CLI_REGION: ap-southeast-1 - storage-3-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/storage-3.test.ts - CLI_REGION: ap-southeast-2 - plugin-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/plugin.test.ts - CLI_REGION: us-east-2 - init-special-case-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/init-special-case.test.ts - CLI_REGION: us-west-2 - datastore-modelgen-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/datastore-modelgen.test.ts - CLI_REGION: eu-west-2 - amplify-configure-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/amplify-configure.test.ts - CLI_REGION: eu-central-1 - init-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/init.test.ts - CLI_REGION: ap-northeast-1 - tags-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/tags.test.ts - CLI_REGION: ap-southeast-1 - notifications-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/notifications.test.ts - CLI_REGION: ap-southeast-2 - schema-versioned-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-versioned.test.ts - CLI_REGION: us-east-2 - schema-data-access-patterns-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-data-access-patterns.test.ts - CLI_REGION: us-west-2 - interactions-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/interactions.test.ts - CLI_REGION: us-west-2 - schema-predictions-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-predictions.test.ts - CLI_REGION: eu-central-1 - amplify-app-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/amplify-app.test.ts - CLI_REGION: ap-northeast-1 - hosting-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/hosting.test.ts - CLI_REGION: ap-southeast-1 - analytics-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/analytics.test.ts - CLI_REGION: ap-southeast-2 - feature-flags-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/feature-flags.test.ts - CLI_REGION: us-east-2 - schema-iterative-update-2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-iterative-update-2.test.ts - CLI_REGION: us-west-2 - containers-api-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/containers-api.test.ts - CLI_REGION: eu-west-2 - predictions-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/predictions.test.ts - CLI_REGION: eu-central-1 - hostingPROD-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/hostingPROD.test.ts - CLI_REGION: ap-northeast-1 - geo-add-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/geo-add.test.ts - CLI_REGION: ap-southeast-1 - geo-update-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/geo-update.test.ts - CLI_REGION: ap-southeast-2 - geo-remove-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/geo-remove.test.ts - CLI_REGION: us-east-2 - schema-auth-10-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-10.test.ts - CLI_REGION: us-west-2 - schema-key-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-key.test.ts - CLI_REGION: eu-west-2 - auth_1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/auth_1.test.ts - CLI_REGION: eu-central-1 - auth_5-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/auth_5.test.ts - CLI_REGION: ap-northeast-1 - function_3-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_3.test.ts - CLI_REGION: ap-southeast-1 - schema-iterative-update-1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-iterative-update-1.test.ts - CLI_REGION: ap-southeast-2 - schema-auth-3-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-3.test.ts - CLI_REGION: us-east-2 - delete-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/delete.test.ts - CLI_REGION: us-west-2 - function_2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_2.test.ts - CLI_REGION: eu-west-2 - auth_3-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/auth_3.test.ts - CLI_REGION: eu-central-1 - migration-api-key-migration1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/migration/api.key.migration1.test.ts - CLI_REGION: ap-northeast-1 - auth_4-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/auth_4.test.ts - CLI_REGION: ap-southeast-1 - schema-auth-7-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-7.test.ts - CLI_REGION: ap-southeast-2 - schema-auth-8-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-8.test.ts - CLI_REGION: us-east-2 - schema-searchable-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-searchable.test.ts - CLI_REGION: us-west-2 - schema-auth-4-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-4.test.ts - CLI_REGION: eu-west-2 - api_3-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/api_3.test.ts - CLI_REGION: eu-central-1 - import_auth_1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/import_auth_1.test.ts - CLI_REGION: ap-northeast-1 - import_auth_2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/import_auth_2.test.ts - CLI_REGION: ap-southeast-1 - import_s3_1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/import_s3_1.test.ts - CLI_REGION: ap-southeast-2 - USE_PARENT_ACCOUNT: 1 - import_dynamodb_1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/import_dynamodb_1.test.ts - CLI_REGION: us-east-2 - USE_PARENT_ACCOUNT: 1 - schema-iterative-rollback-1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-iterative-rollback-1.test.ts - CLI_REGION: us-west-2 - schema-iterative-rollback-2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-iterative-rollback-2.test.ts - CLI_REGION: eu-west-2 - env-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/env.test.ts - CLI_REGION: eu-central-1 - auth_2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/auth_2.test.ts - CLI_REGION: ap-northeast-1 - USE_PARENT_ACCOUNT: 1 - schema-auth-9-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-9.test.ts - CLI_REGION: ap-southeast-1 - schema-auth-11-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-11.test.ts - CLI_REGION: ap-southeast-2 - migration-api-key-migration2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/migration/api.key.migration2.test.ts - CLI_REGION: us-east-2 - USE_PARENT_ACCOUNT: 1 - function_1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_1.test.ts - CLI_REGION: us-west-2 - schema-auth-1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-1.test.ts - CLI_REGION: eu-west-2 - function_4-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/function_4.test.ts - CLI_REGION: eu-central-1 - schema-model-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-model.test.ts - CLI_REGION: ap-northeast-1 - migration-api-connection-migration-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/migration/api.connection.migration.test.ts - CLI_REGION: ap-southeast-1 - schema-connection-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-connection.test.ts - CLI_REGION: ap-southeast-2 - schema-auth-6-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-6.test.ts - CLI_REGION: us-east-2 - schema-iterative-update-3-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-iterative-update-3.test.ts - CLI_REGION: us-west-2 - schema-auth-2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-2.test.ts - CLI_REGION: eu-west-2 - api_1-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/api_1.test.ts - CLI_REGION: eu-central-1 - USE_PARENT_ACCOUNT: 1 - schema-auth-5-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-auth-5.test.ts - CLI_REGION: ap-northeast-1 - api_2-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/api_2.test.ts - CLI_REGION: ap-southeast-1 - USE_PARENT_ACCOUNT: 1 - api_5-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/api_5.test.ts - CLI_REGION: ap-southeast-2 - schema-iterative-update-4-amplify_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Start verdaccio, install node CLI and amplify-app - command: | - source .circleci/local_publish_helpers.sh - startLocalRegistry "$(pwd)/.circleci/verdaccio.yaml" - setNpmRegistryUrlToLocal - changeNpmGlobalPath - npm install -g @aws-amplify/cli - npm install -g amplify-app - unsetNpmRegistryUrl - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - retry runE2eTest - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/schema-iterative-update-4.test.ts - CLI_REGION: us-east-2 - api_4-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/api_4.test.ts - CLI_REGION: us-east-2 - auth_6-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/auth_6.test.ts - CLI_REGION: us-west-2 - auth_7-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/auth_7.test.ts - CLI_REGION: eu-west-2 - auth_8-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/auth_8.test.ts - CLI_REGION: eu-central-1 - configure-project-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/configure-project.test.ts - CLI_REGION: ap-northeast-1 - container-hosting-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/container-hosting.test.ts - CLI_REGION: ap-southeast-1 - custom_policies_container-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/custom_policies_container.test.ts - CLI_REGION: ap-southeast-2 - custom_policies_function-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/custom_policies_function.test.ts - CLI_REGION: us-east-2 - frontend_config_drift-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/frontend_config_drift.test.ts - CLI_REGION: us-west-2 - function_5-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_5.test.ts - CLI_REGION: eu-west-2 - function_6-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_6.test.ts - CLI_REGION: eu-central-1 - function_7-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_7.test.ts - CLI_REGION: ap-northeast-1 - function_8-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_8.test.ts - CLI_REGION: ap-southeast-1 - function_9-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_9.test.ts - CLI_REGION: ap-southeast-2 - hooks-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/hooks.test.ts - CLI_REGION: us-east-2 - iam-permissions-boundary-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/iam-permissions-boundary.test.ts - CLI_REGION: us-west-2 - import_auth_3-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/import_auth_3.test.ts - CLI_REGION: eu-west-2 - import_dynamodb_2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/import_dynamodb_2.test.ts - CLI_REGION: eu-central-1 - import_s3_2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/import_s3_2.test.ts - CLI_REGION: ap-northeast-1 - layer-1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/layer-1.test.ts - CLI_REGION: ap-southeast-1 - layer-2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/layer-2.test.ts - CLI_REGION: ap-southeast-2 - layer-3-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/layer-3.test.ts - CLI_REGION: us-east-2 - layer-4-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/layer-4.test.ts - CLI_REGION: us-west-2 - migration-api-connection-migration2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/migration/api.connection.migration2.test.ts - CLI_REGION: eu-west-2 - migration-node-function-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/migration/node.function.test.ts - CLI_REGION: eu-central-1 - pull-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/pull.test.ts - CLI_REGION: ap-northeast-1 - s3-sse-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/s3-sse.test.ts - CLI_REGION: ap-southeast-1 - schema-auth-12-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-12.test.ts - CLI_REGION: ap-southeast-2 - schema-auth-13-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-13.test.ts - CLI_REGION: us-east-2 - schema-function-1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-function-1.test.ts - CLI_REGION: us-west-2 - schema-function-2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-function-2.test.ts - CLI_REGION: eu-west-2 - schema-iterative-update-locking-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-iterative-update-locking.test.ts - CLI_REGION: eu-central-1 - storage-1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/storage-1.test.ts - CLI_REGION: ap-northeast-1 - storage-2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/storage-2.test.ts - CLI_REGION: ap-southeast-1 - storage-3-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/storage-3.test.ts - CLI_REGION: ap-southeast-2 - plugin-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/plugin.test.ts - CLI_REGION: us-east-2 - init-special-case-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/init-special-case.test.ts - CLI_REGION: us-west-2 - datastore-modelgen-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/datastore-modelgen.test.ts - CLI_REGION: eu-west-2 - amplify-configure-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/amplify-configure.test.ts - CLI_REGION: eu-central-1 - init-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/init.test.ts - CLI_REGION: ap-northeast-1 - tags-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/tags.test.ts - CLI_REGION: ap-southeast-1 - notifications-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/notifications.test.ts - CLI_REGION: ap-southeast-2 - schema-versioned-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-versioned.test.ts - CLI_REGION: us-east-2 - schema-data-access-patterns-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-data-access-patterns.test.ts - CLI_REGION: us-west-2 - interactions-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/interactions.test.ts - CLI_REGION: us-west-2 - schema-predictions-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-predictions.test.ts - CLI_REGION: eu-central-1 - amplify-app-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/amplify-app.test.ts - CLI_REGION: ap-northeast-1 - hosting-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/hosting.test.ts - CLI_REGION: ap-southeast-1 - analytics-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/analytics.test.ts - CLI_REGION: ap-southeast-2 - feature-flags-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/feature-flags.test.ts - CLI_REGION: us-east-2 - schema-iterative-update-2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-iterative-update-2.test.ts - CLI_REGION: us-west-2 - containers-api-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/containers-api.test.ts - CLI_REGION: eu-west-2 - predictions-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/predictions.test.ts - CLI_REGION: eu-central-1 - hostingPROD-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/hostingPROD.test.ts - CLI_REGION: ap-northeast-1 - geo-add-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/geo-add.test.ts - CLI_REGION: ap-southeast-1 - geo-update-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/geo-update.test.ts - CLI_REGION: ap-southeast-2 - geo-remove-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/geo-remove.test.ts - CLI_REGION: us-east-2 - schema-auth-10-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-10.test.ts - CLI_REGION: us-west-2 - schema-key-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-key.test.ts - CLI_REGION: eu-west-2 - auth_1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/auth_1.test.ts - CLI_REGION: eu-central-1 - auth_5-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/auth_5.test.ts - CLI_REGION: ap-northeast-1 - function_3-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_3.test.ts - CLI_REGION: ap-southeast-1 - schema-iterative-update-1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-iterative-update-1.test.ts - CLI_REGION: ap-southeast-2 - schema-auth-3-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-3.test.ts - CLI_REGION: us-east-2 - delete-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/delete.test.ts - CLI_REGION: us-west-2 - function_2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_2.test.ts - CLI_REGION: eu-west-2 - auth_3-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/auth_3.test.ts - CLI_REGION: eu-central-1 - migration-api-key-migration1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/migration/api.key.migration1.test.ts - CLI_REGION: ap-northeast-1 - auth_4-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/auth_4.test.ts - CLI_REGION: ap-southeast-1 - schema-auth-7-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-7.test.ts - CLI_REGION: ap-southeast-2 - schema-auth-8-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-8.test.ts - CLI_REGION: us-east-2 - schema-searchable-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-searchable.test.ts - CLI_REGION: us-west-2 - schema-auth-4-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-4.test.ts - CLI_REGION: eu-west-2 - api_3-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/api_3.test.ts - CLI_REGION: eu-central-1 - import_auth_1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/import_auth_1.test.ts - CLI_REGION: ap-northeast-1 - import_auth_2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/import_auth_2.test.ts - CLI_REGION: ap-southeast-1 - import_s3_1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/import_s3_1.test.ts - CLI_REGION: ap-southeast-2 - USE_PARENT_ACCOUNT: 1 - import_dynamodb_1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/import_dynamodb_1.test.ts - CLI_REGION: us-east-2 - USE_PARENT_ACCOUNT: 1 - schema-iterative-rollback-1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-iterative-rollback-1.test.ts - CLI_REGION: us-west-2 - schema-iterative-rollback-2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-iterative-rollback-2.test.ts - CLI_REGION: eu-west-2 - env-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/env.test.ts - CLI_REGION: eu-central-1 - auth_2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/auth_2.test.ts - CLI_REGION: ap-northeast-1 - USE_PARENT_ACCOUNT: 1 - schema-auth-9-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-9.test.ts - CLI_REGION: ap-southeast-1 - schema-auth-11-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-11.test.ts - CLI_REGION: ap-southeast-2 - migration-api-key-migration2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/migration/api.key.migration2.test.ts - CLI_REGION: us-east-2 - USE_PARENT_ACCOUNT: 1 - function_1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_1.test.ts - CLI_REGION: us-west-2 - schema-auth-1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-1.test.ts - CLI_REGION: eu-west-2 - function_4-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/function_4.test.ts - CLI_REGION: eu-central-1 - schema-model-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-model.test.ts - CLI_REGION: ap-northeast-1 - migration-api-connection-migration-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/migration/api.connection.migration.test.ts - CLI_REGION: ap-southeast-1 - schema-connection-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-connection.test.ts - CLI_REGION: ap-southeast-2 - schema-auth-6-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-6.test.ts - CLI_REGION: us-east-2 - schema-iterative-update-3-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-iterative-update-3.test.ts - CLI_REGION: us-west-2 - schema-auth-2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-2.test.ts - CLI_REGION: eu-west-2 - api_1-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/api_1.test.ts - CLI_REGION: eu-central-1 - USE_PARENT_ACCOUNT: 1 - schema-auth-5-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-auth-5.test.ts - CLI_REGION: ap-northeast-1 - api_2-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/api_2.test.ts - CLI_REGION: ap-southeast-1 - USE_PARENT_ACCOUNT: 1 - api_5-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/api_5.test.ts - CLI_REGION: ap-southeast-2 - schema-iterative-update-4-amplify_e2e_tests_pkg: - parameters: - os: - type: executor - default: os.linux - executor: << parameters.os >> - working_directory: ~/repo - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-verdaccio-cache-{{ .Branch }}-{{ .Revision }} - - restore_cache: - key: amplify-build-artifact-{{ .Revision }}-{{ arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - install_yarn: - os: << parameters.os >> - - install_packaged_cli: - os: << parameters.os >> - - run_e2e_tests: - os: << parameters.os >> - - scan_e2e_test_artifacts: - os: << parameters.os >> - - store_test_results: - path: packages/amplify-e2e-tests/ - - store_artifacts: - path: packages/amplify-e2e-tests/amplify-e2e-reports - environment: - TEST_SUITE: src/__tests__/schema-iterative-update-4.test.ts - CLI_REGION: us-east-2 - ConnectionsWithAuthTests-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/ConnectionsWithAuthTests.e2e.test.ts - CLI_REGION: us-east-2 - CustomRoots-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/CustomRoots.e2e.test.ts - CLI_REGION: us-west-2 - DefaultValueTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/DefaultValueTransformer.e2e.test.ts - CLI_REGION: eu-west-2 - DynamoDBModelTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/DynamoDBModelTransformer.e2e.test.ts - CLI_REGION: eu-central-1 - FunctionTransformerTests-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/FunctionTransformerTests.e2e.test.ts - CLI_REGION: ap-northeast-1 - HttpTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/HttpTransformer.e2e.test.ts - CLI_REGION: ap-southeast-1 - IndexTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/IndexTransformer.e2e.test.ts - CLI_REGION: ap-southeast-2 - KeyTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/KeyTransformer.e2e.test.ts - CLI_REGION: us-east-2 - KeyTransformerLocal-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/KeyTransformerLocal.e2e.test.ts - CLI_REGION: us-west-2 - KeyWithAuth-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/KeyWithAuth.e2e.test.ts - CLI_REGION: eu-west-2 - ModelAuthTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/ModelAuthTransformer.e2e.test.ts - CLI_REGION: eu-central-1 - ModelConnectionTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/ModelConnectionTransformer.e2e.test.ts - CLI_REGION: ap-northeast-1 - ModelConnectionWithKeyTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/ModelConnectionWithKeyTransformer.e2e.test.ts - CLI_REGION: ap-southeast-1 - ModelTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/ModelTransformer.e2e.test.ts - CLI_REGION: ap-southeast-2 - MultiAuthModelAuthTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/MultiAuthModelAuthTransformer.e2e.test.ts - CLI_REGION: us-east-2 - MutationCondition-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/MutationCondition.e2e.test.ts - CLI_REGION: us-west-2 - NestedStacksTest-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/NestedStacksTest.e2e.test.ts - CLI_REGION: eu-west-2 - NewConnectionTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/NewConnectionTransformer.e2e.test.ts - CLI_REGION: eu-central-1 - NewConnectionWithAuth-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/NewConnectionWithAuth.e2e.test.ts - CLI_REGION: ap-northeast-1 - NoneEnvFunctionTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/NoneEnvFunctionTransformer.e2e.test.ts - CLI_REGION: ap-southeast-1 - NonModelAuthFunction-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/NonModelAuthFunction.e2e.test.ts - CLI_REGION: ap-southeast-2 - PerFieldAuthTests-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/PerFieldAuthTests.e2e.test.ts - CLI_REGION: us-east-2 - PredictionsTransformerTests-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/PredictionsTransformerTests.e2e.test.ts - CLI_REGION: us-west-2 - RelationalTransformers-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/RelationalTransformers.e2e.test.ts - CLI_REGION: eu-west-2 - SearchableModelTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/SearchableModelTransformer.e2e.test.ts - CLI_REGION: eu-central-1 - SearchableModelTransformerV2-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/SearchableModelTransformerV2.e2e.test.ts - CLI_REGION: ap-northeast-1 - SearchableWithAuthTests-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/SearchableWithAuthTests.e2e.test.ts - CLI_REGION: ap-southeast-1 - SubscriptionsWithAuthTest-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/SubscriptionsWithAuthTest.e2e.test.ts - CLI_REGION: ap-southeast-2 - TestComplexStackMappingsLocal-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/TestComplexStackMappingsLocal.e2e.test.ts - CLI_REGION: us-east-2 - VersionedModelTransformer-e2e-graphql_e2e_tests: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - run: - name: Run GraphQL end-to-end tests - command: | - source .circleci/local_publish_helpers.sh - cd packages/graphql-transformers-e2e-tests/ - retry yarn e2e --maxWorkers=3 $TEST_SUITE - environment: - AMPLIFY_CLI_DISABLE_LOGGING: 'true' - no_output_timeout: 90m - - store_test_results: - path: packages/graphql-transformers-e2e-tests/ - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/VersionedModelTransformer.e2e.test.ts - CLI_REGION: us-west-2 - migration_tests-auth-deployment-migration-auth-deployment-secrets-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/auth-deployment-migration/auth.deployment.secrets.test.ts - CLI_REGION: us-east-2 - migration_tests-lambda-layer-migration-layer-migration-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/lambda-layer-migration/layer-migration.test.ts - CLI_REGION: us-west-2 - migration_tests-overrides-auth-migration-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/migration_tests/overrides/auth-migration.test.ts - CLI_REGION: eu-west-2 - migration_tests-overrides-init-migration-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/migration_tests/overrides/init-migration.test.ts - CLI_REGION: eu-central-1 - migration_tests-transformer_migration-api-key-migration-2-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/transformer_migration/api.key.migration-2.test.ts - CLI_REGION: ap-northeast-1 - migration_tests-transformer_migration-api-key-migration-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/transformer_migration/api.key.migration.test.ts - CLI_REGION: ap-southeast-1 - migration_tests-transformer_migration-api-searchable-migration-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/transformer_migration/api.searchable.migration.test.ts - CLI_REGION: ap-southeast-2 - update_tests-api_migration_update-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/update_tests/api_migration_update.test.ts - CLI_REGION: us-east-2 - update_tests-auth_migration_update-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/update_tests/auth_migration_update.test.ts - CLI_REGION: us-west-2 - update_tests-function_migration_update-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/update_tests/function_migration_update.test.ts - CLI_REGION: eu-west-2 - update_tests-storage_migration_update-amplify_migration_tests_v4: - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from CLI v4.0.0 - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration_v4.0.0 --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/repo/packages/amplify-cli/bin/amplify - TEST_SUITE: src/__tests__/update_tests/storage_migration_update.test.ts - CLI_REGION: eu-central-1 - migration_tests-auth-deployment-migration-auth-deployment-secrets-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/auth-deployment-migration/auth.deployment.secrets.test.ts - CLI_REGION: us-east-2 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - migration_tests-lambda-layer-migration-layer-migration-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/lambda-layer-migration/layer-migration.test.ts - CLI_REGION: us-west-2 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - migration_tests-overrides-auth-migration-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: src/__tests__/migration_tests/overrides/auth-migration.test.ts - CLI_REGION: eu-west-2 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - migration_tests-overrides-init-migration-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: src/__tests__/migration_tests/overrides/init-migration.test.ts - CLI_REGION: eu-central-1 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - migration_tests-transformer_migration-api-key-migration-2-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/transformer_migration/api.key.migration-2.test.ts - CLI_REGION: ap-northeast-1 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - migration_tests-transformer_migration-api-key-migration-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/transformer_migration/api.key.migration.test.ts - CLI_REGION: ap-southeast-1 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - migration_tests-transformer_migration-api-searchable-migration-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: >- - src/__tests__/migration_tests/transformer_migration/api.searchable.migration.test.ts - CLI_REGION: ap-southeast-2 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - update_tests-api_migration_update-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: src/__tests__/update_tests/api_migration_update.test.ts - CLI_REGION: us-east-2 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - update_tests-auth_migration_update-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: src/__tests__/update_tests/auth_migration_update.test.ts - CLI_REGION: us-west-2 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - update_tests-function_migration_update-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: src/__tests__/update_tests/function_migration_update.test.ts - CLI_REGION: eu-west-2 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports - update_tests-storage_migration_update-amplify_migration_tests_latest: - environment: - AMPLIFY_DIR: /home/circleci/repo/packages/amplify-cli/bin - AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify - TEST_SUITE: src/__tests__/update_tests/storage_migration_update.test.ts - CLI_REGION: eu-central-1 - working_directory: ~/repo - parameters: - os: - type: executor - default: linux - executor: << parameters.os >> - steps: - - attach_workspace: - at: ./ - - restore_cache: - key: >- - amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ - arch }} - - restore_cache: - key: amplify-pkg-binaries-{{ .Branch }}-{{ .Revision }} - - run: - name: Run tests migrating from latest CLI - command: | - source .circleci/local_publish_helpers.sh - changeNpmGlobalPath - cd packages/amplify-migration-tests - retry yarn run migration --maxWorkers=3 $TEST_SUITE - no_output_timeout: 90m - - run: - name: Scan And Cleanup E2E Test Artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - - store_test_results: - path: packages/amplify-migration-tests/ - - store_artifacts: - path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports -workflows: - version: 2 - nightly_console_integration_tests: - triggers: - - schedule: - cron: 0 14 * * * - filters: - branches: - only: - - master - jobs: - - build - - publish_to_local_registry: - requires: - - build - - amplify_console_integration_tests: - context: - - amplify-ecr-image-pull - - console-e2e-test - - e2e-auth-credentials - - e2e-test-context - requires: - - build - - publish_to_local_registry - e2e_resource_cleanup: - triggers: - - schedule: - cron: 45 0,12 * * * - filters: - branches: - only: - - master - jobs: - - build - - cleanup_resources: - context: - - cleanup-resources - - e2e-test-context - requires: - - build - build_test_deploy: - jobs: - - build: - matrix: - parameters: - os: - - linux - - windows - - test: - requires: - - build - - mock_e2e_tests: - requires: - - build - - integration_test: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - beta - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - - publish_to_local_registry: - filters: - branches: - only: - - master - - beta - - release - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - - build_pkg_binaries: - requires: - - publish_to_local_registry - - amplify_sudo_install_test: - context: amplify-ecr-image-pull - requires: - - publish_to_local_registry - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - - amplify_migration_tests_v4_30_0: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - - amplify_migration_tests_non_multi_env_layers: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - - amplify_migration_tests_multi_env_layers: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - - amplify_console_integration_tests: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - console-e2e-test - - e2e-test-context - filters: - branches: - only: - - beta - requires: - - build - - publish_to_local_registry - - github_prerelease: - context: github-publish - requires: - - build_pkg_binaries - filters: - branches: - only: - - release - - github_prerelease_install_sanity_check: - requires: - - github_prerelease - filters: - branches: - only: - - release - - cleanup_resources_after_e2e_runs: - context: - - cleanup-resources - - e2e-test-context - requires: - - amplify_migration_tests_v4_30_0 - - api_4-amplify_e2e_tests - - custom_policies_function-amplify_e2e_tests - - hooks-amplify_e2e_tests - - layer-3-amplify_e2e_tests - - schema-auth-13-amplify_e2e_tests - - plugin-amplify_e2e_tests - - schema-versioned-amplify_e2e_tests - - feature-flags-amplify_e2e_tests - - geo-remove-amplify_e2e_tests - - schema-auth-3-amplify_e2e_tests - - schema-auth-8-amplify_e2e_tests - - import_dynamodb_1-amplify_e2e_tests - - migration-api-key-migration2-amplify_e2e_tests - - schema-auth-6-amplify_e2e_tests - - schema-iterative-update-4-amplify_e2e_tests - - auth_6-amplify_e2e_tests - - frontend_config_drift-amplify_e2e_tests - - iam-permissions-boundary-amplify_e2e_tests - - layer-4-amplify_e2e_tests - - schema-function-1-amplify_e2e_tests - - init-special-case-amplify_e2e_tests - - schema-data-access-patterns-amplify_e2e_tests - - interactions-amplify_e2e_tests - - schema-iterative-update-2-amplify_e2e_tests - - schema-auth-10-amplify_e2e_tests - - delete-amplify_e2e_tests - - schema-searchable-amplify_e2e_tests - - schema-iterative-rollback-1-amplify_e2e_tests - - function_1-amplify_e2e_tests - - schema-iterative-update-3-amplify_e2e_tests - - auth_7-amplify_e2e_tests - - function_5-amplify_e2e_tests - - import_auth_3-amplify_e2e_tests - - migration-api-connection-migration2-amplify_e2e_tests - - schema-function-2-amplify_e2e_tests - - datastore-modelgen-amplify_e2e_tests - - containers-api-amplify_e2e_tests - - schema-key-amplify_e2e_tests - - function_2-amplify_e2e_tests - - schema-auth-4-amplify_e2e_tests - - schema-iterative-rollback-2-amplify_e2e_tests - - schema-auth-1-amplify_e2e_tests - - schema-auth-2-amplify_e2e_tests - - auth_8-amplify_e2e_tests - - function_6-amplify_e2e_tests - - import_dynamodb_2-amplify_e2e_tests - - migration-node-function-amplify_e2e_tests - - schema-iterative-update-locking-amplify_e2e_tests - - amplify-configure-amplify_e2e_tests - - schema-predictions-amplify_e2e_tests - - predictions-amplify_e2e_tests - - auth_1-amplify_e2e_tests - - auth_3-amplify_e2e_tests - - api_3-amplify_e2e_tests - - env-amplify_e2e_tests - - function_4-amplify_e2e_tests - - api_1-amplify_e2e_tests - - configure-project-amplify_e2e_tests - - function_7-amplify_e2e_tests - - import_s3_2-amplify_e2e_tests - - pull-amplify_e2e_tests - - storage-1-amplify_e2e_tests - - init-amplify_e2e_tests - - amplify-app-amplify_e2e_tests - - hostingPROD-amplify_e2e_tests - - auth_5-amplify_e2e_tests - - migration-api-key-migration1-amplify_e2e_tests - - import_auth_1-amplify_e2e_tests - - auth_2-amplify_e2e_tests - - schema-model-amplify_e2e_tests - - schema-auth-5-amplify_e2e_tests - - container-hosting-amplify_e2e_tests - - function_8-amplify_e2e_tests - - layer-1-amplify_e2e_tests - - s3-sse-amplify_e2e_tests - - storage-2-amplify_e2e_tests - - tags-amplify_e2e_tests - - hosting-amplify_e2e_tests - - geo-add-amplify_e2e_tests - - function_3-amplify_e2e_tests - - auth_4-amplify_e2e_tests - - import_auth_2-amplify_e2e_tests - - schema-auth-9-amplify_e2e_tests - - migration-api-connection-migration-amplify_e2e_tests - - api_2-amplify_e2e_tests - - custom_policies_container-amplify_e2e_tests - - function_9-amplify_e2e_tests - - layer-2-amplify_e2e_tests - - schema-auth-12-amplify_e2e_tests - - storage-3-amplify_e2e_tests - - notifications-amplify_e2e_tests - - analytics-amplify_e2e_tests - - geo-update-amplify_e2e_tests - - schema-iterative-update-1-amplify_e2e_tests - - schema-auth-7-amplify_e2e_tests - - import_s3_1-amplify_e2e_tests - - schema-auth-11-amplify_e2e_tests - - schema-connection-amplify_e2e_tests - - api_5-amplify_e2e_tests - - api_4-amplify_e2e_tests_pkg - - custom_policies_function-amplify_e2e_tests_pkg - - hooks-amplify_e2e_tests_pkg - - layer-3-amplify_e2e_tests_pkg - - schema-auth-13-amplify_e2e_tests_pkg - - plugin-amplify_e2e_tests_pkg - - schema-versioned-amplify_e2e_tests_pkg - - feature-flags-amplify_e2e_tests_pkg - - geo-remove-amplify_e2e_tests_pkg - - schema-auth-3-amplify_e2e_tests_pkg - - schema-auth-8-amplify_e2e_tests_pkg - - import_dynamodb_1-amplify_e2e_tests_pkg - - migration-api-key-migration2-amplify_e2e_tests_pkg - - schema-auth-6-amplify_e2e_tests_pkg - - schema-iterative-update-4-amplify_e2e_tests_pkg - - auth_6-amplify_e2e_tests_pkg - - frontend_config_drift-amplify_e2e_tests_pkg - - iam-permissions-boundary-amplify_e2e_tests_pkg - - layer-4-amplify_e2e_tests_pkg - - schema-function-1-amplify_e2e_tests_pkg - - init-special-case-amplify_e2e_tests_pkg - - schema-data-access-patterns-amplify_e2e_tests_pkg - - interactions-amplify_e2e_tests_pkg - - schema-iterative-update-2-amplify_e2e_tests_pkg - - schema-auth-10-amplify_e2e_tests_pkg - - delete-amplify_e2e_tests_pkg - - schema-searchable-amplify_e2e_tests_pkg - - schema-iterative-rollback-1-amplify_e2e_tests_pkg - - function_1-amplify_e2e_tests_pkg - - schema-iterative-update-3-amplify_e2e_tests_pkg - - auth_7-amplify_e2e_tests_pkg - - function_5-amplify_e2e_tests_pkg - - import_auth_3-amplify_e2e_tests_pkg - - migration-api-connection-migration2-amplify_e2e_tests_pkg - - schema-function-2-amplify_e2e_tests_pkg - - datastore-modelgen-amplify_e2e_tests_pkg - - containers-api-amplify_e2e_tests_pkg - - schema-key-amplify_e2e_tests_pkg - - function_2-amplify_e2e_tests_pkg - - schema-auth-4-amplify_e2e_tests_pkg - - schema-iterative-rollback-2-amplify_e2e_tests_pkg - - schema-auth-1-amplify_e2e_tests_pkg - - schema-auth-2-amplify_e2e_tests_pkg - - auth_8-amplify_e2e_tests_pkg - - function_6-amplify_e2e_tests_pkg - - import_dynamodb_2-amplify_e2e_tests_pkg - - migration-node-function-amplify_e2e_tests_pkg - - schema-iterative-update-locking-amplify_e2e_tests_pkg - - amplify-configure-amplify_e2e_tests_pkg - - schema-predictions-amplify_e2e_tests_pkg - - predictions-amplify_e2e_tests_pkg - - auth_1-amplify_e2e_tests_pkg - - auth_3-amplify_e2e_tests_pkg - - api_3-amplify_e2e_tests_pkg - - env-amplify_e2e_tests_pkg - - function_4-amplify_e2e_tests_pkg - - api_1-amplify_e2e_tests_pkg - - configure-project-amplify_e2e_tests_pkg - - function_7-amplify_e2e_tests_pkg - - import_s3_2-amplify_e2e_tests_pkg - - pull-amplify_e2e_tests_pkg - - storage-1-amplify_e2e_tests_pkg - - init-amplify_e2e_tests_pkg - - amplify-app-amplify_e2e_tests_pkg - - hostingPROD-amplify_e2e_tests_pkg - - auth_5-amplify_e2e_tests_pkg - - migration-api-key-migration1-amplify_e2e_tests_pkg - - import_auth_1-amplify_e2e_tests_pkg - - auth_2-amplify_e2e_tests_pkg - - schema-model-amplify_e2e_tests_pkg - - schema-auth-5-amplify_e2e_tests_pkg - - container-hosting-amplify_e2e_tests_pkg - - function_8-amplify_e2e_tests_pkg - - layer-1-amplify_e2e_tests_pkg - - s3-sse-amplify_e2e_tests_pkg - - storage-2-amplify_e2e_tests_pkg - - tags-amplify_e2e_tests_pkg - - hosting-amplify_e2e_tests_pkg - - geo-add-amplify_e2e_tests_pkg - - function_3-amplify_e2e_tests_pkg - - auth_4-amplify_e2e_tests_pkg - - import_auth_2-amplify_e2e_tests_pkg - - schema-auth-9-amplify_e2e_tests_pkg - - migration-api-connection-migration-amplify_e2e_tests_pkg - - api_2-amplify_e2e_tests_pkg - - custom_policies_container-amplify_e2e_tests_pkg - - function_9-amplify_e2e_tests_pkg - - layer-2-amplify_e2e_tests_pkg - - schema-auth-12-amplify_e2e_tests_pkg - - storage-3-amplify_e2e_tests_pkg - - notifications-amplify_e2e_tests_pkg - - analytics-amplify_e2e_tests_pkg - - geo-update-amplify_e2e_tests_pkg - - schema-iterative-update-1-amplify_e2e_tests_pkg - - schema-auth-7-amplify_e2e_tests_pkg - - import_s3_1-amplify_e2e_tests_pkg - - schema-auth-11-amplify_e2e_tests_pkg - - schema-connection-amplify_e2e_tests_pkg - - api_5-amplify_e2e_tests_pkg - - >- - migration_tests-auth-deployment-migration-auth-deployment-secrets-amplify_migration_tests_v4 - - update_tests-api_migration_update-amplify_migration_tests_v4 - - >- - migration_tests-lambda-layer-migration-layer-migration-amplify_migration_tests_v4 - - update_tests-auth_migration_update-amplify_migration_tests_v4 - - >- - migration_tests-overrides-auth-migration-amplify_migration_tests_v4 - - update_tests-function_migration_update-amplify_migration_tests_v4 - - >- - migration_tests-overrides-init-migration-amplify_migration_tests_v4 - - update_tests-storage_migration_update-amplify_migration_tests_v4 - - >- - migration_tests-transformer_migration-api-key-migration-2-amplify_migration_tests_v4 - - >- - migration_tests-transformer_migration-api-key-migration-amplify_migration_tests_v4 - - >- - migration_tests-transformer_migration-api-searchable-migration-amplify_migration_tests_v4 - - >- - migration_tests-auth-deployment-migration-auth-deployment-secrets-amplify_migration_tests_latest - - update_tests-api_migration_update-amplify_migration_tests_latest - - >- - migration_tests-lambda-layer-migration-layer-migration-amplify_migration_tests_latest - - update_tests-auth_migration_update-amplify_migration_tests_latest - - >- - migration_tests-overrides-auth-migration-amplify_migration_tests_latest - - >- - update_tests-function_migration_update-amplify_migration_tests_latest - - >- - migration_tests-overrides-init-migration-amplify_migration_tests_latest - - >- - update_tests-storage_migration_update-amplify_migration_tests_latest - - >- - migration_tests-transformer_migration-api-key-migration-2-amplify_migration_tests_latest - - >- - migration_tests-transformer_migration-api-key-migration-amplify_migration_tests_latest - - >- - migration_tests-transformer_migration-api-searchable-migration-amplify_migration_tests_latest - - deploy: - context: - - amplify-ecr-image-pull - - npm-publish - requires: - - test - - mock_e2e_tests - - integration_test - - amplify_sudo_install_test - - amplify_console_integration_tests - - amplify_migration_tests_v4_30_0 - - amplify_migration_tests_non_multi_env_layers - - amplify_migration_tests_multi_env_layers - - github_prerelease_install_sanity_check - - api_4-amplify_e2e_tests - - custom_policies_function-amplify_e2e_tests - - hooks-amplify_e2e_tests - - layer-3-amplify_e2e_tests - - schema-auth-13-amplify_e2e_tests - - plugin-amplify_e2e_tests - - schema-versioned-amplify_e2e_tests - - feature-flags-amplify_e2e_tests - - geo-remove-amplify_e2e_tests - - schema-auth-3-amplify_e2e_tests - - schema-auth-8-amplify_e2e_tests - - import_dynamodb_1-amplify_e2e_tests - - migration-api-key-migration2-amplify_e2e_tests - - schema-auth-6-amplify_e2e_tests - - schema-iterative-update-4-amplify_e2e_tests - - auth_6-amplify_e2e_tests - - frontend_config_drift-amplify_e2e_tests - - iam-permissions-boundary-amplify_e2e_tests - - layer-4-amplify_e2e_tests - - schema-function-1-amplify_e2e_tests - - init-special-case-amplify_e2e_tests - - schema-data-access-patterns-amplify_e2e_tests - - interactions-amplify_e2e_tests - - schema-iterative-update-2-amplify_e2e_tests - - schema-auth-10-amplify_e2e_tests - - delete-amplify_e2e_tests - - schema-searchable-amplify_e2e_tests - - schema-iterative-rollback-1-amplify_e2e_tests - - function_1-amplify_e2e_tests - - schema-iterative-update-3-amplify_e2e_tests - - auth_7-amplify_e2e_tests - - function_5-amplify_e2e_tests - - import_auth_3-amplify_e2e_tests - - migration-api-connection-migration2-amplify_e2e_tests - - schema-function-2-amplify_e2e_tests - - datastore-modelgen-amplify_e2e_tests - - containers-api-amplify_e2e_tests - - schema-key-amplify_e2e_tests - - function_2-amplify_e2e_tests - - schema-auth-4-amplify_e2e_tests - - schema-iterative-rollback-2-amplify_e2e_tests - - schema-auth-1-amplify_e2e_tests - - schema-auth-2-amplify_e2e_tests - - auth_8-amplify_e2e_tests - - function_6-amplify_e2e_tests - - import_dynamodb_2-amplify_e2e_tests - - migration-node-function-amplify_e2e_tests - - schema-iterative-update-locking-amplify_e2e_tests - - amplify-configure-amplify_e2e_tests - - schema-predictions-amplify_e2e_tests - - predictions-amplify_e2e_tests - - auth_1-amplify_e2e_tests - - auth_3-amplify_e2e_tests - - api_3-amplify_e2e_tests - - env-amplify_e2e_tests - - function_4-amplify_e2e_tests - - api_1-amplify_e2e_tests - - configure-project-amplify_e2e_tests - - function_7-amplify_e2e_tests - - import_s3_2-amplify_e2e_tests - - pull-amplify_e2e_tests - - storage-1-amplify_e2e_tests - - init-amplify_e2e_tests - - amplify-app-amplify_e2e_tests - - hostingPROD-amplify_e2e_tests - - auth_5-amplify_e2e_tests - - migration-api-key-migration1-amplify_e2e_tests - - import_auth_1-amplify_e2e_tests - - auth_2-amplify_e2e_tests - - schema-model-amplify_e2e_tests - - schema-auth-5-amplify_e2e_tests - - container-hosting-amplify_e2e_tests - - function_8-amplify_e2e_tests - - layer-1-amplify_e2e_tests - - s3-sse-amplify_e2e_tests - - storage-2-amplify_e2e_tests - - tags-amplify_e2e_tests - - hosting-amplify_e2e_tests - - geo-add-amplify_e2e_tests - - function_3-amplify_e2e_tests - - auth_4-amplify_e2e_tests - - import_auth_2-amplify_e2e_tests - - schema-auth-9-amplify_e2e_tests - - migration-api-connection-migration-amplify_e2e_tests - - api_2-amplify_e2e_tests - - custom_policies_container-amplify_e2e_tests - - function_9-amplify_e2e_tests - - layer-2-amplify_e2e_tests - - schema-auth-12-amplify_e2e_tests - - storage-3-amplify_e2e_tests - - notifications-amplify_e2e_tests - - analytics-amplify_e2e_tests - - geo-update-amplify_e2e_tests - - schema-iterative-update-1-amplify_e2e_tests - - schema-auth-7-amplify_e2e_tests - - import_s3_1-amplify_e2e_tests - - schema-auth-11-amplify_e2e_tests - - schema-connection-amplify_e2e_tests - - api_5-amplify_e2e_tests - - api_4-amplify_e2e_tests_pkg - - custom_policies_function-amplify_e2e_tests_pkg - - hooks-amplify_e2e_tests_pkg - - layer-3-amplify_e2e_tests_pkg - - schema-auth-13-amplify_e2e_tests_pkg - - plugin-amplify_e2e_tests_pkg - - schema-versioned-amplify_e2e_tests_pkg - - feature-flags-amplify_e2e_tests_pkg - - geo-remove-amplify_e2e_tests_pkg - - schema-auth-3-amplify_e2e_tests_pkg - - schema-auth-8-amplify_e2e_tests_pkg - - import_dynamodb_1-amplify_e2e_tests_pkg - - migration-api-key-migration2-amplify_e2e_tests_pkg - - schema-auth-6-amplify_e2e_tests_pkg - - schema-iterative-update-4-amplify_e2e_tests_pkg - - auth_6-amplify_e2e_tests_pkg - - frontend_config_drift-amplify_e2e_tests_pkg - - iam-permissions-boundary-amplify_e2e_tests_pkg - - layer-4-amplify_e2e_tests_pkg - - schema-function-1-amplify_e2e_tests_pkg - - init-special-case-amplify_e2e_tests_pkg - - schema-data-access-patterns-amplify_e2e_tests_pkg - - interactions-amplify_e2e_tests_pkg - - schema-iterative-update-2-amplify_e2e_tests_pkg - - schema-auth-10-amplify_e2e_tests_pkg - - delete-amplify_e2e_tests_pkg - - schema-searchable-amplify_e2e_tests_pkg - - schema-iterative-rollback-1-amplify_e2e_tests_pkg - - function_1-amplify_e2e_tests_pkg - - schema-iterative-update-3-amplify_e2e_tests_pkg - - auth_7-amplify_e2e_tests_pkg - - function_5-amplify_e2e_tests_pkg - - import_auth_3-amplify_e2e_tests_pkg - - migration-api-connection-migration2-amplify_e2e_tests_pkg - - schema-function-2-amplify_e2e_tests_pkg - - datastore-modelgen-amplify_e2e_tests_pkg - - containers-api-amplify_e2e_tests_pkg - - schema-key-amplify_e2e_tests_pkg - - function_2-amplify_e2e_tests_pkg - - schema-auth-4-amplify_e2e_tests_pkg - - schema-iterative-rollback-2-amplify_e2e_tests_pkg - - schema-auth-1-amplify_e2e_tests_pkg - - schema-auth-2-amplify_e2e_tests_pkg - - auth_8-amplify_e2e_tests_pkg - - function_6-amplify_e2e_tests_pkg - - import_dynamodb_2-amplify_e2e_tests_pkg - - migration-node-function-amplify_e2e_tests_pkg - - schema-iterative-update-locking-amplify_e2e_tests_pkg - - amplify-configure-amplify_e2e_tests_pkg - - schema-predictions-amplify_e2e_tests_pkg - - predictions-amplify_e2e_tests_pkg - - auth_1-amplify_e2e_tests_pkg - - auth_3-amplify_e2e_tests_pkg - - api_3-amplify_e2e_tests_pkg - - env-amplify_e2e_tests_pkg - - function_4-amplify_e2e_tests_pkg - - api_1-amplify_e2e_tests_pkg - - configure-project-amplify_e2e_tests_pkg - - function_7-amplify_e2e_tests_pkg - - import_s3_2-amplify_e2e_tests_pkg - - pull-amplify_e2e_tests_pkg - - storage-1-amplify_e2e_tests_pkg - - init-amplify_e2e_tests_pkg - - amplify-app-amplify_e2e_tests_pkg - - hostingPROD-amplify_e2e_tests_pkg - - auth_5-amplify_e2e_tests_pkg - - migration-api-key-migration1-amplify_e2e_tests_pkg - - import_auth_1-amplify_e2e_tests_pkg - - auth_2-amplify_e2e_tests_pkg - - schema-model-amplify_e2e_tests_pkg - - schema-auth-5-amplify_e2e_tests_pkg - - container-hosting-amplify_e2e_tests_pkg - - function_8-amplify_e2e_tests_pkg - - layer-1-amplify_e2e_tests_pkg - - s3-sse-amplify_e2e_tests_pkg - - storage-2-amplify_e2e_tests_pkg - - tags-amplify_e2e_tests_pkg - - hosting-amplify_e2e_tests_pkg - - geo-add-amplify_e2e_tests_pkg - - function_3-amplify_e2e_tests_pkg - - auth_4-amplify_e2e_tests_pkg - - import_auth_2-amplify_e2e_tests_pkg - - schema-auth-9-amplify_e2e_tests_pkg - - migration-api-connection-migration-amplify_e2e_tests_pkg - - api_2-amplify_e2e_tests_pkg - - custom_policies_container-amplify_e2e_tests_pkg - - function_9-amplify_e2e_tests_pkg - - layer-2-amplify_e2e_tests_pkg - - schema-auth-12-amplify_e2e_tests_pkg - - storage-3-amplify_e2e_tests_pkg - - notifications-amplify_e2e_tests_pkg - - analytics-amplify_e2e_tests_pkg - - geo-update-amplify_e2e_tests_pkg - - schema-iterative-update-1-amplify_e2e_tests_pkg - - schema-auth-7-amplify_e2e_tests_pkg - - import_s3_1-amplify_e2e_tests_pkg - - schema-auth-11-amplify_e2e_tests_pkg - - schema-connection-amplify_e2e_tests_pkg - - api_5-amplify_e2e_tests_pkg - - ConnectionsWithAuthTests-e2e-graphql_e2e_tests - - KeyTransformer-e2e-graphql_e2e_tests - - MultiAuthModelAuthTransformer-e2e-graphql_e2e_tests - - PerFieldAuthTests-e2e-graphql_e2e_tests - - TestComplexStackMappingsLocal-e2e-graphql_e2e_tests - - CustomRoots-e2e-graphql_e2e_tests - - KeyTransformerLocal-e2e-graphql_e2e_tests - - MutationCondition-e2e-graphql_e2e_tests - - PredictionsTransformerTests-e2e-graphql_e2e_tests - - VersionedModelTransformer-e2e-graphql_e2e_tests - - DefaultValueTransformer-e2e-graphql_e2e_tests - - KeyWithAuth-e2e-graphql_e2e_tests - - NestedStacksTest-e2e-graphql_e2e_tests - - RelationalTransformers-e2e-graphql_e2e_tests - - DynamoDBModelTransformer-e2e-graphql_e2e_tests - - ModelAuthTransformer-e2e-graphql_e2e_tests - - NewConnectionTransformer-e2e-graphql_e2e_tests - - SearchableModelTransformer-e2e-graphql_e2e_tests - - FunctionTransformerTests-e2e-graphql_e2e_tests - - ModelConnectionTransformer-e2e-graphql_e2e_tests - - NewConnectionWithAuth-e2e-graphql_e2e_tests - - SearchableModelTransformerV2-e2e-graphql_e2e_tests - - HttpTransformer-e2e-graphql_e2e_tests - - ModelConnectionWithKeyTransformer-e2e-graphql_e2e_tests - - NoneEnvFunctionTransformer-e2e-graphql_e2e_tests - - SearchableWithAuthTests-e2e-graphql_e2e_tests - - IndexTransformer-e2e-graphql_e2e_tests - - ModelTransformer-e2e-graphql_e2e_tests - - NonModelAuthFunction-e2e-graphql_e2e_tests - - SubscriptionsWithAuthTest-e2e-graphql_e2e_tests - - >- - migration_tests-auth-deployment-migration-auth-deployment-secrets-amplify_migration_tests_v4 - - update_tests-api_migration_update-amplify_migration_tests_v4 - - >- - migration_tests-lambda-layer-migration-layer-migration-amplify_migration_tests_v4 - - update_tests-auth_migration_update-amplify_migration_tests_v4 - - >- - migration_tests-overrides-auth-migration-amplify_migration_tests_v4 - - update_tests-function_migration_update-amplify_migration_tests_v4 - - >- - migration_tests-overrides-init-migration-amplify_migration_tests_v4 - - update_tests-storage_migration_update-amplify_migration_tests_v4 - - >- - migration_tests-transformer_migration-api-key-migration-2-amplify_migration_tests_v4 - - >- - migration_tests-transformer_migration-api-key-migration-amplify_migration_tests_v4 - - >- - migration_tests-transformer_migration-api-searchable-migration-amplify_migration_tests_v4 - - >- - migration_tests-auth-deployment-migration-auth-deployment-secrets-amplify_migration_tests_latest - - update_tests-api_migration_update-amplify_migration_tests_latest - - >- - migration_tests-lambda-layer-migration-layer-migration-amplify_migration_tests_latest - - update_tests-auth_migration_update-amplify_migration_tests_latest - - >- - migration_tests-overrides-auth-migration-amplify_migration_tests_latest - - >- - update_tests-function_migration_update-amplify_migration_tests_latest - - >- - migration_tests-overrides-init-migration-amplify_migration_tests_latest - - >- - update_tests-storage_migration_update-amplify_migration_tests_latest - - >- - migration_tests-transformer_migration-api-key-migration-2-amplify_migration_tests_latest - - >- - migration_tests-transformer_migration-api-key-migration-amplify_migration_tests_latest - - >- - migration_tests-transformer_migration-api-searchable-migration-amplify_migration_tests_latest - filters: - branches: - only: - - release - - master - - beta - - /tagged-release\/.*/ - - /tagged-release-without-e2e-tests\/.*/ - - github_release: - context: github-publish - requires: - - deploy - filters: - branches: - only: - - release - - api_4-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - custom_policies_function-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - hooks-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - layer-3-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-13-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - plugin-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-versioned-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - feature-flags-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - geo-remove-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-3-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-8-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - import_dynamodb_1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - migration-api-key-migration2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-6-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-iterative-update-4-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - auth_6-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - frontend_config_drift-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - iam-permissions-boundary-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - layer-4-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-function-1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - init-special-case-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-data-access-patterns-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - interactions-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-iterative-update-2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-10-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - delete-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-searchable-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-iterative-rollback-1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-iterative-update-3-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - auth_7-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_5-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - import_auth_3-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - migration-api-connection-migration2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-function-2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - datastore-modelgen-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - containers-api-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-key-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-4-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-iterative-rollback-2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - auth_8-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_6-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - import_dynamodb_2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - migration-node-function-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-iterative-update-locking-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - amplify-configure-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-predictions-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - predictions-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - auth_1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - auth_3-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - api_3-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - env-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_4-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - api_1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - configure-project-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_7-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - import_s3_2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - pull-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - storage-1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - init-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - amplify-app-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - hostingPROD-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - auth_5-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - migration-api-key-migration1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - import_auth_1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - auth_2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-model-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-5-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - container-hosting-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_8-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - layer-1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - s3-sse-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - storage-2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - tags-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - hosting-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - geo-add-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_3-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - auth_4-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - import_auth_2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-9-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - migration-api-connection-migration-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - api_2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - custom_policies_container-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - function_9-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - layer-2-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-12-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - storage-3-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - notifications-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - analytics-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - geo-update-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-iterative-update-1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-7-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - import_s3_1-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-auth-11-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - schema-connection-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - api_5-amplify_e2e_tests: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - publish_to_local_registry - matrix: - parameters: - os: - - linux - - api_4-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - custom_policies_function-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - hooks-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - layer-3-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-13-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - plugin-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - windows - - schema-versioned-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - feature-flags-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - geo-remove-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-3-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-8-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - import_dynamodb_1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - migration-api-key-migration2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-6-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-iterative-update-4-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - auth_6-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - frontend_config_drift-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - iam-permissions-boundary-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - layer-4-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-function-1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - init-special-case-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - windows - - schema-data-access-patterns-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - interactions-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-iterative-update-2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-10-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - delete-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-searchable-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-iterative-rollback-1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - function_1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-iterative-update-3-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - auth_7-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - function_5-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - import_auth_3-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - migration-api-connection-migration2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-function-2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - datastore-modelgen-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - containers-api-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-key-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - function_2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-4-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-iterative-rollback-2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - auth_8-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - function_6-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - import_dynamodb_2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - migration-node-function-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-iterative-update-locking-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - amplify-configure-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - windows - - schema-predictions-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - predictions-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - auth_1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - auth_3-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - api_3-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - env-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - function_4-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - api_1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - configure-project-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - windows - - function_7-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - import_s3_2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - pull-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - storage-1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - init-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - amplify-app-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - hostingPROD-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - auth_5-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - migration-api-key-migration1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - import_auth_1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - auth_2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-model-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-5-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - container-hosting-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - windows - - function_8-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - layer-1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - s3-sse-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - storage-2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - tags-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - hosting-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - geo-add-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - function_3-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - auth_4-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - import_auth_2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-9-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - migration-api-connection-migration-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - api_2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - custom_policies_container-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - function_9-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - layer-2-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-12-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - storage-3-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - notifications-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - analytics-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - geo-update-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-iterative-update-1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-7-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - import_s3_1-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-auth-11-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - schema-connection-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - api_5-amplify_e2e_tests_pkg: - context: - - amplify-ecr-image-pull - - cleanup-resources - - e2e-auth-credentials - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build_pkg_binaries - matrix: - parameters: - os: - - linux - - ConnectionsWithAuthTests-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - KeyTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - MultiAuthModelAuthTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - PerFieldAuthTests-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - TestComplexStackMappingsLocal-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - CustomRoots-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - KeyTransformerLocal-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - MutationCondition-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - PredictionsTransformerTests-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - VersionedModelTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - DefaultValueTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - KeyWithAuth-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - NestedStacksTest-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - RelationalTransformers-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - DynamoDBModelTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - ModelAuthTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - NewConnectionTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - SearchableModelTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - FunctionTransformerTests-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - ModelConnectionTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - NewConnectionWithAuth-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - SearchableModelTransformerV2-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - HttpTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - ModelConnectionWithKeyTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - NoneEnvFunctionTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - SearchableWithAuthTests-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - IndexTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - ModelTransformer-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - NonModelAuthFunction-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - SubscriptionsWithAuthTest-e2e-graphql_e2e_tests: - context: - - amplify-ecr-image-pull - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-auth-deployment-migration-auth-deployment-secrets-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - update_tests-api_migration_update-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-lambda-layer-migration-layer-migration-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - update_tests-auth_migration_update-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-overrides-auth-migration-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - update_tests-function_migration_update-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-overrides-init-migration-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - update_tests-storage_migration_update-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-transformer_migration-api-key-migration-2-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-transformer_migration-api-key-migration-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-transformer_migration-api-searchable-migration-amplify_migration_tests_v4: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-auth-deployment-migration-auth-deployment-secrets-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - update_tests-api_migration_update-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-lambda-layer-migration-layer-migration-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - update_tests-auth_migration_update-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-overrides-auth-migration-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - update_tests-function_migration_update-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-overrides-init-migration-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - update_tests-storage_migration_update-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ - requires: - - build - matrix: - parameters: - os: - - linux - - migration_tests-transformer_migration-api-key-migration-2-amplify_migration_tests_latest: - context: - - amplify-ecr-image-pull - - e2e-auth-credentials - - cleanup-resources - - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ + path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports + +# our single workflow, that triggers the setup job defined above +workflows: + setup: + when: << pipeline.parameters.setup >> + jobs: + - setup + nightly_console_integration_tests: + when: << pipeline.parameters.nightly_console_integration_tests >> + jobs: + - build + - publish_to_local_registry: requires: - build - matrix: - parameters: - os: - - linux - - migration_tests-transformer_migration-api-key-migration-amplify_migration_tests_latest: + - amplify_console_integration_tests: context: - amplify-ecr-image-pull + - console-e2e-test - e2e-auth-credentials - - cleanup-resources - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ requires: - build - matrix: - parameters: - os: - - linux - - migration_tests-transformer_migration-api-searchable-migration-amplify_migration_tests_latest: + - publish_to_local_registry + e2e_resource_cleanup: + when: << pipeline.parameters.e2e_resource_cleanup >> + jobs: + - build + - cleanup_resources: context: - - amplify-ecr-image-pull - - e2e-auth-credentials - cleanup-resources - e2e-test-context - filters: - branches: - only: - - master - - /tagged-release\/.*/ - - /run-e2e\/.*/ requires: - build - matrix: - parameters: - os: - - linux -commands: - install_packaged_cli: - description: Install Amplify Packaged CLI to PATH - parameters: - os: - type: executor - default: linux-e2e-executor - steps: - - when: - condition: - equal: - - machine: - image: windows-server-2019-vs2019:stable - resource_class: windows.large - shell: bash.exe - working_directory: ~/repo - environment: - AMPLIFY_DIR: C:/home/circleci/repo/out - AMPLIFY_PATH: C:/home/circleci/repo/out/amplify.exe - - << parameters.os >> - steps: - - run: - shell: powershell.exe - name: Rename the Packaged CLI to amplify - command: | - # rename the command to amplify - cd /home/circleci/repo/out - cp amplify-pkg-win.exe amplify.exe - - run: - shell: powershell.exe - name: Move to CLI Binary to already existing PATH - command: > - # This is a Hack to make sure the Amplify CLI is in the PATH - - cp /home/circleci/repo/out/amplify.exe - $env:homedrive\$env:homepath\AppData\Local\Microsoft\WindowsApps - - run: - name: Confirm Amplify CLI is installed and available in PATH - command: amplify version - - when: - condition: - equal: - - docker: - - image: >- - public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - - << parameters.os >> - steps: - - run: - name: Symlink Amplify packaged CLI - command: | - cd out - ln -sf amplify-pkg-linux amplify - echo "export PATH=$AMPLIFY_DIR:$PATH" >> $BASH_ENV - source $BASH_ENV - - run: - name: Confirm Amplify CLI is installed and available in PATH - command: amplify version - install_yarn: - description: Install Amplify Packaged CLI to PATH - parameters: - os: - type: executor - default: linux-e2e-executor - steps: - - when: - condition: - equal: - - machine: - image: windows-server-2019-vs2019:stable - resource_class: windows.large - shell: bash.exe - working_directory: ~/repo - environment: - AMPLIFY_DIR: C:/home/circleci/repo/out - AMPLIFY_PATH: C:/home/circleci/repo/out/amplify.exe - - << parameters.os >> - steps: - - run: nvm install 12.22.5 - - run: nvm use 12.22.5 - - run: npm install -g yarn - - run: yarn --cache-folder ~/.cache/yarn - - run: - shell: powershell.exe - command: >- - cp /home/circleci/repo/out/amplify-pkg-win.exe - $env:homedrive\$env:homepath\AppData\Local\Microsoft\WindowsApps\amplify.exe - install_java: - description: Install Java on Linux and Docker images - parameters: - os: - type: executor - default: linux-e2e-executor - steps: - - when: - condition: - equal: - - docker: - - image: >- - public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - - << parameters.os >> - steps: - - run: - name: Install Java - command: | - sudo apt-get update && sudo apt-get install default-jdk - run_e2e_tests: - description: Run Amplify E2E tests - parameters: - os: - type: executor - default: linux-e2e-executor - steps: - - when: - condition: - equal: - - machine: - image: windows-server-2019-vs2019:stable - resource_class: windows.large - shell: bash.exe - working_directory: ~/repo - environment: - AMPLIFY_DIR: C:/home/circleci/repo/out - AMPLIFY_PATH: C:/home/circleci/repo/out/amplify.exe - - << parameters.os >> - steps: - - run: - name: Run E2e Tests - shell: powershell.exe - command: > - cd packages/amplify-e2e-tests - - yarn run e2e --detectOpenHandles --maxWorkers=3 - $env:TEST_SUITE - no_output_timeout: 90m - - when: - condition: - equal: - - docker: - - image: >- - public.ecr.aws/a6e6w2n0/amplify-cli-e2e-base-image-repo-public:latest - working_directory: ~/repo - resource_class: large - environment: - AMPLIFY_DIR: /home/circleci/repo/out - AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux - - << parameters.os >> - steps: - - run: - name: Run E2e Tests - command: | - source .circleci/local_publish_helpers.sh - source $BASH_ENV - amplify version - retry runE2eTest - no_output_timeout: 90m - scan_e2e_test_artifacts: - description: Scan And Cleanup E2E Test Artifacts - parameters: - os: - type: executor - default: linux-e2e-executor - steps: - - run: - name: Scan E2E artifacts - command: | - if ! yarn ts-node .circleci/scan_artifacts.ts; then - echo "Cleaning the repository" - git clean -fdx - exit 1 - fi - when: always - clean_e2e_resources: - description: Cleanup resources - parameters: - os: - type: executor - default: linux-e2e-executor - steps: - - run: - name: Clean job resources - command: | - pwd - cd packages/amplify-e2e-tests - yarn clean-e2e-resources job ${CIRCLE_BUILD_NUM} - when: always diff --git a/.github/ISSUE_TEMPLATE/2.feature_request.yaml b/.github/ISSUE_TEMPLATE/2.feature_request.yaml index e75cd3b7975..38c0a2104b1 100644 --- a/.github/ISSUE_TEMPLATE/2.feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/2.feature_request.yaml @@ -1,6 +1,5 @@ name: Feature request description: Suggest an idea for the CLI -labels: feature-request body: - type: markdown @@ -12,7 +11,7 @@ body: - type: dropdown attributes: - label: Is this related to a new or existing Amplify category? + label: Is this feature request related to a new or existing Amplify category? description: Which categories does this request impact? multiple: true options: diff --git a/.gitignore b/.gitignore index 6b24ec226e4..cc64779b924 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ **/.DS_Store +.circleci/generated_config.yml build node_modules out diff --git a/package.json b/package.json index 3023ff6b531..134447a6bb5 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "production-build": "yarn --frozen-lockfile --cache-folder ~/.cache/yarn && lerna run build --concurrency 3 --stream", "dev-build": "yarn --cache-folder ~/.cache/yarn && lerna run build", "link-aa-dev": "cd packages/amplify-app && ln -s \"$(pwd)/bin/amplify-app\" \"$(yarn global bin)/amplify-app-dev\" && cd -", - "rm-aa-dev-link": "rm -f \"$(yarn global bin)/amplify-app-dev\"", + "rm-aa-dev-link": "rimraf -f \"$(yarn global bin)/amplify-app-dev\"", "link-dev": "cd packages/amplify-cli && ln -s \"$(pwd)/bin/amplify\" \"$(yarn global bin)/amplify-dev\" && cd -", - "rm-dev-link": "rm -f \"$(yarn global bin)/amplify-dev\"", + "rm-dev-link": "rimraf -f \"$(yarn global bin)/amplify-dev\"", "setup-dev": "yarn dev-build && yarn rm-dev-link && yarn link-dev && yarn rm-aa-dev-link && yarn link-aa-dev", "link-win": "node ./scripts/link-bin.js packages/amplify-cli/bin/amplify amplify-dev", "link-aa-win": "node ./scripts/link-bin.js packages/amplify-app/bin/amplify-app amplify-app-dev", diff --git a/packages/amplify-appsync-simulator/src/__tests__/scalars/AWSIPAddress.test.ts b/packages/amplify-appsync-simulator/src/__tests__/scalars/AWSIPAddress.test.ts new file mode 100644 index 00000000000..93d70013134 --- /dev/null +++ b/packages/amplify-appsync-simulator/src/__tests__/scalars/AWSIPAddress.test.ts @@ -0,0 +1,15 @@ +import { URL } from 'url'; +import { scalars } from '../../schema/appsync-scalars'; +describe('AWSIPAddress parse', () => { + it('should parse valid ip address', () => { + expect(scalars.AWSIPAddress.parseValue('127.0.0.1')).toEqual('127.0.0.1'); + }); + + it('should parse IPV6 address', () => { + expect(scalars.AWSIPAddress.parseValue('::ffff:127.0.0.1')).toEqual('::ffff:127.0.0.1'); + }); + + it('should throw error when its not a valid ip address', () => { + expect(() => scalars.AWSIPAddress.parseValue('not valid ip')).toThrowError('Value is not a valid IP address:'); + }); +}); diff --git a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/auth-utils.test.ts b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/auth-utils.test.ts new file mode 100644 index 00000000000..e3eb5408787 --- /dev/null +++ b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/auth-utils.test.ts @@ -0,0 +1,57 @@ +import { create } from '../../../velocity/util/index'; +import { GraphQLResolveInfo } from 'graphql'; +import { AppSyncGraphQLExecutionContext } from '../../../utils/graphql-runner'; +import { AmplifyAppSyncSimulatorAuthenticationType } from '../../../type-definition'; + +const stubInfo = {} as unknown; +const mockInfo = stubInfo as GraphQLResolveInfo; + +describe('$util.authType', () => { + it('should return API Key Authorization', () => { + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.API_KEY, + appsyncErrors: [], + }; + + const util = create(undefined, undefined, mockInfo, executionContext); + + expect(util.authType()).toEqual('API Key Authorization'); + }); + + it('should return IAM Authorization', () => { + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.AWS_IAM, + appsyncErrors: [], + }; + + const util = create(undefined, undefined, mockInfo, executionContext); + + expect(util.authType()).toEqual('IAM Authorization'); + }); + + it('should return Open ID Connect Authorization', () => { + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.OPENID_CONNECT, + appsyncErrors: [], + }; + + const util = create(undefined, undefined, mockInfo, executionContext); + + expect(util.authType()).toEqual('Open ID Connect Authorization'); + }); + + it('should return User Pool Authorization', () => { + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.AMAZON_COGNITO_USER_POOLS, + appsyncErrors: [], + }; + + const util = create(undefined, undefined, mockInfo, executionContext); + + expect(util.authType()).toEqual('User Pool Authorization'); + }); +}); diff --git a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/general-utils.test.ts b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/general-utils.test.ts index be13a7986cf..3fedb836f38 100644 --- a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/general-utils.test.ts +++ b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/general-utils.test.ts @@ -1,8 +1,9 @@ import { create } from '../../../velocity/util/index'; import { JavaMap } from '../../../velocity/value-mapper/map'; import { GraphQLResolveInfo } from 'graphql'; -import { hasUncaughtExceptionCaptureCallback } from 'process'; import { generalUtils } from '../../../velocity/util/general-utils'; +import { AppSyncGraphQLExecutionContext } from '../../../utils/graphql-runner'; +import { AmplifyAppSyncSimulatorAuthenticationType } from '../../../type-definition'; const stubInfo = { fieldName: 'testFieldName', @@ -47,7 +48,13 @@ const stubJavaMap: JavaMap = new JavaMap({ field1: 'field1Value', field2: 'field var util; beforeEach(() => { - util = create(undefined, undefined, mockInfo); + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.API_KEY, + appsyncErrors: [], + }; + + util = create(undefined, undefined, mockInfo, executionContext); }); it('error_filterDataJavaMap', () => { diff --git a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/list-utils.test.ts b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/list-utils.test.ts index 0a02067a45b..ea1bf7f3298 100644 --- a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/list-utils.test.ts +++ b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/list-utils.test.ts @@ -1,13 +1,21 @@ import { create } from '../../../velocity/util/index'; import { GraphQLResolveInfo } from 'graphql'; import { map, random } from 'lodash'; +import { AppSyncGraphQLExecutionContext } from '../../../utils/graphql-runner'; +import { AmplifyAppSyncSimulatorAuthenticationType } from '../../../type-definition'; const stubInfo = {} as unknown; export const mockInfo = stubInfo as GraphQLResolveInfo; var util; beforeEach(() => { - util = create(undefined, undefined, mockInfo); + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.API_KEY, + appsyncErrors: [], + }; + + util = create(undefined, undefined, mockInfo, executionContext); }); describe('$utils.list.copyAndRetainAll', () => { diff --git a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/math.test.ts b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/math.test.ts index 55d4ad4de8b..00fe68423ea 100644 --- a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/math.test.ts +++ b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/math.test.ts @@ -1,12 +1,20 @@ import { create } from '../../../velocity/util/index'; import { GraphQLResolveInfo } from 'graphql'; +import { AppSyncGraphQLExecutionContext } from '../../../utils/graphql-runner'; +import { AmplifyAppSyncSimulatorAuthenticationType } from '../../../type-definition'; const stubInfo = {} as unknown; export const mockInfo = stubInfo as GraphQLResolveInfo; var util; beforeEach(() => { - util = create(undefined, undefined, mockInfo); + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.API_KEY, + appsyncErrors: [], + }; + + util = create(undefined, undefined, mockInfo, executionContext); }); describe('$utils.math.round', () => { diff --git a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/rds.test.ts b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/rds.test.ts index 9a6155c20f8..bd44c7b7ada 100644 --- a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/rds.test.ts +++ b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/rds.test.ts @@ -1,13 +1,21 @@ import { create } from '../../../velocity/util/index'; import { mockedInputToRdsJsonString, mockedOutputFromRdsJsonString } from './mock-data'; import { GraphQLResolveInfo } from 'graphql'; +import { AppSyncGraphQLExecutionContext } from '../../../utils/graphql-runner'; +import { AmplifyAppSyncSimulatorAuthenticationType } from '../../../type-definition'; const stubInfo = {} as unknown; const mockInfo = stubInfo as GraphQLResolveInfo; let util; beforeEach(() => { - util = create(undefined, undefined, mockInfo); + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.API_KEY, + appsyncErrors: [], + }; + + util = create(undefined, undefined, mockInfo, executionContext); }); describe('$utils.rds.toJsonString', () => { diff --git a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/str.test.ts b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/str.test.ts index 5a2d253b3a4..622d75c1a3c 100644 --- a/packages/amplify-appsync-simulator/src/__tests__/velocity/util/str.test.ts +++ b/packages/amplify-appsync-simulator/src/__tests__/velocity/util/str.test.ts @@ -1,12 +1,20 @@ import { create } from '../../../velocity/util/index'; import { GraphQLResolveInfo } from 'graphql'; +import { AmplifyAppSyncSimulatorAuthenticationType } from '../../../type-definition'; +import { AppSyncGraphQLExecutionContext } from '../../../utils/graphql-runner'; const stubInfo = {} as unknown; export const mockInfo = stubInfo as GraphQLResolveInfo; var util; beforeEach(() => { - util = create(undefined, undefined, mockInfo); + const executionContext: AppSyncGraphQLExecutionContext = { + headers: { 'x-api-key': 'da-fake-key' }, + requestAuthorizationMode: AmplifyAppSyncSimulatorAuthenticationType.API_KEY, + appsyncErrors: [], + }; + + util = create(undefined, undefined, mockInfo, executionContext); }); describe('$utils.str.toLower', () => { diff --git a/packages/amplify-appsync-simulator/src/schema/appsync-scalars/index.ts b/packages/amplify-appsync-simulator/src/schema/appsync-scalars/index.ts index 8cd35d099ab..3d53c319428 100644 --- a/packages/amplify-appsync-simulator/src/schema/appsync-scalars/index.ts +++ b/packages/amplify-appsync-simulator/src/schema/appsync-scalars/index.ts @@ -1,12 +1,12 @@ import { URL } from 'url'; import { GraphQLInt, GraphQLScalarType, GraphQLError, Kind, StringValueNode, ValueNode } from 'graphql'; import { isValidNumber } from 'libphonenumber-js'; +import * as net from 'net'; import { GraphQLDate, GraphQLTime, GraphQLDateTime } from 'graphql-iso-date'; -const EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; -const IPV4_REGEX = /^(?:(?:(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\/(?:[0-9]|[1-2][0-9]|3[0-2]))?)$/; -const IPV6_REGEX = /^(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)(?:\/(?:0?0?[0-9]|0?[1-9][0-9]|1[01][0-9]|12[0-8]))?)$/; +const EMAIL_ADDRESS_REGEX = + /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; // Some of the custom scalars in this file are inspired by the graphql-scalars npm module. @@ -118,13 +118,11 @@ const validateIPAddress = value => { if (typeof value !== 'string') { throw new TypeError(`Value is not string: ${value}`); } - if (!IPV4_REGEX.test(value)) { - throw new TypeError(`Value is not a valid IPv4 address: ${value}`); - } - if (!IPV6_REGEX.test(value)) { - throw new TypeError(`Value is not a valid IPv6 address: ${value}`); + if (net.isIPv4(value) || net.isIPv6(value)) { + return value; } - return value; + + throw new TypeError(`Value is not a valid IP address: ${value}`); }; const AWSIPAddress = new GraphQLScalarType({ diff --git a/packages/amplify-appsync-simulator/src/velocity/index.ts b/packages/amplify-appsync-simulator/src/velocity/index.ts index e93591849a1..f51d4d138bf 100644 --- a/packages/amplify-appsync-simulator/src/velocity/index.ts +++ b/packages/amplify-appsync-simulator/src/velocity/index.ts @@ -93,7 +93,7 @@ export class VelocityTemplate { const { jwt } = requestContext; const { iss: issuer, sub, 'cognito:username': cognitoUserName, username } = jwt || {}; - const util = createUtil([], new Date(Date.now()), info); + const util = createUtil([], new Date(Date.now()), info, requestContext); const args = convertToJavaTypes(argument); // Identity is null for API Key let identity = null; diff --git a/packages/amplify-appsync-simulator/src/velocity/util/auth-utils.ts b/packages/amplify-appsync-simulator/src/velocity/util/auth-utils.ts new file mode 100644 index 00000000000..ddd3f668564 --- /dev/null +++ b/packages/amplify-appsync-simulator/src/velocity/util/auth-utils.ts @@ -0,0 +1,17 @@ +import { AmplifyAppSyncSimulatorAuthenticationType } from '../../type-definition'; + +export const authUtils = context => ({ + authType() { + if (context.requestAuthorizationMode === AmplifyAppSyncSimulatorAuthenticationType.API_KEY) { + return 'API Key Authorization'; + } else if (context.requestAuthorizationMode === AmplifyAppSyncSimulatorAuthenticationType.AWS_IAM) { + return 'IAM Authorization'; + } else if (context.requestAuthorizationMode === AmplifyAppSyncSimulatorAuthenticationType.AMAZON_COGNITO_USER_POOLS) { + return 'User Pool Authorization'; + } else if (context.requestAuthorizationMode === AmplifyAppSyncSimulatorAuthenticationType.OPENID_CONNECT) { + return 'Open ID Connect Authorization'; + } + + return 'API Key Authorization'; + }, +}); diff --git a/packages/amplify-appsync-simulator/src/velocity/util/index.ts b/packages/amplify-appsync-simulator/src/velocity/util/index.ts index 4844ddc1eec..3001ea9df11 100644 --- a/packages/amplify-appsync-simulator/src/velocity/util/index.ts +++ b/packages/amplify-appsync-simulator/src/velocity/util/index.ts @@ -3,14 +3,17 @@ import { generalUtils } from './general-utils'; import { dynamodbUtils } from './dynamodb-utils'; import { listUtils } from './list-utils'; import { mapUtils } from './map-utils'; +import { authUtils } from './auth-utils'; import { transformUtils } from './transform'; import { time } from './time'; import { rds } from './rds'; import { str } from './str'; import { math } from './math'; import { GraphQLResolveInfo } from 'graphql'; -export function create(errors = [], now: Date = new Date(), info: GraphQLResolveInfo) { +import { AppSyncGraphQLExecutionContext } from '../../utils/graphql-runner'; +export function create(errors = [], now: Date = new Date(), info: GraphQLResolveInfo, context: AppSyncGraphQLExecutionContext) { return { + ...authUtils(context), ...generalUtils, dynamodb: dynamodbUtils, list: listUtils, diff --git a/packages/amplify-category-api/amplify-plugin.json b/packages/amplify-category-api/amplify-plugin.json index 8cf4ad9d46c..8bd97a6a3c7 100644 --- a/packages/amplify-category-api/amplify-plugin.json +++ b/packages/amplify-category-api/amplify-plugin.json @@ -1,18 +1,9 @@ { - "name": "api", - "type": "category", - "commands": [ - "add-graphql-datasource", - "add", - "console", - "gql-compile", - "push", - "remove", - "update", - "help" - ], - "commandAliases":{ - "configure": "update" - }, - "eventHandlers": [] -} \ No newline at end of file + "name": "api", + "type": "category", + "commands": ["add-graphql-datasource", "add", "console", "gql-compile", "push", "rebuild", "remove", "update", "help"], + "commandAliases": { + "configure": "update" + }, + "eventHandlers": [] +} diff --git a/packages/amplify-category-api/package.json b/packages/amplify-category-api/package.json index e5fe23c82de..fa76cb59454 100644 --- a/packages/amplify-category-api/package.json +++ b/packages/amplify-category-api/package.json @@ -65,6 +65,8 @@ "@octokit/rest": "^18.0.9", "amplify-cli-core": "1.31.1", "amplify-headless-interface": "1.10.0", + "amplify-prompts": "1.2.0", + "amplify-provider-awscloudformation": "4.61.1", "amplify-util-headless-input": "1.5.4", "chalk": "^4.1.1", "constructs": "^3.3.125", diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/blank-schema.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/blank-schema.graphql new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema-v2.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema-v2.graphql new file mode 100644 index 00000000000..2619b187297 --- /dev/null +++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema-v2.graphql @@ -0,0 +1,18 @@ +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! + post: Post @belongsTo + content: String! +} diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema-v2.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema-v2.graphql new file mode 100644 index 00000000000..d1706a3bab9 --- /dev/null +++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema-v2.graphql @@ -0,0 +1,18 @@ +type Task + @model + @auth( + rules: [ + { allow: groups, groups: ["Managers"], operations: [create, update, read, delete] } + { allow: groups, groups: ["Employees"], operations: [read] } + ] + ) { + id: ID! + title: String! + description: String + status: String +} + +type PrivateNote @model @auth(rules: [{ allow: owner }]) { + id: ID! + content: String! +} diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema-v2.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema-v2.graphql new file mode 100644 index 00000000000..74b097ceae9 --- /dev/null +++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema-v2.graphql @@ -0,0 +1,5 @@ +type Todo @model { + id: ID! + name: String! + description: String +} diff --git a/packages/amplify-category-api/src/__tests__/commands/api/rebuild.test.ts b/packages/amplify-category-api/src/__tests__/commands/api/rebuild.test.ts new file mode 100644 index 00000000000..ce28470ecd0 --- /dev/null +++ b/packages/amplify-category-api/src/__tests__/commands/api/rebuild.test.ts @@ -0,0 +1,64 @@ +import { $TSContext, FeatureFlags, stateManager } from 'amplify-cli-core'; +import { printer, prompter } from 'amplify-prompts'; +import { mocked } from 'ts-jest/utils'; +import { run } from '../../../commands/api/rebuild'; + +jest.mock('amplify-cli-core'); +jest.mock('amplify-prompts'); + +const FeatureFlags_mock = mocked(FeatureFlags); +const stateManager_mock = mocked(stateManager); +const printer_mock = mocked(printer); +const prompter_mock = mocked(prompter); + +FeatureFlags_mock.getBoolean.mockReturnValue(true); + +beforeEach(jest.clearAllMocks); + +const pushResourcesMock = jest.fn(); + +const context_stub = { + amplify: { + constructExeInfo: jest.fn(), + pushResources: pushResourcesMock, + }, + parameters: { + first: 'resourceName', + }, +} as unknown as $TSContext; + +it('prints error if iterative updates not enabled', async () => { + FeatureFlags_mock.getBoolean.mockReturnValueOnce(false); + + await run(context_stub); + + expect(printer_mock.error.mock.calls.length).toBe(1); + expect(pushResourcesMock.mock.calls.length).toBe(0); +}); + +it('exits early if no api in project', async () => { + stateManager_mock.getMeta.mockReturnValueOnce({ + api: {}, + }); + + await run(context_stub); + + expect(printer_mock.info.mock.calls.length).toBe(1); + expect(pushResourcesMock.mock.calls.length).toBe(0); +}); + +it('asks for strong confirmation before continuing', async () => { + stateManager_mock.getMeta.mockReturnValueOnce({ + api: { + testapiname: { + service: 'AppSync', + }, + }, + }); + + await run(context_stub); + + expect(prompter_mock.input.mock.calls.length).toBe(1); + expect(pushResourcesMock.mock.calls.length).toBe(1); + expect(pushResourcesMock.mock.calls[0][4]).toBe(true); // rebuild flag is set +}); diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/cfn-api-artifact-handler.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/cfn-api-artifact-handler.test.ts.snap index 36f39965a9b..3b9aa6928f1 100644 --- a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/cfn-api-artifact-handler.test.ts.snap +++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/cfn-api-artifact-handler.test.ts.snap @@ -18,6 +18,7 @@ Object { }, Object { "apiKeyConfig": Object { + "apiKeyExpirationDate": undefined, "apiKeyExpirationDays": undefined, "description": undefined, }, diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cfn-api-artifact-handler.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cfn-api-artifact-handler.test.ts index 28e2aec71d3..e8dfa51770a 100644 --- a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cfn-api-artifact-handler.test.ts +++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cfn-api-artifact-handler.test.ts @@ -30,7 +30,7 @@ jest.mock('../../../provider-utils/awscloudformation/utils/amplify-meta-utils', jest.mock('amplify-cli-core'); -const fs_mock = (fs as unknown) as jest.Mocked; +const fs_mock = fs as unknown as jest.Mocked; const writeTransformerConfiguration_mock = writeTransformerConfiguration as jest.MockedFunction; const getAppSyncResourceName_mock = getAppSyncResourceName as jest.MockedFunction; const getAppSyncAuthConfig_mock = getAppSyncAuthConfig as jest.MockedFunction; diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/prompt-to-add-api-key.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/prompt-to-add-api-key.test.ts new file mode 100644 index 00000000000..871b8c8f1ea --- /dev/null +++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/prompt-to-add-api-key.test.ts @@ -0,0 +1,44 @@ +import { $TSContext } from 'amplify-cli-core'; +import * as prompts from 'amplify-prompts'; +import { promptToAddApiKey } from '../../../provider-utils/awscloudformation/prompt-to-add-api-key'; +import * as walkthrough from '../../../provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough'; +import * as cfnApiArtifactHandler from '../../../provider-utils/awscloudformation/cfn-api-artifact-handler'; + +jest.mock('../../../provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough', () => ({ + askApiKeyQuestions: jest.fn(), +})); + +jest.mock('../../../provider-utils/awscloudformation/cfn-api-artifact-handler', () => ({ + getCfnApiArtifactHandler: jest.fn(() => { + return { updateArtifacts: jest.fn() }; + }), +})); + +jest.mock('amplify-prompts', () => ({ + prompter: { + confirmContinue: jest.fn().mockImplementation(() => true), + }, +})); + +describe('prompt to add Api Key', () => { + it('runs through expected user flow: print info, update files', async () => { + const envName = 'envone'; + const ctx = { + amplify: { + getEnvInfo() { + return { envName }; + }, + }, + } as unknown as $TSContext; + + jest.spyOn(prompts.prompter, 'confirmContinue'); + jest.spyOn(walkthrough, 'askApiKeyQuestions'); + jest.spyOn(cfnApiArtifactHandler, 'getCfnApiArtifactHandler'); + + await promptToAddApiKey(ctx); + + expect(prompts.prompter.confirmContinue).toHaveBeenCalledWith('Would you like to create an API Key?'); + expect(walkthrough.askApiKeyQuestions).toHaveBeenCalledTimes(1); + expect(cfnApiArtifactHandler.getCfnApiArtifactHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.test.ts index a664b0b4698..6e6a75c8d8c 100644 --- a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.test.ts +++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.test.ts @@ -3,7 +3,7 @@ import { askAdditionalAuthQuestions, } from '../../../../provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough'; import { authConfigHasApiKey, getAppSyncAuthConfig } from '../../../../provider-utils/awscloudformation/utils/amplify-meta-utils'; -import { FeatureFlags, CLIEnvironmentProvider, FeatureFlagRegistration } from 'amplify-cli-core'; +import { FeatureFlags } from 'amplify-cli-core'; jest.mock('../../../../provider-utils/awscloudformation/utils/amplify-meta-utils', () => ({ getAppSyncAuthConfig: jest.fn(), authConfigHasApiKey: jest.fn(), @@ -25,16 +25,15 @@ const context_stub = (prompt: jest.Mock) => ({ }); type IAMArtifact = { - attributes: string[], - policy: any, + attributes: string[]; + policy: any; }; - describe('get IAM policies', () => { beforeEach(() => { jest.resetModules(); }); -it('does not include API key if none exists', async () => { + it('does not include API key if none exists', async () => { mockGetBoolean.mockImplementationOnce(() => true); authConfigHasApiKey_mock.mockImplementationOnce(() => false); const iamArtifact: IAMArtifact = getIAMPolicies('testResourceName', ['Query'], context_stub(confirmPromptFalse_mock)); @@ -47,7 +46,7 @@ it('does not include API key if none exists', async () => { expect(iamArtifact.policy.Resource[0]['Fn::Join'][1][6]).toMatch('/types/Query/*'); }); -it('includes API key if it exists', async () => { + it('includes API key if it exists', async () => { mockGetBoolean.mockImplementationOnce(() => true); authConfigHasApiKey_mock.mockImplementationOnce(() => true); const iamArtifact: IAMArtifact = getIAMPolicies('testResourceName', ['Query'], context_stub(confirmPromptFalse_mock)); @@ -61,7 +60,7 @@ it('includes API key if it exists', async () => { expect(iamArtifact.policy.Resource[0]['Fn::Join'][1][6]).toMatch('/types/Query/*'); }); -it('policy path includes the new format for graphql operations', async () => { + it('policy path includes the new format for graphql operations', async () => { mockGetBoolean.mockImplementationOnce(() => true); authConfigHasApiKey_mock.mockImplementationOnce(() => false); const iamArtifact: IAMArtifact = getIAMPolicies('testResourceName', ['Query', 'Mutate'], context_stub(confirmPromptFalse_mock)); @@ -85,7 +84,7 @@ it('policy path includes the new format for graphql operations', async () => { ] `); expect(iamArtifact.policy.Action).toHaveLength(4); - expect(iamArtifact.policy.Action).toEqual(["appsync:Create*", "appsync:StartSchemaCreation", "appsync:GraphQL", "appsync:Update*"]); + expect(iamArtifact.policy.Action).toEqual(['appsync:Create*', 'appsync:StartSchemaCreation', 'appsync:GraphQL', 'appsync:Update*']); expect(iamArtifact.policy.Resource).toHaveLength(2); expect(iamArtifact.policy.Resource[0]['Fn::Join'][1][6]).toMatch('/*'); }); diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts.snap index 73049e4ca95..3c18c0fa241 100644 --- a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts.snap +++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts.snap @@ -12,6 +12,7 @@ Object { exports[`AppSyncAuthType to authConfig maps API_KEY correctly 1`] = ` Object { "apiKeyConfig": Object { + "apiKeyExpirationDate": undefined, "apiKeyExpirationDays": 120, "description": undefined, }, @@ -47,6 +48,7 @@ Object { exports[`authConfig to AppSyncAuthType maps API_KEY auth correctly 1`] = ` Object { + "apiKeyExpirationDate": undefined, "expirationTime": 120, "keyDescription": "api key description", "mode": "API_KEY", diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/global-sandbox-mode.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/global-sandbox-mode.test.ts new file mode 100644 index 00000000000..a895da7f667 --- /dev/null +++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/global-sandbox-mode.test.ts @@ -0,0 +1,21 @@ +import { defineGlobalSandboxMode } from '../../../../provider-utils/awscloudformation/utils/global-sandbox-mode'; +import { $TSContext } from 'amplify-cli-core'; + +describe('global sandbox mode GraphQL directive', () => { + it('returns AMPLIFY_DIRECTIVE type with code comment, directive, and env name', () => { + const envName = 'envone'; + const ctx = <$TSContext>{ + amplify: { + getEnvInfo() { + return { envName }; + }, + }, + }; + + expect(defineGlobalSandboxMode(ctx)) + .toBe(`# This allows public create, read, update, and delete access for a limited time to all models via API Key. +# To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql-transformer/auth +type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: \"${envName}\") # FOR TESTING ONLY!\n +`); + }); +}); diff --git a/packages/amplify-category-api/src/commands/api.js b/packages/amplify-category-api/src/commands/api.js index 948dc4ae4a2..e8c261e00df 100644 --- a/packages/amplify-category-api/src/commands/api.js +++ b/packages/amplify-category-api/src/commands/api.js @@ -41,6 +41,11 @@ module.exports = { name: 'console', description: 'Opens the web console for the selected api service', }, + { + name: 'rebuild', + description: + 'Removes and recreates all DynamoDB tables backing a GraphQL API. Useful for resetting test data during the development phase of an app', + }, ]; context.amplify.showHelp(header, commands); diff --git a/packages/amplify-category-api/src/commands/api/rebuild.ts b/packages/amplify-category-api/src/commands/api/rebuild.ts new file mode 100644 index 00000000000..26b8ead3641 --- /dev/null +++ b/packages/amplify-category-api/src/commands/api/rebuild.ts @@ -0,0 +1,40 @@ +import { $TSAny, $TSContext, FeatureFlags, stateManager } from 'amplify-cli-core'; +import { printer, prompter, exact } from 'amplify-prompts'; + +const subcommand = 'rebuild'; +const category = 'api'; + +export const name = subcommand; + +const rebuild = true; + +export const run = async (context: $TSContext) => { + if (!FeatureFlags.getBoolean('graphqlTransformer.enableIterativeGSIUpdates')) { + printer.error('Iterative GSI Updates must be enabled to rebuild an API. See https://docs.amplify.aws/cli/reference/feature-flags/'); + return; + } + const apiNames = Object.entries(stateManager.getMeta()?.api || {}) + .filter(([_, apiResource]) => (apiResource as $TSAny).service === 'AppSync') + .map(([name]) => name); + if (apiNames.length === 0) { + printer.info('No GraphQL API configured in the project. Only GraphQL APIs can be rebuilt. To add a GraphQL API run `amplify add api`.'); + return; + } + if (apiNames.length > 1) { + // this condition should never hit as we have upstream defensive logic to prevent multiple GraphQL APIs. But just to cover all the bases + printer.error( + 'You have multiple GraphQL APIs in the project. Only one GraphQL API is allowed per project. Run `amplify remove api` to remove an API.', + ); + return; + } + const apiName = apiNames[0]; + printer.warn(`This will recreate all tables backing models in your GraphQL API ${apiName}.`); + printer.warn('ALL EXISTING DATA IN THESE TABLES WILL BE LOST.'); + await prompter.input('Type the name of the API to confirm you want to continue', { + validate: exact(apiName, 'Input does not match the GraphQL API name'), + }); + const { amplify, parameters } = context; + const resourceName = parameters.first; + amplify.constructExeInfo(context); + return amplify.pushResources(context, category, resourceName, undefined, rebuild); +}; diff --git a/packages/amplify-category-api/src/index.ts b/packages/amplify-category-api/src/index.ts index af7f1625011..4ada1ab74d5 100644 --- a/packages/amplify-category-api/src/index.ts +++ b/packages/amplify-category-api/src/index.ts @@ -3,6 +3,9 @@ import fs from 'fs-extra'; import path from 'path'; import { run } from './commands/api/console'; import { getCfnApiArtifactHandler } from './provider-utils/awscloudformation/cfn-api-artifact-handler'; +import { askAuthQuestions } from './provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough'; +import { getAppSyncResourceName, getAppSyncAuthConfig } from './provider-utils/awscloudformation//utils/amplify-meta-utils'; +import { authConfigToAppSyncAuthType } from './provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper'; export { NETWORK_STACK_LOGICAL_ID } from './category-constants'; export { DEPLOYMENT_MECHANISM } from './provider-utils/awscloudformation/base-api-stack'; @@ -15,6 +18,7 @@ export { processDockerConfig, } from './provider-utils/awscloudformation/utils/containers-artifacts'; export { getContainers } from './provider-utils/awscloudformation/docker-compose'; +export { promptToAddApiKey } from './provider-utils/awscloudformation/prompt-to-add-api-key'; const category = 'api'; @@ -217,3 +221,32 @@ export async function handleAmplifyEvent(context, args) { context.print.info(`${category} handleAmplifyEvent to be implemented`); context.print.info(`Received event args ${args}`); } + +export async function addGraphQLAuthorizationMode(context, args) { + const { authType, printLeadText, authSettings } = args; + const apiName = getAppSyncResourceName(context.amplify.getProjectMeta()); + if (!apiName) { + return; + } + + const authConfig = getAppSyncAuthConfig(context.amplify.getProjectMeta()); + const addAuthConfig = await askAuthQuestions(authType, context, printLeadText, authSettings); + authConfig.additionalAuthenticationProviders.push(addAuthConfig); + await context.amplify.updateamplifyMetaAfterResourceUpdate(category, apiName, 'output', { authConfig }); + await context.amplify.updateBackendConfigAfterResourceUpdate(category, apiName, 'output', { authConfig }); + + await getCfnApiArtifactHandler(context).updateArtifacts( + { + version: 1, + serviceModification: { + serviceName: 'AppSync', + additionalAuthTypes: authConfig.additionalAuthenticationProviders.map(authConfigToAppSyncAuthType), + }, + }, + { + skipCompile: false, + }, + ); + + return addAuthConfig; +} diff --git a/packages/amplify-category-api/src/provider-utils/api-artifact-handler.ts b/packages/amplify-category-api/src/provider-utils/api-artifact-handler.ts index a3b7de27c51..7dbab41d779 100644 --- a/packages/amplify-category-api/src/provider-utils/api-artifact-handler.ts +++ b/packages/amplify-category-api/src/provider-utils/api-artifact-handler.ts @@ -1,6 +1,10 @@ import { AddApiRequest, UpdateApiRequest } from 'amplify-headless-interface'; +export interface ApiArtifactHandlerOptions { + skipCompile?: boolean; +} + export interface ApiArtifactHandler { createArtifacts(request: AddApiRequest): Promise; - updateArtifacts(request: UpdateApiRequest): Promise; + updateArtifacts(request: UpdateApiRequest, opts?: ApiArtifactHandlerOptions): Promise; } diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts index cc421715b5d..f06fc279414 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts @@ -12,7 +12,7 @@ import _ from 'lodash'; import * as path from 'path'; import uuid from 'uuid'; import { category } from '../../category-constants'; -import { ApiArtifactHandler } from '../api-artifact-handler'; +import { ApiArtifactHandler, ApiArtifactHandlerOptions } from '../api-artifact-handler'; import { cfnParametersFilename, gqlSchemaFilename, provider, rootAssetDir } from './aws-constants'; import { authConfigHasApiKey, checkIfAuthExists, getAppSyncAuthConfig, getAppSyncResourceName } from './utils/amplify-meta-utils'; import { appSyncAuthTypeToAuthConfig } from './utils/auth-config-to-app-sync-auth-type-bi-di-mapper'; @@ -96,7 +96,7 @@ class CfnApiArtifactHandler implements ApiArtifactHandler { // TODO once the AddApiRequest contains multiple services this class should depend on an ApiArtifactHandler // for each service and delegate to the correct one - updateArtifacts = async (request: UpdateApiRequest): Promise => { + updateArtifacts = async (request: UpdateApiRequest, opts?: ApiArtifactHandlerOptions): Promise => { const updates = request.serviceModification; const apiName = getAppSyncResourceName(this.context.amplify.getProjectMeta()); if (!apiName) { @@ -118,11 +118,14 @@ class CfnApiArtifactHandler implements ApiArtifactHandler { if (updates.additionalAuthTypes) { authConfig.additionalAuthenticationProviders = updates.additionalAuthTypes.map(appSyncAuthTypeToAuthConfig); } - await this.context.amplify.executeProviderUtils(this.context, 'awscloudformation', 'compileSchema', { - resourceDir, - parameters: this.getCfnParameters(apiName, authConfig, resourceDir), - authConfig, - }); + + if (!opts?.skipCompile) { + await this.context.amplify.executeProviderUtils(this.context, 'awscloudformation', 'compileSchema', { + resourceDir, + parameters: this.getCfnParameters(apiName, authConfig, resourceDir), + authConfig, + }); + } this.context.amplify.updateamplifyMetaAfterResourceUpdate(category, apiName, 'output', { authConfig }); this.context.amplify.updateBackendConfigAfterResourceUpdate(category, apiName, 'output', { authConfig }); diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/prompt-to-add-api-key.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/prompt-to-add-api-key.ts new file mode 100644 index 00000000000..4e3a92b5ea4 --- /dev/null +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/prompt-to-add-api-key.ts @@ -0,0 +1,27 @@ +import { $TSContext } from 'amplify-cli-core'; +import { askApiKeyQuestions } from './service-walkthroughs/appSync-walkthrough'; +import { authConfigToAppSyncAuthType } from './utils/auth-config-to-app-sync-auth-type-bi-di-mapper'; +import { getCfnApiArtifactHandler } from './cfn-api-artifact-handler'; +import { prompter } from 'amplify-prompts'; + +export async function promptToAddApiKey(context: $TSContext): Promise { + if (await prompter.confirmContinue('Would you like to create an API Key?')) { + const apiKeyConfig = await askApiKeyQuestions(); + const authConfig = [apiKeyConfig]; + + await getCfnApiArtifactHandler(context).updateArtifacts( + { + version: 1, + serviceModification: { + serviceName: 'AppSync', + additionalAuthTypes: authConfig.map(authConfigToAppSyncAuthType), + }, + }, + { + skipCompile: true, + }, + ); + + return apiKeyConfig; + } +} diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts index f2878dbb503..379a66a4ee9 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts @@ -4,12 +4,13 @@ import inquirer from 'inquirer'; import fs from 'fs-extra'; import path from 'path'; import { rootAssetDir } from '../aws-constants'; -import { collectDirectivesByTypeNames, readProjectConfiguration, ConflictHandlerType } from 'graphql-transformer-core'; +import { collectDirectivesByTypeNames, readProjectConfiguration } from 'graphql-transformer-core'; import { category } from '../../../category-constants'; import { UpdateApiRequest } from '../../../../../amplify-headless-interface/lib/interface/api/update'; import { authConfigToAppSyncAuthType } from '../utils/auth-config-to-app-sync-auth-type-bi-di-mapper'; import { resolverConfigToConflictResolution } from '../utils/resolver-config-to-conflict-resolution-bi-di-mapper'; import _ from 'lodash'; +import chalk from 'chalk'; import { getAppSyncAuthConfig, checkIfAuthExists, authConfigHasApiKey } from '../utils/amplify-meta-utils'; import { ResourceAlreadyExistsError, @@ -21,6 +22,8 @@ import { $TSContext, open, } from 'amplify-cli-core'; +import { Duration, Expiration } from '@aws-cdk/core'; +import { defineGlobalSandboxMode } from '../utils/global-sandbox-mode'; const serviceName = 'AppSync'; const elasticContainerServiceName = 'ElasticContainer'; @@ -46,6 +49,63 @@ const authProviderChoices = [ }, ]; +const conflictResolutionHanlderChoices = [ + { + name: 'Auto Merge', + value: 'AUTOMERGE', + }, + { + name: 'Optimistic Concurrency', + value: 'OPTIMISTIC_CONCURRENCY', + }, + { + name: 'Custom Lambda', + value: 'LAMBDA', + }, + { + name: 'Learn More', + value: 'Learn More', + }, +]; + +const schemaTemplatesV1 = [ + { + name: 'Single object with fields (e.g., “Todo” with ID, name, description)', + value: 'single-object-schema.graphql', + }, + { + name: 'One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)', + value: 'many-relationship-schema.graphql', + }, + { + name: 'Objects with fine-grained access control (e.g., a project management app with owner-based authorization)', + value: 'single-object-auth-schema.graphql', + }, + { + name: 'Blank Schema', + value: 'blank-schema.graphql', + }, +]; + +const schemaTemplatesV2 = [ + { + name: 'Single object with fields (e.g., “Todo” with ID, name, description)', + value: 'single-object-schema-v2.graphql', + }, + { + name: 'One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)', + value: 'many-relationship-schema-v2.graphql', + }, + { + name: 'Objects with fine-grained access control (e.g., a project management app with owner-based authorization)', + value: 'single-object-auth-schema-v2.graphql', + }, + { + name: 'Blank Schema', + value: 'blank-schema.graphql', + }, +]; + export const openConsole = async (context: $TSContext) => { const amplifyMeta = stateManager.getMeta(); const categoryAmplifyMeta = amplifyMeta[category]; @@ -136,90 +196,129 @@ export const openConsole = async (context: $TSContext) => { } }; -export const serviceWalkthrough = async (context: $TSContext, defaultValuesFilename, serviceMetadata) => { - const resourceName = resourceAlreadyExists(context); +const serviceApiInputWalkthrough = async (context: $TSContext, defaultValuesFilename, serviceMetadata) => { + let continuePrompt = false; let authConfig; let defaultAuthType; let resolverConfig; - - if (resourceName) { - const errMessage = - 'You already have an AppSync API in your project. Use the "amplify update api" command to update your existing AppSync API.'; - context.print.warning(errMessage); - await context.usageData.emitError(new ResourceAlreadyExistsError(errMessage)); - exitOnNextTick(0); - } - const { amplify } = context; const { inputs } = serviceMetadata; const defaultValuesSrc = `${__dirname}/../default-values/${defaultValuesFilename}`; const { getAllDefaults } = require(defaultValuesSrc); const allDefaultValues = getAllDefaults(amplify.getProjectDetails()); - const resourceQuestions = [ - { - type: inputs[1].type, - name: inputs[1].key, - message: inputs[1].question, - validate: amplify.inputValidation(inputs[1]), - default: () => { - const defaultValue = allDefaultValues[inputs[1].key]; - return defaultValue; + let resourceAnswers = {}; + resourceAnswers[inputs[1].key] = allDefaultValues[inputs[1].key]; + resourceAnswers[inputs[0].key] = resourceAnswers[inputs[1].key]; + + // + // Default authConfig - API Key (expires in 7 days) + // + authConfig = { + defaultAuthentication: { + apiKeyConfig: { + apiKeyExpirationDays: 7, }, + authenticationType: 'API_KEY', }, - ]; + additionalAuthenticationProviders: [], + }; - // API name question + // + // Repeat prompt until user selects Continue + // + while (!continuePrompt) { + const getAuthModeChoice = async () => { + if (authConfig.defaultAuthentication.authenticationType === 'API_KEY') { + return `${ + authProviderChoices.find(choice => choice.value === authConfig.defaultAuthentication.authenticationType).name + } (default, expiration time: ${authConfig.defaultAuthentication.apiKeyConfig.apiKeyExpirationDays} days from now)`; + } + return `${authProviderChoices.find(choice => choice.value === authConfig.defaultAuthentication.authenticationType).name} (default)`; + }; - const resourceAnswers = await inquirer.prompt(resourceQuestions); - resourceAnswers[inputs[0].key] = resourceAnswers[inputs[1].key]; + const getAdditionalAuthModeChoices = async () => { + let additionalAuthModesText = ''; + authConfig.additionalAuthenticationProviders.map(async authMode => { + additionalAuthModesText += `, ${authProviderChoices.find(choice => choice.value === authMode.authenticationType).name}`; + }); + return additionalAuthModesText; + }; + + const basicInfoQuestionChoices = []; - // Ask additonal questions + basicInfoQuestionChoices.push({ + name: chalk`{bold Name:} ${resourceAnswers[inputs[1].key]}`, + value: 'API_NAME', + }); - ({ authConfig, defaultAuthType } = await askDefaultAuthQuestion(context)); - ({ authConfig, resolverConfig } = await askAdditionalQuestions(context, authConfig, defaultAuthType)); + basicInfoQuestionChoices.push({ + name: chalk`{bold Authorization modes:} ${await getAuthModeChoice()}${await getAdditionalAuthModeChoices()}`, + value: 'API_AUTH_MODE', + }); - // Ask schema file question + basicInfoQuestionChoices.push({ + name: chalk`{bold Conflict detection (required for DataStore):} ${resolverConfig?.project ? 'Enabled' : 'Disabled'}`, + value: 'CONFLICT_DETECTION', + }); - const schemaFileQuestion = { - type: inputs[2].type, - name: inputs[2].key, - message: inputs[2].question, - validate: amplify.inputValidation(inputs[2]), - default: () => { - const defaultValue = allDefaultValues[inputs[2].key]; - return defaultValue; - }, - }; + if (resolverConfig?.project) { + basicInfoQuestionChoices.push({ + name: chalk`{bold Conflict resolution strategy:} ${ + conflictResolutionHanlderChoices.find(x => x.value === resolverConfig.project.ConflictHandler).name + }`, + value: 'CONFLICT_STRATEGY', + }); + } - const schemaFileAnswer = await inquirer.prompt(schemaFileQuestion); + basicInfoQuestionChoices.push({ + name: 'Continue', + value: 'CONTINUE', + }); - let schemaContent = ''; - let askToEdit = true; - if (schemaFileAnswer[inputs[2].key]) { - // User has an annotated schema file - const filePathQuestion = { - type: inputs[3].type, - name: inputs[3].key, - message: inputs[3].question, - validate: amplify.inputValidation(inputs[3]), - }; - const { schemaFilePath } = await inquirer.prompt(filePathQuestion); - schemaContent = fs.readFileSync(schemaFilePath, 'utf8'); - askToEdit = false; - } else { - // Schema template selection - const templateSelectionQuestion = { - type: inputs[4].type, - name: inputs[4].key, - message: inputs[4].question, - choices: inputs[4].options.filter(templateSchemaFilter(authConfig)), - validate: amplify.inputValidation(inputs[4]), + const basicInfoQuestion = { + type: 'list', + name: 'basicApiSettings', + message: 'Here is the GraphQL API that we will create. Select a setting to edit or continue', + default: 'CONTINUE', + choices: basicInfoQuestionChoices, }; - const { templateSelection } = await inquirer.prompt(templateSelectionQuestion); - const schemaFilePath = path.join(graphqlSchemaDir, templateSelection); - schemaContent = fs.readFileSync(schemaFilePath, 'utf8'); + let { basicApiSettings } = await inquirer.prompt([basicInfoQuestion]); + + switch (basicApiSettings) { + case 'API_NAME': + const resourceQuestions = [ + { + type: inputs[1].type, + name: inputs[1].key, + message: inputs[1].question, + validate: amplify.inputValidation(inputs[1]), + default: () => { + const defaultValue = allDefaultValues[inputs[1].key]; + return defaultValue; + }, + }, + ]; + // API name question + resourceAnswers = await inquirer.prompt(resourceQuestions); + resourceAnswers[inputs[0].key] = resourceAnswers[inputs[1].key]; + break; + case 'API_AUTH_MODE': + // Ask additonal questions + ({ authConfig, defaultAuthType } = await askDefaultAuthQuestion(context)); + ({ authConfig } = await askAdditionalQuestions(context, authConfig, defaultAuthType)); + break; + case 'CONFLICT_DETECTION': + resolverConfig = await askResolverConflictQuestion(context, resolverConfig); + break; + case 'CONFLICT_STRATEGY': + resolverConfig = await askResolverConflictHandlerQuestion(context); + break; + case 'CONTINUE': + continuePrompt = true; + break; + } } return { @@ -227,8 +326,100 @@ export const serviceWalkthrough = async (context: $TSContext, defaultValuesFilen output: { authConfig, }, - noCfnFile: true, resolverConfig, + }; +}; + +const updateApiInputWalkthrough = async (context, project, resolverConfig, modelTypes) => { + let authConfig; + let defaultAuthType; + const updateChoices = [ + { + name: 'Authorization modes', + value: 'AUTH_MODE', + }, + ]; + // check if DataStore is enabled for the entire API + if (project.config && !_.isEmpty(project.config.ResolverConfig)) { + updateChoices.push({ + name: 'Conflict resolution strategy', + value: 'CONFLICT_STRATEGY', + }); + updateChoices.push({ + name: 'Disable conflict detection', + value: 'DISABLE_CONFLICT', + }); + } else { + updateChoices.push({ + name: 'Enable conflict detection (required for DataStore)', + value: 'ENABLE_CONFLICT', + }); + } + + const updateOptionQuestion = { + type: 'list', + name: 'updateOption', + message: 'Select a setting to edit', + choices: updateChoices, + }; + + const { updateOption } = await inquirer.prompt([updateOptionQuestion]); + + if (updateOption === 'ENABLE_CONFLICT') { + resolverConfig = await askResolverConflictHandlerQuestion(context, modelTypes); + } else if (updateOption === 'DISABLE_CONFLICT') { + resolverConfig = {}; + } else if (updateOption === 'AUTH_MODE') { + ({ authConfig, defaultAuthType } = await askDefaultAuthQuestion(context)); + authConfig = await askAdditionalAuthQuestions(context, authConfig, defaultAuthType); + } else if (updateOption === 'CONFLICT_STRATEGY') { + resolverConfig = await askResolverConflictHandlerQuestion(context, modelTypes); + } + + return { + authConfig, + resolverConfig, + }; +}; + +export const serviceWalkthrough = async (context: $TSContext, defaultValuesFilename, serviceMetadata) => { + const resourceName = resourceAlreadyExists(context); + const providerPlugin = await import(context.amplify.getProviderPlugins(context).awscloudformation); + const transformerVersion = providerPlugin.getTransformerVersion(context); + + if (resourceName) { + const errMessage = + 'You already have an AppSync API in your project. Use the "amplify update api" command to update your existing AppSync API.'; + context.print.warning(errMessage); + await context.usageData.emitError(new ResourceAlreadyExistsError(errMessage)); + exitOnNextTick(0); + } + + const { amplify } = context; + const { inputs } = serviceMetadata; + + const basicInfoAnswers = await serviceApiInputWalkthrough(context, defaultValuesFilename, serviceMetadata); + let schemaContent = ''; + let askToEdit = true; + + // Schema template selection + const schemaTemplateOptions = transformerVersion === 2 ? schemaTemplatesV2 : schemaTemplatesV1; + const templateSelectionQuestion = { + type: inputs[4].type, + name: inputs[4].key, + message: inputs[4].question, + choices: schemaTemplateOptions.filter(templateSchemaFilter(basicInfoAnswers.output.authConfig)), + validate: amplify.inputValidation(inputs[4]), + }; + + const { templateSelection } = await inquirer.prompt(templateSelectionQuestion); + const schemaFilePath = path.join(graphqlSchemaDir, templateSelection); + schemaContent += transformerVersion === 2 ? defineGlobalSandboxMode(context) : ''; + schemaContent += fs.readFileSync(schemaFilePath, 'utf8'); + + return { + ...basicInfoAnswers, + noCfnFile: true, schemaContent, askToEdit, }; @@ -238,13 +429,13 @@ export const updateWalkthrough = async (context): Promise => { const { allResources } = await context.amplify.getResourceStatus(); let resourceDir; let resourceName; + let resource; let authConfig; - let defaultAuthType; const resources = allResources.filter(resource => resource.service === 'AppSync'); // There can only be one appsync resource if (resources.length > 0) { - const resource = resources[0]; + resource = resources[0]; if (resource.providerPlugin !== providerName) { // TODO: Move message string to seperate file throw new Error( @@ -265,6 +456,8 @@ export const updateWalkthrough = async (context): Promise => { const project = await readProjectConfiguration(resourceDir); let resolverConfig = project.config.ResolverConfig; + await displayApiInformation(context, resource, project); + // Check for common errors const directiveMap = collectDirectivesByTypeNames(project.schema); let modelTypes = []; @@ -276,45 +469,8 @@ export const updateWalkthrough = async (context): Promise => { } }); } - const updateChoices = [ - { - name: 'Walkthrough all configurations', - value: 'all', - }, - { - name: 'Update auth settings', - value: 'authUpdate', - }, - ]; - // check if DataStore is enabled for the entire API - if (project.config && !_.isEmpty(project.config.ResolverConfig)) { - updateChoices.push({ name: 'Disable DataStore for entire API', value: 'disableDatastore' }); - } else { - updateChoices.push({ name: 'Enable DataStore for entire API', value: 'enableDatastore' }); - } - - const updateOptionQuestion = { - type: 'list', - name: 'updateOption', - message: 'Select from the options below', - choices: updateChoices, - }; - const { updateOption } = await inquirer.prompt([updateOptionQuestion]); - - if (updateOption === 'enableDatastore') { - resolverConfig = { - project: { ConflictHandler: ConflictHandlerType.AUTOMERGE, ConflictDetection: 'VERSION' }, - }; - } else if (updateOption === 'disableDatastore') { - resolverConfig = {}; - } else if (updateOption === 'authUpdate') { - ({ authConfig, defaultAuthType } = await askDefaultAuthQuestion(context)); - authConfig = await askAdditionalAuthQuestions(context, authConfig, defaultAuthType); - } else if (updateOption === 'all') { - ({ authConfig, defaultAuthType } = await askDefaultAuthQuestion(context)); - ({ authConfig, resolverConfig } = await askAdditionalQuestions(context, authConfig, defaultAuthType, modelTypes)); - } + ({ authConfig, resolverConfig } = await updateApiInputWalkthrough(context, project, resolverConfig, modelTypes)); return { version: 1, @@ -330,111 +486,130 @@ export const updateWalkthrough = async (context): Promise => { }; }; -async function askAdditionalQuestions(context, authConfig, defaultAuthType, modelTypes?) { - let resolverConfig; +async function displayApiInformation(context, resource, project) { + let authModes: string[] = []; + authModes.push( + `- Default: ${await displayAuthMode(context, resource, resource.output.authConfig.defaultAuthentication.authenticationType)}`, + ); + await resource.output.authConfig.additionalAuthenticationProviders.map(async authMode => { + authModes.push(`- ${await displayAuthMode(context, resource, authMode.authenticationType)}`); + }); - const advancedSettingsQuestion = { - type: 'list', - name: 'advancedSettings', - message: 'Do you want to configure advanced settings for the GraphQL API', - choices: [ - { - name: 'No, I am done.', - value: false, - }, - { - name: 'Yes, I want to make some additional changes.', - value: true, - }, - ], - }; + context.print.info(''); - const advancedSettingsAnswer = await inquirer.prompt([advancedSettingsQuestion]); + context.print.success('General information'); + context.print.info('- Name: '.concat(resource.resourceName)); + if (resource?.output?.GraphQLAPIEndpointOutput) { + context.print.info(`- API endpoint: ${resource?.output?.GraphQLAPIEndpointOutput}`); + } + context.print.info(''); - if (advancedSettingsAnswer.advancedSettings) { - authConfig = await askAdditionalAuthQuestions(context, authConfig, defaultAuthType); - resolverConfig = await askResolverConflictQuestion(context, modelTypes); + context.print.success('Authorization modes'); + authModes.forEach(authMode => context.print.info(authMode)); + context.print.info(''); + + context.print.success('Conflict detection (required for DataStore)'); + if (project.config && !_.isEmpty(project.config.ResolverConfig)) { + context.print.info( + `- Conflict resolution strategy: ${ + conflictResolutionHanlderChoices.find(choice => choice.value === project.config.ResolverConfig.project.ConflictHandler).name + }`, + ); + } else { + context.print.info('- Disabled'); } - return { authConfig, resolverConfig }; + context.print.info(''); } -async function askResolverConflictQuestion(context, modelTypes?) { - let resolverConfig: any = {}; +async function displayAuthMode(context, resource, authMode) { + if (authMode == 'API_KEY' && resource.output.GraphQLAPIKeyOutput) { + let { apiKeys } = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'getAppSyncApiKeys', { + apiId: resource.output.GraphQLAPIIdOutput, + }); + let apiKeyExpires = apiKeys.find(key => key.id == resource.output.GraphQLAPIKeyOutput)?.expires; + if (!apiKeyExpires) { + return authProviderChoices.find(choice => choice.value === authMode).name; + } + let apiKeyExpiresDate = new Date(apiKeyExpires * 1000); + return `${authProviderChoices.find(choice => choice.value === authMode).name} expiring ${apiKeyExpiresDate}: ${ + resource.output.GraphQLAPIKeyOutput + }`; + } + return authProviderChoices.find(choice => choice.value === authMode).name; +} - if (await context.prompt.confirm('Enable conflict detection?')) { - const askConflictResolutionStrategy = async msg => { - let conflictResolutionStrategy; - - do { - const conflictResolutionQuestion: ListQuestion = { - type: 'list', - name: 'conflictResolutionStrategy', - message: msg, - default: 'AUTOMERGE', - choices: [ - { - name: 'Auto Merge', - value: 'AUTOMERGE', - }, - { - name: 'Optimistic Concurrency', - value: 'OPTIMISTIC_CONCURRENCY', - }, - { - name: 'Custom Lambda', - value: 'LAMBDA', - }, - { - name: 'Learn More', - value: 'Learn More', - }, - ], - }; - if (conflictResolutionStrategy === 'Learn More') { - conflictResolutionQuestion.prefix = dataStoreLearnMore; - } - ({ conflictResolutionStrategy } = await inquirer.prompt([conflictResolutionQuestion])); - } while (conflictResolutionStrategy === 'Learn More'); +async function askAdditionalQuestions(context, authConfig, defaultAuthType, modelTypes?) { + authConfig = await askAdditionalAuthQuestions(context, authConfig, defaultAuthType); + return { authConfig }; +} - let syncConfig: any = { - ConflictHandler: conflictResolutionStrategy, - ConflictDetection: 'VERSION', - }; +async function askResolverConflictQuestion(context, resolverConfig, modelTypes?) { + let resolverConfigResponse: any = {}; + + if (await context.prompt.confirm('Enable conflict detection?', !resolverConfig?.project)) { + resolverConfigResponse = await askResolverConflictHandlerQuestion(context, modelTypes); + } + + return resolverConfigResponse; +} - if (conflictResolutionStrategy === 'LAMBDA') { - const { newFunction, lambdaFunctionName } = await askSyncFunctionQuestion(context); - syncConfig.LambdaConflictHandler = { - name: lambdaFunctionName, - new: newFunction, - }; +async function askResolverConflictHandlerQuestion(context, modelTypes?) { + let resolverConfig: any = {}; + const askConflictResolutionStrategy = async msg => { + let conflictResolutionStrategy; + + do { + const conflictResolutionQuestion: ListQuestion = { + type: 'list', + name: 'conflictResolutionStrategy', + message: msg, + default: 'AUTOMERGE', + choices: conflictResolutionHanlderChoices, + }; + if (conflictResolutionStrategy === 'Learn More') { + conflictResolutionQuestion.prefix = dataStoreLearnMore; } + ({ conflictResolutionStrategy } = await inquirer.prompt([conflictResolutionQuestion])); + } while (conflictResolutionStrategy === 'Learn More'); - return syncConfig; + let syncConfig: any = { + ConflictHandler: conflictResolutionStrategy, + ConflictDetection: 'VERSION', }; - resolverConfig.project = await askConflictResolutionStrategy('Select the default resolution strategy'); + if (conflictResolutionStrategy === 'LAMBDA') { + const { newFunction, lambdaFunctionName } = await askSyncFunctionQuestion(context); + syncConfig.LambdaConflictHandler = { + name: lambdaFunctionName, + new: newFunction, + }; + } - // Ask for per-model resolver override setting + return syncConfig; + }; + + resolverConfig.project = await askConflictResolutionStrategy('Select the default resolution strategy'); + + // Ask for per-model resolver override setting - if (modelTypes && modelTypes.length > 0) { - if (await context.prompt.confirm('Do you want to override default per model settings?', false)) { - const modelTypeQuestion = { - type: 'checkbox', - name: 'selectedModelTypes', - message: 'Select the models from below:', - choices: modelTypes, - }; + if (modelTypes && modelTypes.length > 0) { + if (await context.prompt.confirm('Do you want to override default per model settings?', false)) { + const modelTypeQuestion = { + type: 'checkbox', + name: 'selectedModelTypes', + message: 'Select the models from below:', + choices: modelTypes, + }; - const { selectedModelTypes } = await inquirer.prompt([modelTypeQuestion]); + const { selectedModelTypes } = await inquirer.prompt([modelTypeQuestion]); - if (selectedModelTypes.length > 0) { - resolverConfig.models = {}; - for (let i = 0; i < selectedModelTypes.length; i += 1) { - resolverConfig.models[selectedModelTypes[i]] = await askConflictResolutionStrategy( - `Select the resolution strategy for ${selectedModelTypes[i]} model`, - ); - } + if (selectedModelTypes.length > 0) { + resolverConfig.models = {}; + for (let i = 0; i < selectedModelTypes.length; i += 1) { + resolverConfig.models[selectedModelTypes[i]] = await askConflictResolutionStrategy( + `Select the resolution strategy for ${selectedModelTypes[i]} model`, + ); } } } @@ -492,7 +667,7 @@ async function askDefaultAuthQuestion(context) { const { defaultAuthType } = await inquirer.prompt([defaultAuthTypeQuestion]); // Get default auth configured - const defaultAuth = await askAuthQuestions(defaultAuthType, context); + const defaultAuth = await askAuthQuestions(defaultAuthType, context, false, currentAuthConfig?.defaultAuthentication); return { authConfig: { @@ -508,9 +683,11 @@ export async function askAdditionalAuthQuestions(context, authConfig, defaultAut if (await context.prompt.confirm('Configure additional auth types?')) { // Get additional auth configured const remainingAuthProviderChoices = authProviderChoices.filter(p => p.value !== defaultAuthType); - const currentAdditionalAuth = ((currentAuthConfig && currentAuthConfig.additionalAuthenticationProviders - ? currentAuthConfig.additionalAuthenticationProviders - : []) as any[]).map(authProvider => authProvider.authenticationType); + const currentAdditionalAuth = ( + (currentAuthConfig && currentAuthConfig.additionalAuthenticationProviders + ? currentAuthConfig.additionalAuthenticationProviders + : []) as any[] + ).map(authProvider => authProvider.authenticationType); const additionalProvidersQuestion: CheckboxQuestion = { type: 'checkbox', @@ -525,7 +702,12 @@ export async function askAdditionalAuthQuestions(context, authConfig, defaultAut for (let i = 0; i < additionalProvidersAnswer.authType.length; i += 1) { const authProvider = additionalProvidersAnswer.authType[i]; - const config = await askAuthQuestions(authProvider, context, true); + const config = await askAuthQuestions( + authProvider, + context, + true, + currentAuthConfig?.additionalAuthenticationProviders?.find(authSetting => authSetting.authenticationType == authProvider), + ); authConfig.additionalAuthenticationProviders.push(config); } @@ -537,7 +719,7 @@ export async function askAdditionalAuthQuestions(context, authConfig, defaultAut return authConfig; } -async function askAuthQuestions(authType, context, printLeadText = false) { +export async function askAuthQuestions(authType, context, printLeadText = false, authSettings) { if (authType === 'AMAZON_COGNITO_USER_POOLS') { if (printLeadText) { context.print.info('Cognito UserPool configuration'); @@ -553,7 +735,7 @@ async function askAuthQuestions(authType, context, printLeadText = false) { context.print.info('API key configuration'); } - const apiKeyConfig = await askApiKeyQuestions(); + const apiKeyConfig = await askApiKeyQuestions(authSettings); return apiKeyConfig; } @@ -569,7 +751,7 @@ async function askAuthQuestions(authType, context, printLeadText = false) { context.print.info('OpenID Connect configuration'); } - const openIDConnectConfig = await askOpenIDConnectQuestions(); + const openIDConnectConfig = await askOpenIDConnectQuestions(authSettings); return openIDConnectConfig; } @@ -582,9 +764,8 @@ async function askAuthQuestions(authType, context, printLeadText = false) { async function askUserPoolQuestions(context) { let authResourceName = checkIfAuthExists(context); - if (!authResourceName) { - authResourceName = await context.amplify.invokePluginMethod(context, 'auth', undefined, 'add', [context]); + authResourceName = await context.amplify.invokePluginMethod(context, 'auth', undefined, 'add', [context, true]); } else { context.print.info('Use a Cognito user pool configured as a part of this project.'); } @@ -600,18 +781,25 @@ async function askUserPoolQuestions(context) { }; } -async function askApiKeyQuestions() { +export async function askApiKeyQuestions(authSettings = undefined) { + let defaultValues = { + apiKeyExpirationDays: 7, + description: undefined, + }; + Object.assign(defaultValues, authSettings?.apiKeyConfig); + const apiKeyQuestions = [ { type: 'input', name: 'description', message: 'Enter a description for the API key:', + default: defaultValues.description, }, { type: 'input', name: 'apiKeyExpirationDays', message: 'After how many days from now the API key should expire (1-365):', - default: 7, + default: defaultValues.apiKeyExpirationDays, validate: validateDays, // adding filter to ensure parsing input as int -> https://github.com/SBoudrias/Inquirer.js/issues/866 filter: value => (isNaN(parseInt(value, 10)) ? value : parseInt(value, 10)), @@ -619,6 +807,8 @@ async function askApiKeyQuestions() { ]; const apiKeyConfig = await inquirer.prompt(apiKeyQuestions); + const apiKeyExpirationDaysNum = Number(apiKeyConfig.apiKeyExpirationDays); + apiKeyConfig.apiKeyExpirationDate = Expiration.after(Duration.days(apiKeyExpirationDaysNum)).date; return { authenticationType: 'API_KEY', @@ -626,35 +816,49 @@ async function askApiKeyQuestions() { }; } -async function askOpenIDConnectQuestions() { +async function askOpenIDConnectQuestions(authSettings) { + let defaultValues = { + authTTL: undefined, + clientId: undefined, + iatTTL: undefined, + issuerUrl: undefined, + name: undefined, + }; + Object.assign(defaultValues, authSettings?.openIDConnectConfig); + const openIDConnectQuestions = [ { type: 'input', name: 'name', message: 'Enter a name for the OpenID Connect provider:', + default: defaultValues.name, }, { type: 'input', name: 'issuerUrl', message: 'Enter the OpenID Connect provider domain (Issuer URL):', validate: validateIssuerUrl, + default: defaultValues.issuerUrl, }, { type: 'input', name: 'clientId', message: 'Enter the Client Id from your OpenID Client Connect application (optional):', + default: defaultValues.clientId, }, { type: 'input', name: 'iatTTL', message: 'Enter the number of milliseconds a token is valid after being issued to a user:', validate: validateTTL, + default: defaultValues.iatTTL, }, { type: 'input', name: 'authTTL', message: 'Enter the number of milliseconds a token is valid after being authenticated:', validate: validateTTL, + default: defaultValues.authTTL, }, ]; @@ -677,9 +881,10 @@ function validateDays(input) { } function validateIssuerUrl(input) { - const isValid = /^(((?!http:\/\/(?!localhost))([a-zA-Z0-9.]{1,}):\/\/([a-zA-Z0-9-._~:?#@!$&'()*+,;=/]{1,})\/)|(?!http)(?!https)([a-zA-Z0-9.]{1,}):\/\/)$/.test( - input, - ); + const isValid = + /^(((?!http:\/\/(?!localhost))([a-zA-Z0-9.]{1,}):\/\/([a-zA-Z0-9-._~:?#@!$&'()*+,;=/]{1,})\/)|(?!http)(?!https)([a-zA-Z0-9.]{1,}):\/\/)$/.test( + input, + ); if (!isValid) { return 'The value must be a valid URI with a trailing forward slash. HTTPS must be used instead of HTTP unless you are using localhost.'; @@ -779,8 +984,8 @@ const buildPolicyResource = (resourceName: string, path: string | null) => { { Ref: `${category}${resourceName}GraphQLAPIIdOutput`, }, - ...(path ? [path] : []) - ] + ...(path ? [path] : []), + ], ], }; }; @@ -788,7 +993,8 @@ const buildPolicyResource = (resourceName: string, path: string | null) => { const templateSchemaFilter = authConfig => { const authIncludesCognito = getAuthTypes(authConfig).includes('AMAZON_COGNITO_USER_POOLS'); return (templateOption: ListChoiceOptions): boolean => - authIncludesCognito || templateOption.value !== 'single-object-auth-schema.graphql'; + authIncludesCognito || + templateOption.name !== 'Objects with fine-grained access control (e.g., a project management app with owner-based authorization)'; }; const getAuthTypes = authConfig => { diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.ts index d52094eae50..c1a8dde916a 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.ts @@ -30,6 +30,7 @@ const authConfigToAppSyncAuthTypeMap: Record AppSyn API_KEY: authConfig => ({ mode: 'API_KEY', expirationTime: authConfig.apiKeyConfig.apiKeyExpirationDays, + apiKeyExpirationDate: authConfig.apiKeyConfig?.apiKeyExpirationDate, keyDescription: authConfig.apiKeyConfig.description, }), AWS_IAM: () => ({ @@ -54,6 +55,7 @@ const appSyncAuthTypeToAuthConfigMap: Record { type: 'confirm', name: 'editNow', message: 'Do you want to edit the schema now?', - default: false, + default: true, }; if (!(await inquirer.prompt(prompt)).editNow) return; diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/global-sandbox-mode.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/global-sandbox-mode.ts new file mode 100644 index 00000000000..27fc26f1f30 --- /dev/null +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/global-sandbox-mode.ts @@ -0,0 +1,10 @@ +import { $TSContext } from 'amplify-cli-core'; + +export function defineGlobalSandboxMode(context: $TSContext): string { + const envName = context.amplify.getEnvInfo().envName; + + return `# This allows public create, read, update, and delete access for a limited time to all models via API Key. +# To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql-transformer/auth +type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: \"${envName}\") # FOR TESTING ONLY!\n +`; +} diff --git a/packages/amplify-category-api/src/provider-utils/supported-services.ts b/packages/amplify-category-api/src/provider-utils/supported-services.ts index 9bf9cbba01e..d5fe5482aa2 100644 --- a/packages/amplify-category-api/src/provider-utils/supported-services.ts +++ b/packages/amplify-category-api/src/provider-utils/supported-services.ts @@ -52,6 +52,10 @@ export const supportedServices = { name: 'Objects with fine-grained access control (e.g., a project management app with owner-based authorization)', value: 'single-object-auth-schema.graphql', }, + { + name: 'Blank Schema', + value: 'blank-schema.graphql', + }, ], required: true, }, diff --git a/packages/amplify-category-api/tsconfig.json b/packages/amplify-category-api/tsconfig.json index dd332b004a4..31c85559bbb 100644 --- a/packages/amplify-category-api/tsconfig.json +++ b/packages/amplify-category-api/tsconfig.json @@ -14,7 +14,9 @@ "src/__tests__" ], "references": [ + {"path": "../amplify-cli-core"}, {"path": "../amplify-headless-interface"}, + {"path": "../amplify-prompts"}, {"path": "../graphql-transformer-core"}, {"path": "../amplify-util-headless-input"}, ] diff --git a/packages/amplify-category-auth/src/index.js b/packages/amplify-category-auth/src/index.js index f73937ec453..5fec6981024 100644 --- a/packages/amplify-category-auth/src/index.js +++ b/packages/amplify-category-auth/src/index.js @@ -37,7 +37,7 @@ const { privateKeys } = require('./provider-utils/awscloudformation/constants'); const { checkAuthResourceMigration } = require('./provider-utils/awscloudformation/utils/check-for-auth-migration'); // this function is being kept for temporary compatability. -async function add(context) { +async function add(context, skipNextSteps = false) { const { amplify } = context; const servicesMetadata = getSupportedServices.supportedServices; const existingAuth = amplify.getProjectDetails().amplifyMeta.auth || {}; @@ -57,7 +57,7 @@ async function add(context) { context.print.error('Provider not configured for this category'); return; } - return providerController.addResource(context, result.service); + return providerController.addResource(context, result.service, skipNextSteps); }) .catch(err => { context.print.info(err.stack); diff --git a/packages/amplify-category-auth/src/provider-utils/awscloudformation/auth-inputs-manager/auth-input-state.ts b/packages/amplify-category-auth/src/provider-utils/awscloudformation/auth-inputs-manager/auth-input-state.ts index fcaae0db7ce..5818f5d69fe 100644 --- a/packages/amplify-category-auth/src/provider-utils/awscloudformation/auth-inputs-manager/auth-input-state.ts +++ b/packages/amplify-category-auth/src/provider-utils/awscloudformation/auth-inputs-manager/auth-input-state.ts @@ -92,6 +92,10 @@ export class AuthInputState extends CategoryInputState { triggers: parameters.triggers, dependsOn, }); + } else { + if (parameters.triggers) { + parameters.triggers = JSON.stringify(parameters.triggers); + } } return parameters; } diff --git a/packages/amplify-category-auth/src/provider-utils/awscloudformation/handlers/resource-handlers.ts b/packages/amplify-category-auth/src/provider-utils/awscloudformation/handlers/resource-handlers.ts index f105b53ffc0..e54d49f7040 100644 --- a/packages/amplify-category-auth/src/provider-utils/awscloudformation/handlers/resource-handlers.ts +++ b/packages/amplify-category-auth/src/provider-utils/awscloudformation/handlers/resource-handlers.ts @@ -9,7 +9,7 @@ import { } from '../utils/synthesize-resources'; import { getPostAddAuthMetaUpdater, getPostUpdateAuthMetaUpdater } from '../utils/amplify-meta-updaters'; import { getPostAddAuthMessagePrinter, getPostUpdateAuthMessagePrinter, printSMSSandboxWarning } from '../utils/message-printer'; -import { supportedServices } from '../../supported-services'; +import { getSupportedServices, supportedServices } from '../../supported-services'; import { doesConfigurationIncludeSMS } from '../utils/auth-sms-workflow-helper'; import { AuthInputState } from '../auth-inputs-manager/auth-input-state'; import { category, ENV_SPECIFIC_PARAMS, privateKeys } from '../constants'; @@ -25,66 +25,68 @@ import { $TSContext } from 'amplify-cli-core'; * The consumer returns the resourceName of the generated resource. * @param context The amplify context */ -export const getAddAuthHandler = (context: $TSContext) => async (request: ServiceQuestionHeadlessResult | CognitoConfiguration) => { - const serviceMetadata = supportedServices[request.serviceName]; - const { defaultValuesFilename, provider } = serviceMetadata; - - let projectName = context.amplify.getProjectConfig().projectName.toLowerCase(); - const disallowedChars = /[^A-Za-z0-9]+/g; - projectName = projectName.replace(disallowedChars, ''); - - const requestWithDefaults = await getAddAuthDefaultsApplier(context, defaultValuesFilename, projectName)(request); - - // replace secret keys from cli inputs to be stored in deployment secrets - - let sharedParams = Object.assign({}, requestWithDefaults) as any; - privateKeys.forEach(p => delete sharedParams[p]); - sharedParams = removeDeprecatedProps(sharedParams); - // extracting env-specific params from parameters object - let envSpecificParams: any = {}; - const cliInputs = { ...sharedParams }; - ENV_SPECIFIC_PARAMS.forEach(paramName => { - if (paramName in request) { - envSpecificParams[paramName] = cliInputs[paramName]; - delete cliInputs[paramName]; +export const getAddAuthHandler = + (context: $TSContext, skipNextSteps: boolean = false) => + async (request: ServiceQuestionHeadlessResult | CognitoConfiguration) => { + const serviceMetadata = getSupportedServices()[request.serviceName]; + const { cfnFilename, defaultValuesFilename, provider } = serviceMetadata; + + let projectName = context.amplify.getProjectConfig().projectName.toLowerCase(); + const disallowedChars = /[^A-Za-z0-9]+/g; + projectName = projectName.replace(disallowedChars, ''); + + const requestWithDefaults = await getAddAuthDefaultsApplier(context, defaultValuesFilename, projectName)(request); + + // replace secret keys from cli inputs to be stored in deployment secrets + + let sharedParams = Object.assign({}, requestWithDefaults) as any; + privateKeys.forEach(p => delete sharedParams[p]); + sharedParams = removeDeprecatedProps(sharedParams); + // extracting env-specific params from parameters object + let envSpecificParams: any = {}; + const cliInputs = { ...sharedParams }; + ENV_SPECIFIC_PARAMS.forEach(paramName => { + if (paramName in request) { + envSpecificParams[paramName] = cliInputs[paramName]; + delete cliInputs[paramName]; + } + }); + + const cognitoCLIInputs: CognitoCLIInputs = { + version: '1', + cognitoConfig: cliInputs, + }; + + context.amplify.saveEnvResourceParameters(context, category, cognitoCLIInputs.cognitoConfig.resourceName, envSpecificParams); + + // move this function outside of AddHandler + try { + const cliState = new AuthInputState(cognitoCLIInputs.cognitoConfig.resourceName); + // saving cli-inputs except secrets + await cliState.saveCLIInputPayload(cognitoCLIInputs); + // cdk transformation in this function + // start auth transform here + await generateAuthStackTemplate(context, cognitoCLIInputs.cognitoConfig.resourceName); + // remoe this when api and functions transform are done + await getResourceSynthesizer(context, requestWithDefaults); + + getPostAddAuthMetaUpdater(context, { service: cognitoCLIInputs.cognitoConfig.serviceName, providerName: provider })( + cliInputs.resourceName, + ); + getPostAddAuthMessagePrinter(cognitoCLIInputs.cognitoConfig.resourceName); + + if (doesConfigurationIncludeSMS(request)) { + await printSMSSandboxWarning(); + } + } catch (err) { + printer.info(err.stack); + printer.error('There was an error adding the auth resource'); + context.usageData.emitError(err); + process.exitCode = 1; } - }); - - const cognitoCLIInputs: CognitoCLIInputs = { - version: '1', - cognitoConfig: cliInputs, + return cognitoCLIInputs.cognitoConfig.resourceName; }; - context.amplify.saveEnvResourceParameters(context, category, cognitoCLIInputs.cognitoConfig.resourceName, envSpecificParams); - - // move this function outside of AddHandler - try { - const cliState = new AuthInputState(cognitoCLIInputs.cognitoConfig.resourceName); - // saving cli-inputs except secrets - await cliState.saveCLIInputPayload(cognitoCLIInputs); - // cdk transformation in this function - // start auth transform here - await generateAuthStackTemplate(context, cognitoCLIInputs.cognitoConfig.resourceName); - // remoe this when api and functions transform are done - await getResourceSynthesizer(context, requestWithDefaults); - - getPostAddAuthMetaUpdater(context, { service: cognitoCLIInputs.cognitoConfig.serviceName, providerName: provider })( - cliInputs.resourceName, - ); - getPostAddAuthMessagePrinter(cognitoCLIInputs.cognitoConfig.resourceName); - - if (doesConfigurationIncludeSMS(request)) { - await printSMSSandboxWarning(); - } - } catch (err) { - printer.info(err.stack); - printer.error('There was an error adding the auth resource'); - context.usageData.emitError(err); - process.exitCode = 1; - } - return cognitoCLIInputs.cognitoConfig.resourceName; -}; - export const getUpdateAuthHandler = (context: any) => async (request: ServiceQuestionHeadlessResult | CognitoConfiguration) => { const { defaultValuesFilename } = supportedServices[request.serviceName]; // loading previous cli-inputs diff --git a/packages/amplify-category-auth/src/provider-utils/awscloudformation/index.js b/packages/amplify-category-auth/src/provider-utils/awscloudformation/index.js index 12765a96a7c..48101bfa4f5 100644 --- a/packages/amplify-category-auth/src/provider-utils/awscloudformation/index.js +++ b/packages/amplify-category-auth/src/provider-utils/awscloudformation/index.js @@ -5,9 +5,8 @@ const { getAuthResourceName } = require('../../utils/getAuthResourceName'); const { copyCfnTemplate, saveResourceParameters } = require('./utils/synthesize-resources'); const { ENV_SPECIFIC_PARAMS, AmplifyAdmin, UserPool, IdentityPool, BothPools, privateKeys } = require('./constants'); const { getAddAuthHandler, getUpdateAuthHandler } = require('./handlers/resource-handlers'); -const { supportedServices } = require('../supported-services'); +const { getSupportedServices } = require('../supported-services'); const { importResource, importedAuthEnvInit } = require('./import'); -const { AuthInputState } = require('./auth-inputs-manager/auth-input-state'); function serviceQuestions(context, defaultValuesFilename, stringMapsFilename, serviceWalkthroughFilename, serviceMetadata) { const serviceWalkthroughSrc = `${__dirname}/service-walkthroughs/${serviceWalkthroughFilename}`; @@ -15,17 +14,17 @@ function serviceQuestions(context, defaultValuesFilename, stringMapsFilename, se return serviceWalkthrough(context, defaultValuesFilename, stringMapsFilename, serviceMetadata); } -async function addResource(context, service) { - const serviceMetadata = supportedServices[service]; +async function addResource(context, service, skipNextSteps = false) { + const serviceMetadata = getSupportedServices()[service]; const { defaultValuesFilename, stringMapsFilename, serviceWalkthroughFilename } = serviceMetadata; - // add - return getAddAuthHandler(context)( - await serviceQuestions(context, defaultValuesFilename, stringMapsFilename, serviceWalkthroughFilename, serviceMetadata), - ); + return getAddAuthHandler( + context, + skipNextSteps, + )(await serviceQuestions(context, defaultValuesFilename, stringMapsFilename, serviceWalkthroughFilename, serviceMetadata)); } async function updateResource(context, { service }) { - const serviceMetadata = supportedServices[service]; + const serviceMetadata = getSupportedServices()[service]; const { defaultValuesFilename, stringMapsFilename, serviceWalkthroughFilename } = serviceMetadata; return getUpdateAuthHandler(context)( await serviceQuestions(context, defaultValuesFilename, stringMapsFilename, serviceWalkthroughFilename, serviceMetadata), @@ -33,7 +32,7 @@ async function updateResource(context, { service }) { } async function updateConfigOnEnvInit(context, category, service) { - const srvcMetaData = supportedServices.Cognito; + const srvcMetaData = getSupportedServices().Cognito; const { defaultValuesFilename, stringMapsFilename, serviceWalkthroughFilename, provider } = srvcMetaData; const providerPlugin = context.amplify.getPluginInstance(context, provider); @@ -182,7 +181,7 @@ async function migrate(context) { if (!Object.keys(existingAuth).length > 0) { return; } - const { provider, cfnFilename, defaultValuesFilename } = supportedServices.Cognito; + const { provider, cfnFilename, defaultValuesFilename } = getSupportedServices().Cognito; const defaultValuesSrc = `${__dirname}/assets/${defaultValuesFilename}`; const { roles } = require(defaultValuesSrc); @@ -432,7 +431,7 @@ async function openIdentityPoolConsole(context, region, identityPoolId) { } function getPermissionPolicies(context, service, resourceName, crudOptions) { - const { serviceWalkthroughFilename } = supportedServices[service]; + const { serviceWalkthroughFilename } = getSupportedServices()[service]; const serviceWalkthroughSrc = `${__dirname}/service-walkthroughs/${serviceWalkthroughFilename}`; const { getIAMPolicies } = require(serviceWalkthroughSrc); diff --git a/packages/amplify-category-auth/src/provider-utils/awscloudformation/utils/message-printer.ts b/packages/amplify-category-auth/src/provider-utils/awscloudformation/utils/message-printer.ts index a924af0a535..78a94b8a1a9 100644 --- a/packages/amplify-category-auth/src/provider-utils/awscloudformation/utils/message-printer.ts +++ b/packages/amplify-category-auth/src/provider-utils/awscloudformation/utils/message-printer.ts @@ -6,10 +6,15 @@ import { printer } from 'amplify-prompts'; * A factory function that returns a function that prints the "success message" after adding auth * @param print The amplify print object */ -export const getPostAddAuthMessagePrinter = (print: any) => (resourceName: string) => { - printer.success(`Successfully added auth resource ${resourceName} locally`); - printCommonText(print); -}; +export const getPostAddAuthMessagePrinter = + (print: any) => + (resourceName: string, skipNextSteps: boolean = false) => { + print.success(`Successfully added auth resource ${resourceName} locally`); + + if (!skipNextSteps) { + printCommonText(print); + } + }; /** * A factory function that returns a function that prints the "success message" after updating auth diff --git a/packages/amplify-category-auth/src/provider-utils/supported-services.ts b/packages/amplify-category-auth/src/provider-utils/supported-services.ts index 52c453faf92..5e02048b721 100644 --- a/packages/amplify-category-auth/src/provider-utils/supported-services.ts +++ b/packages/amplify-category-auth/src/provider-utils/supported-services.ts @@ -1,3 +1,5 @@ +import { $TSAny, FeatureFlags } from 'amplify-cli-core'; + export const supportedServices = { Cognito: { inputs: [ @@ -1235,3 +1237,14 @@ export const supportedServices = { provider: 'awscloudformation', }, }; + +export const getSupportedServices = (): $TSAny => { + const keyToRemove = FeatureFlags.getBoolean('auth.forceAliasAttributes') ? 'usernameAttributes' : 'aliasAttributes'; + const inputs = supportedServices.Cognito.inputs.filter(input => input.key !== keyToRemove); + return { + Cognito: { + ...supportedServices.Cognito, + inputs, + }, + }; +}; diff --git a/packages/amplify-category-function/src/index.ts b/packages/amplify-category-function/src/index.ts index 0d6de3a6976..63a6841431e 100644 --- a/packages/amplify-category-function/src/index.ts +++ b/packages/amplify-category-function/src/index.ts @@ -28,6 +28,7 @@ export { hashLayerResource } from './provider-utils/awscloudformation/utils/laye export { migrateLegacyLayer } from './provider-utils/awscloudformation/utils/layerMigrationUtils'; export { packageResource } from './provider-utils/awscloudformation/utils/package'; export { updateDependentFunctionsCfn } from './provider-utils/awscloudformation/utils/updateDependentFunctionCfn'; +export { loadFunctionParameters } from './provider-utils/awscloudformation/utils/loadFunctionParameters'; export async function add(context, providerName, service, parameters) { const options = { diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/updateTopLevelComment.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/updateTopLevelComment.ts index 5a1dd26a57c..3c12319fd44 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/updateTopLevelComment.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/updateTopLevelComment.ts @@ -1,4 +1,5 @@ import fs from 'fs-extra'; +import { EOL } from 'os'; import path from 'path'; import { categoryName, topLevelCommentPrefix, topLevelCommentSuffix } from '../../../constants'; import _ from 'lodash'; @@ -16,7 +17,7 @@ export const tryUpdateTopLevelComment = (resourceDirPath: string, envVars: strin updateTopLevelComment(sourceFilePath, newComment); }; -const createTopLevelComment = (envVars: string[]) => `${topLevelCommentPrefix}${envVars.sort().join('\n\t')}${topLevelCommentSuffix}`; +const createTopLevelComment = (envVars: string[]) => `${topLevelCommentPrefix}${envVars.sort().join(`${EOL}\t`)}${topLevelCommentSuffix}`; const updateTopLevelComment = (filePath, newComment) => { const commentRegex = new RegExp(`${_.escapeRegExp(topLevelCommentPrefix)}[a-zA-Z0-9\\-\\s._=]+${_.escapeRegExp(topLevelCommentSuffix)}`); @@ -52,9 +53,9 @@ export const tryPrependSecretsUsageExample = async (functionName: string, secret await fs.writeFile(sourceFilePath, fileContent); }; -const secretsUsageHeader = '/*\nUse the following code to retrieve configured secrets from SSM:'; +const secretsUsageHeader = `/*${EOL}Use the following code to retrieve configured secrets from SSM:`; -const secretsUsageFooter = "Parameters will be of the form { Name: 'secretName', Value: 'secretValue', ... }[]\n*/\n"; +const secretsUsageFooter = `Parameters will be of the form { Name: 'secretName', Value: 'secretValue', ... }[]${EOL}*/${EOL}`; const secretsUsageTemplate = (secretNames: string[]) => `${secretsUsageHeader} diff --git a/packages/amplify-category-geo/amplify-plugin.json b/packages/amplify-category-geo/amplify-plugin.json index c0e36ecff65..e34ea906e1a 100644 --- a/packages/amplify-category-geo/amplify-plugin.json +++ b/packages/amplify-category-geo/amplify-plugin.json @@ -1,16 +1,9 @@ { - "name": "geo", - "type": "category", - "commands": [ - "add", - "push", - "remove", - "update", - "help", - "console" - ], - "commandAliases":{ - "configure": "update" - }, - "eventHandlers": [] -} \ No newline at end of file + "name": "geo", + "type": "category", + "commands": ["add", "push", "remove", "update", "help", "console"], + "commandAliases": { + "configure": "update" + }, + "eventHandlers": [] +} diff --git a/packages/amplify-category-geo/resources/custom-map-resource-handler.js b/packages/amplify-category-geo/resources/custom-map-resource-handler.js index db8903b3f39..f5a005fd2a7 100644 --- a/packages/amplify-category-geo/resources/custom-map-resource-handler.js +++ b/packages/amplify-category-geo/resources/custom-map-resource-handler.js @@ -1,61 +1,61 @@ const response = require('cfn-response'); const aws = require('aws-sdk'); exports.handler = async (event, context) => { - try { - console.log('REQUEST RECEIVED:' + JSON.stringify(event)); - if (event.RequestType == 'Create') { - let params = { - MapName: event.ResourceProperties.mapName, - Configuration: { - Style: event.ResourceProperties.mapStyle - }, - PricingPlan: event.ResourceProperties.pricingPlan - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.createMap(params).promise(); - console.log("create resource response data" + JSON.stringify(res)); - if (res.MapName && res.MapArn) { + try { + console.log('REQUEST RECEIVED:' + JSON.stringify(event)); + if (event.RequestType == 'Create') { + let params = { + MapName: event.ResourceProperties.mapName, + Configuration: { + Style: event.ResourceProperties.mapStyle, + }, + PricingPlan: event.ResourceProperties.pricingPlan, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.createMap(params).promise(); + console.log('create resource response data' + JSON.stringify(res)); + if (res.MapName && res.MapArn) { event.PhysicalResourceId = res.MapName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } - } - if (event.RequestType == 'Update') { - let params = { - MapName: event.ResourceProperties.mapName, - PricingPlan: event.ResourceProperties.pricingPlan - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.updateMap(params).promise(); - console.log("update resource response data" + JSON.stringify(res)); - if (res.MapName && res.MapArn) { + if (event.RequestType == 'Update') { + let params = { + MapName: event.ResourceProperties.mapName, + PricingPlan: event.ResourceProperties.pricingPlan, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.updateMap(params).promise(); + console.log('update resource response data' + JSON.stringify(res)); + if (res.MapName && res.MapArn) { event.PhysicalResourceId = res.MapName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } + if (event.RequestType == 'Delete') { + let params = { + MapName: event.ResourceProperties.mapName, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.deleteMap(params).promise(); + event.PhysicalResourceId = event.ResourceProperties.mapName; + console.log('delete resource response data' + JSON.stringify(res)); + await send(event, context, response.SUCCESS, res); + } + } catch (err) { + console.log(err.stack); + const res = { Error: err }; + await send(event, context, response.FAILED, res); + throw err; } - if (event.RequestType == 'Delete') { - let params = { - MapName: event.ResourceProperties.mapName - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.deleteMap(params).promise(); - event.PhysicalResourceId = event.ResourceProperties.mapName; - console.log("delete resource response data" + JSON.stringify(res)); - await send(event, context, response.SUCCESS, res); - } - } catch(err) { - console.log(err.stack); - const res = {Error: err}; - await send(event, context, response.FAILED, res); - throw err; - } }; function send(event, context, status, data) { - return new Promise(() => { response.send(event, context, status, data) }); + return new Promise(() => { + response.send(event, context, status, data); + }); } diff --git a/packages/amplify-category-geo/resources/custom-place-index-resource-handler.js b/packages/amplify-category-geo/resources/custom-place-index-resource-handler.js index a180d0a04a1..0299a2d8f4e 100644 --- a/packages/amplify-category-geo/resources/custom-place-index-resource-handler.js +++ b/packages/amplify-category-geo/resources/custom-place-index-resource-handler.js @@ -1,65 +1,65 @@ const response = require('cfn-response'); const aws = require('aws-sdk'); exports.handler = async (event, context) => { - try { - console.log('REQUEST RECEIVED:' + JSON.stringify(event)); - if (event.RequestType == 'Create') { - const params = { - IndexName: event.ResourceProperties.indexName, - DataSource: event.ResourceProperties.dataSource, - PricingPlan: event.ResourceProperties.pricingPlan, - DataSourceConfiguration: { - IntendedUse: event.ResourceProperties.dataSourceIntendedUse - } - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.createPlaceIndex(params).promise(); - console.log("create resource response data" + JSON.stringify(res)); - if (res.IndexName && res.IndexArn) { + try { + console.log('REQUEST RECEIVED:' + JSON.stringify(event)); + if (event.RequestType == 'Create') { + const params = { + IndexName: event.ResourceProperties.indexName, + DataSource: event.ResourceProperties.dataSource, + PricingPlan: event.ResourceProperties.pricingPlan, + DataSourceConfiguration: { + IntendedUse: event.ResourceProperties.dataSourceIntendedUse, + }, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.createPlaceIndex(params).promise(); + console.log('create resource response data' + JSON.stringify(res)); + if (res.IndexName && res.IndexArn) { event.PhysicalResourceId = res.IndexName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); - } - } - if (event.RequestType == 'Update') { - const params = { - IndexName: event.ResourceProperties.indexName, - PricingPlan: event.ResourceProperties.pricingPlan, - DataSourceConfiguration: { - IntendedUse: event.ResourceProperties.dataSourceIntendedUse } - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.updatePlaceIndex(params).promise(); - console.log("update resource response data" + JSON.stringify(res)); - if (res.IndexName && res.IndexArn) { + } + if (event.RequestType == 'Update') { + const params = { + IndexName: event.ResourceProperties.indexName, + PricingPlan: event.ResourceProperties.pricingPlan, + DataSourceConfiguration: { + IntendedUse: event.ResourceProperties.dataSourceIntendedUse, + }, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.updatePlaceIndex(params).promise(); + console.log('update resource response data' + JSON.stringify(res)); + if (res.IndexName && res.IndexArn) { event.PhysicalResourceId = res.IndexName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } + if (event.RequestType == 'Delete') { + const params = { + IndexName: event.ResourceProperties.indexName, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.deletePlaceIndex(params).promise(); + event.PhysicalResourceId = event.ResourceProperties.indexName; + console.log('delete resource response data' + JSON.stringify(res)); + await send(event, context, response.SUCCESS, res); + } + } catch (err) { + console.log(err.stack); + const res = { Error: err }; + await send(event, context, response.FAILED, res); + throw err; } - if (event.RequestType == 'Delete') { - const params = { - IndexName: event.ResourceProperties.indexName - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.deletePlaceIndex(params).promise(); - event.PhysicalResourceId = event.ResourceProperties.indexName; - console.log("delete resource response data" + JSON.stringify(res)); - await send(event, context, response.SUCCESS, res); - } - } catch(err) { - console.log(err.stack); - const res = {Error: err}; - await send(event, context, response.FAILED, res); - throw err; - } }; function send(event, context, status, data) { - return new Promise(() => { response.send(event, context, status, data) }); + return new Promise(() => { + response.send(event, context, status, data); + }); } diff --git a/packages/amplify-category-geo/src/__tests__/commands/geo/add.test.ts b/packages/amplify-category-geo/src/__tests__/commands/geo/add.test.ts index e04a5ded891..127efe1b99b 100644 --- a/packages/amplify-category-geo/src/__tests__/commands/geo/add.test.ts +++ b/packages/amplify-category-geo/src/__tests__/commands/geo/add.test.ts @@ -3,83 +3,82 @@ import { addResource } from '../../../provider-controllers/index'; import { ServiceName } from '../../../service-utils/constants'; import { run } from '../../../commands/geo/add'; -const mockAddResource = addResource as jest.MockedFunction< typeof addResource >; +const mockAddResource = addResource as jest.MockedFunction; mockAddResource.mockImplementation(async (_, service: string) => service); jest.mock('amplify-cli-core'); jest.mock('../../../provider-controllers/index'); - describe('add command tests', () => { - const provider = 'awscloudformation'; - let mockContext: $TSContext; - // construct mock amplify meta - const mockAmplifyMeta: $TSObject = { - providers: {} + const provider = 'awscloudformation'; + let mockContext: $TSContext; + // construct mock amplify meta + const mockAmplifyMeta: $TSObject = { + providers: {}, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockContext = { + print: { + info: jest.fn(), + warning: jest.fn(), + }, + amplify: {}, + } as unknown as $TSContext; + mockAmplifyMeta.providers[provider] = { + Region: 'us-west-2', }; - - beforeEach(() => { - jest.clearAllMocks(); - mockContext = ({ - print: { - info: jest.fn(), - warning: jest.fn() - }, - amplify: {} - } as unknown) as $TSContext; - mockAmplifyMeta.providers[provider] = { - Region: 'us-west-2' - }; - stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); + stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); + }); + + it('add resource workflow is invoked for map service', async () => { + const service = ServiceName.Map; + mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation(async () => { + return { service: service, providerName: provider }; }); - it('add resource workflow is invoked for map service', async() => { - const service = ServiceName.Map; - mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation( async () => { - return { service: service, providerName: provider }; - }); + await run(mockContext); - await run(mockContext); + expect(mockAddResource).toHaveBeenCalledWith(mockContext, service); + }); - expect(mockAddResource).toHaveBeenCalledWith(mockContext, service); + it('add resource workflow is invoked for place index service', async () => { + const service = ServiceName.PlaceIndex; + mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation(async () => { + return { service: service, providerName: provider }; }); - it('add resource workflow is invoked for place index service', async() => { - const service = ServiceName.PlaceIndex; - mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation( async () => { - return { service: service, providerName: provider }; - }); + await run(mockContext); - await run(mockContext); + expect(mockAddResource).toHaveBeenCalledWith(mockContext, service); + }); - expect(mockAddResource).toHaveBeenCalledWith(mockContext, service); + it('add resource workflow is invoked for Map service in unsupported region', async () => { + mockAmplifyMeta.providers[provider] = { + Region: 'eu-west-2', + }; + const service = ServiceName.Map; + mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation(async () => { + return { service: service, providerName: provider }; }); + stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); - it('add resource workflow is invoked for Map service in unsupported region', async() => { - mockAmplifyMeta.providers[provider] = { - Region: 'eu-west-2' - }; - const service = ServiceName.Map; - mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation( async () => { - return { service: service, providerName: provider }; - }); - stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); + await run(mockContext); + expect(mockAddResource).toHaveBeenCalledWith(mockContext, service); + }); - await run(mockContext); - expect(mockAddResource).toHaveBeenCalledWith(mockContext, service); + it('add resource workflow is invoked for Place Index service in unsupported region', async () => { + mockAmplifyMeta.providers[provider] = { + Region: 'eu-west-2', + }; + const service = ServiceName.PlaceIndex; + mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation(async () => { + return { service: service, providerName: provider }; }); + stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); - it('add resource workflow is invoked for Place Index service in unsupported region', async() => { - mockAmplifyMeta.providers[provider] = { - Region: 'eu-west-2' - }; - const service = ServiceName.PlaceIndex; - mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation( async () => { - return { service: service, providerName: provider }; - }); - stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); - - await run(mockContext); - expect(mockAddResource).toHaveBeenCalledWith(mockContext, service); - }); + await run(mockContext); + expect(mockAddResource).toHaveBeenCalledWith(mockContext, service); + }); }); diff --git a/packages/amplify-category-geo/src/__tests__/commands/geo/update.test.ts b/packages/amplify-category-geo/src/__tests__/commands/geo/update.test.ts index da406887437..9698f423a8a 100644 --- a/packages/amplify-category-geo/src/__tests__/commands/geo/update.test.ts +++ b/packages/amplify-category-geo/src/__tests__/commands/geo/update.test.ts @@ -3,85 +3,84 @@ import { updateResource } from '../../../provider-controllers/index'; import { ServiceName } from '../../../service-utils/constants'; import { run } from '../../../commands/geo/update'; -const mockUpdateResource = updateResource as jest.MockedFunction< typeof updateResource >; +const mockUpdateResource = updateResource as jest.MockedFunction; mockUpdateResource.mockImplementation(async (_, service: string) => service); jest.mock('amplify-cli-core'); jest.mock('../../../provider-controllers/index'); - describe('update command tests', () => { - const provider = 'awscloudformation'; - let mockContext: $TSContext; - // construct mock amplify meta - const mockAmplifyMeta: $TSObject = { - providers: {} + const provider = 'awscloudformation'; + let mockContext: $TSContext; + // construct mock amplify meta + const mockAmplifyMeta: $TSObject = { + providers: {}, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockContext = { + print: { + info: jest.fn(), + warning: jest.fn(), + }, + amplify: {}, + } as unknown as $TSContext; + mockAmplifyMeta.providers[provider] = { + Region: 'us-west-2', }; - - beforeEach(() => { - jest.clearAllMocks(); - mockContext = ({ - print: { - info: jest.fn(), - warning: jest.fn() - }, - amplify: {} - } as unknown) as $TSContext; - mockAmplifyMeta.providers[provider] = { - Region: 'us-west-2' - }; - stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); - }); - - it('update resource workflow is invoked for map service', async() => { - const service = ServiceName.Map; - mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation( async () => { - return { service: service, providerName: provider }; - }); - - await run(mockContext); + stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); + }); - expect(mockUpdateResource).toHaveBeenCalledWith(mockContext, service); + it('update resource workflow is invoked for map service', async () => { + const service = ServiceName.Map; + mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation(async () => { + return { service: service, providerName: provider }; }); - it('update resource workflow is invoked for place index service', async() => { - const service = ServiceName.PlaceIndex; - mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation( async () => { - return { service: service, providerName: provider }; - }); + await run(mockContext); - await run(mockContext); + expect(mockUpdateResource).toHaveBeenCalledWith(mockContext, service); + }); - expect(mockUpdateResource).toHaveBeenCalledWith(mockContext, service); + it('update resource workflow is invoked for place index service', async () => { + const service = ServiceName.PlaceIndex; + mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation(async () => { + return { service: service, providerName: provider }; }); - it('update resource workflow is invoked for Map service in unsupported region', async() => { - mockAmplifyMeta.providers[provider] = { - Region: 'eu-west-2' - }; - stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); + await run(mockContext); - const service = ServiceName.Map; - mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation( async () => { - return { service: service, providerName: provider }; - }); + expect(mockUpdateResource).toHaveBeenCalledWith(mockContext, service); + }); - await run(mockContext); - expect(mockUpdateResource).toHaveBeenCalledWith(mockContext, service); + it('update resource workflow is invoked for Map service in unsupported region', async () => { + mockAmplifyMeta.providers[provider] = { + Region: 'eu-west-2', + }; + stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); + + const service = ServiceName.Map; + mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation(async () => { + return { service: service, providerName: provider }; }); - it('update resource workflow is invoked for Place Index service in unsupported region', async() => { - mockAmplifyMeta.providers[provider] = { - Region: 'eu-west-2' - }; - stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); + await run(mockContext); + expect(mockUpdateResource).toHaveBeenCalledWith(mockContext, service); + }); - const service = ServiceName.PlaceIndex; - mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation( async () => { - return { service: service, providerName: provider }; - }); + it('update resource workflow is invoked for Place Index service in unsupported region', async () => { + mockAmplifyMeta.providers[provider] = { + Region: 'eu-west-2', + }; + stateManager.getMeta = jest.fn().mockReturnValue(mockAmplifyMeta); - await run(mockContext); - expect(mockUpdateResource).toHaveBeenCalledWith(mockContext, service); + const service = ServiceName.PlaceIndex; + mockContext.amplify.serviceSelectionPrompt = jest.fn().mockImplementation(async () => { + return { service: service, providerName: provider }; }); + + await run(mockContext); + expect(mockUpdateResource).toHaveBeenCalledWith(mockContext, service); + }); }); diff --git a/packages/amplify-category-geo/src/__tests__/service-stacks/__snapshots__/mapStack.test.ts.snap b/packages/amplify-category-geo/src/__tests__/service-stacks/__snapshots__/mapStack.test.ts.snap index d099e720af6..749c0375744 100644 --- a/packages/amplify-category-geo/src/__tests__/service-stacks/__snapshots__/mapStack.test.ts.snap +++ b/packages/amplify-category-geo/src/__tests__/service-stacks/__snapshots__/mapStack.test.ts.snap @@ -121,63 +121,63 @@ Object { "ZipFile": "const response = require('cfn-response'); const aws = require('aws-sdk'); exports.handler = async (event, context) => { - try { - console.log('REQUEST RECEIVED:' + JSON.stringify(event)); - if (event.RequestType == 'Create') { - let params = { - MapName: event.ResourceProperties.mapName, - Configuration: { - Style: event.ResourceProperties.mapStyle - }, - PricingPlan: event.ResourceProperties.pricingPlan - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.createMap(params).promise(); - console.log(\\"create resource response data\\" + JSON.stringify(res)); - if (res.MapName && res.MapArn) { + try { + console.log('REQUEST RECEIVED:' + JSON.stringify(event)); + if (event.RequestType == 'Create') { + let params = { + MapName: event.ResourceProperties.mapName, + Configuration: { + Style: event.ResourceProperties.mapStyle, + }, + PricingPlan: event.ResourceProperties.pricingPlan, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.createMap(params).promise(); + console.log('create resource response data' + JSON.stringify(res)); + if (res.MapName && res.MapArn) { event.PhysicalResourceId = res.MapName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } - } - if (event.RequestType == 'Update') { - let params = { - MapName: event.ResourceProperties.mapName, - PricingPlan: event.ResourceProperties.pricingPlan - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.updateMap(params).promise(); - console.log(\\"update resource response data\\" + JSON.stringify(res)); - if (res.MapName && res.MapArn) { + if (event.RequestType == 'Update') { + let params = { + MapName: event.ResourceProperties.mapName, + PricingPlan: event.ResourceProperties.pricingPlan, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.updateMap(params).promise(); + console.log('update resource response data' + JSON.stringify(res)); + if (res.MapName && res.MapArn) { event.PhysicalResourceId = res.MapName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } + if (event.RequestType == 'Delete') { + let params = { + MapName: event.ResourceProperties.mapName, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.deleteMap(params).promise(); + event.PhysicalResourceId = event.ResourceProperties.mapName; + console.log('delete resource response data' + JSON.stringify(res)); + await send(event, context, response.SUCCESS, res); + } + } catch (err) { + console.log(err.stack); + const res = { Error: err }; + await send(event, context, response.FAILED, res); + throw err; } - if (event.RequestType == 'Delete') { - let params = { - MapName: event.ResourceProperties.mapName - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.deleteMap(params).promise(); - event.PhysicalResourceId = event.ResourceProperties.mapName; - console.log(\\"delete resource response data\\" + JSON.stringify(res)); - await send(event, context, response.SUCCESS, res); - } - } catch(err) { - console.log(err.stack); - const res = {Error: err}; - await send(event, context, response.FAILED, res); - throw err; - } }; function send(event, context, status, data) { - return new Promise(() => { response.send(event, context, status, data) }); + return new Promise(() => { + response.send(event, context, status, data); + }); } ", }, @@ -463,63 +463,63 @@ Object { "ZipFile": "const response = require('cfn-response'); const aws = require('aws-sdk'); exports.handler = async (event, context) => { - try { - console.log('REQUEST RECEIVED:' + JSON.stringify(event)); - if (event.RequestType == 'Create') { - let params = { - MapName: event.ResourceProperties.mapName, - Configuration: { - Style: event.ResourceProperties.mapStyle - }, - PricingPlan: event.ResourceProperties.pricingPlan - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.createMap(params).promise(); - console.log(\\"create resource response data\\" + JSON.stringify(res)); - if (res.MapName && res.MapArn) { + try { + console.log('REQUEST RECEIVED:' + JSON.stringify(event)); + if (event.RequestType == 'Create') { + let params = { + MapName: event.ResourceProperties.mapName, + Configuration: { + Style: event.ResourceProperties.mapStyle, + }, + PricingPlan: event.ResourceProperties.pricingPlan, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.createMap(params).promise(); + console.log('create resource response data' + JSON.stringify(res)); + if (res.MapName && res.MapArn) { event.PhysicalResourceId = res.MapName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } - } - if (event.RequestType == 'Update') { - let params = { - MapName: event.ResourceProperties.mapName, - PricingPlan: event.ResourceProperties.pricingPlan - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.updateMap(params).promise(); - console.log(\\"update resource response data\\" + JSON.stringify(res)); - if (res.MapName && res.MapArn) { + if (event.RequestType == 'Update') { + let params = { + MapName: event.ResourceProperties.mapName, + PricingPlan: event.ResourceProperties.pricingPlan, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.updateMap(params).promise(); + console.log('update resource response data' + JSON.stringify(res)); + if (res.MapName && res.MapArn) { event.PhysicalResourceId = res.MapName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } + if (event.RequestType == 'Delete') { + let params = { + MapName: event.ResourceProperties.mapName, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.deleteMap(params).promise(); + event.PhysicalResourceId = event.ResourceProperties.mapName; + console.log('delete resource response data' + JSON.stringify(res)); + await send(event, context, response.SUCCESS, res); + } + } catch (err) { + console.log(err.stack); + const res = { Error: err }; + await send(event, context, response.FAILED, res); + throw err; } - if (event.RequestType == 'Delete') { - let params = { - MapName: event.ResourceProperties.mapName - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.deleteMap(params).promise(); - event.PhysicalResourceId = event.ResourceProperties.mapName; - console.log(\\"delete resource response data\\" + JSON.stringify(res)); - await send(event, context, response.SUCCESS, res); - } - } catch(err) { - console.log(err.stack); - const res = {Error: err}; - await send(event, context, response.FAILED, res); - throw err; - } }; function send(event, context, status, data) { - return new Promise(() => { response.send(event, context, status, data) }); + return new Promise(() => { + response.send(event, context, status, data); + }); } ", }, diff --git a/packages/amplify-category-geo/src/__tests__/service-stacks/__snapshots__/placeIndexStack.test.ts.snap b/packages/amplify-category-geo/src/__tests__/service-stacks/__snapshots__/placeIndexStack.test.ts.snap index 3dbcab3c64c..81899ffacb6 100644 --- a/packages/amplify-category-geo/src/__tests__/service-stacks/__snapshots__/placeIndexStack.test.ts.snap +++ b/packages/amplify-category-geo/src/__tests__/service-stacks/__snapshots__/placeIndexStack.test.ts.snap @@ -122,67 +122,67 @@ Object { "ZipFile": "const response = require('cfn-response'); const aws = require('aws-sdk'); exports.handler = async (event, context) => { - try { - console.log('REQUEST RECEIVED:' + JSON.stringify(event)); - if (event.RequestType == 'Create') { - const params = { - IndexName: event.ResourceProperties.indexName, - DataSource: event.ResourceProperties.dataSource, - PricingPlan: event.ResourceProperties.pricingPlan, - DataSourceConfiguration: { - IntendedUse: event.ResourceProperties.dataSourceIntendedUse - } - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.createPlaceIndex(params).promise(); - console.log(\\"create resource response data\\" + JSON.stringify(res)); - if (res.IndexName && res.IndexArn) { + try { + console.log('REQUEST RECEIVED:' + JSON.stringify(event)); + if (event.RequestType == 'Create') { + const params = { + IndexName: event.ResourceProperties.indexName, + DataSource: event.ResourceProperties.dataSource, + PricingPlan: event.ResourceProperties.pricingPlan, + DataSourceConfiguration: { + IntendedUse: event.ResourceProperties.dataSourceIntendedUse, + }, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.createPlaceIndex(params).promise(); + console.log('create resource response data' + JSON.stringify(res)); + if (res.IndexName && res.IndexArn) { event.PhysicalResourceId = res.IndexName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); - } - } - if (event.RequestType == 'Update') { - const params = { - IndexName: event.ResourceProperties.indexName, - PricingPlan: event.ResourceProperties.pricingPlan, - DataSourceConfiguration: { - IntendedUse: event.ResourceProperties.dataSourceIntendedUse } - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.updatePlaceIndex(params).promise(); - console.log(\\"update resource response data\\" + JSON.stringify(res)); - if (res.IndexName && res.IndexArn) { + } + if (event.RequestType == 'Update') { + const params = { + IndexName: event.ResourceProperties.indexName, + PricingPlan: event.ResourceProperties.pricingPlan, + DataSourceConfiguration: { + IntendedUse: event.ResourceProperties.dataSourceIntendedUse, + }, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.updatePlaceIndex(params).promise(); + console.log('update resource response data' + JSON.stringify(res)); + if (res.IndexName && res.IndexArn) { event.PhysicalResourceId = res.IndexName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } + if (event.RequestType == 'Delete') { + const params = { + IndexName: event.ResourceProperties.indexName, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.deletePlaceIndex(params).promise(); + event.PhysicalResourceId = event.ResourceProperties.indexName; + console.log('delete resource response data' + JSON.stringify(res)); + await send(event, context, response.SUCCESS, res); + } + } catch (err) { + console.log(err.stack); + const res = { Error: err }; + await send(event, context, response.FAILED, res); + throw err; } - if (event.RequestType == 'Delete') { - const params = { - IndexName: event.ResourceProperties.indexName - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.deletePlaceIndex(params).promise(); - event.PhysicalResourceId = event.ResourceProperties.indexName; - console.log(\\"delete resource response data\\" + JSON.stringify(res)); - await send(event, context, response.SUCCESS, res); - } - } catch(err) { - console.log(err.stack); - const res = {Error: err}; - await send(event, context, response.FAILED, res); - throw err; - } }; function send(event, context, status, data) { - return new Promise(() => { response.send(event, context, status, data) }); + return new Promise(() => { + response.send(event, context, status, data); + }); } ", }, @@ -467,67 +467,67 @@ Object { "ZipFile": "const response = require('cfn-response'); const aws = require('aws-sdk'); exports.handler = async (event, context) => { - try { - console.log('REQUEST RECEIVED:' + JSON.stringify(event)); - if (event.RequestType == 'Create') { - const params = { - IndexName: event.ResourceProperties.indexName, - DataSource: event.ResourceProperties.dataSource, - PricingPlan: event.ResourceProperties.pricingPlan, - DataSourceConfiguration: { - IntendedUse: event.ResourceProperties.dataSourceIntendedUse - } - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.createPlaceIndex(params).promise(); - console.log(\\"create resource response data\\" + JSON.stringify(res)); - if (res.IndexName && res.IndexArn) { + try { + console.log('REQUEST RECEIVED:' + JSON.stringify(event)); + if (event.RequestType == 'Create') { + const params = { + IndexName: event.ResourceProperties.indexName, + DataSource: event.ResourceProperties.dataSource, + PricingPlan: event.ResourceProperties.pricingPlan, + DataSourceConfiguration: { + IntendedUse: event.ResourceProperties.dataSourceIntendedUse, + }, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.createPlaceIndex(params).promise(); + console.log('create resource response data' + JSON.stringify(res)); + if (res.IndexName && res.IndexArn) { event.PhysicalResourceId = res.IndexName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); - } - } - if (event.RequestType == 'Update') { - const params = { - IndexName: event.ResourceProperties.indexName, - PricingPlan: event.ResourceProperties.pricingPlan, - DataSourceConfiguration: { - IntendedUse: event.ResourceProperties.dataSourceIntendedUse } - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.updatePlaceIndex(params).promise(); - console.log(\\"update resource response data\\" + JSON.stringify(res)); - if (res.IndexName && res.IndexArn) { + } + if (event.RequestType == 'Update') { + const params = { + IndexName: event.ResourceProperties.indexName, + PricingPlan: event.ResourceProperties.pricingPlan, + DataSourceConfiguration: { + IntendedUse: event.ResourceProperties.dataSourceIntendedUse, + }, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.updatePlaceIndex(params).promise(); + console.log('update resource response data' + JSON.stringify(res)); + if (res.IndexName && res.IndexArn) { event.PhysicalResourceId = res.IndexName; await send(event, context, response.SUCCESS, res); - } - else { + } else { await send(event, context, response.FAILED, res); + } } + if (event.RequestType == 'Delete') { + const params = { + IndexName: event.ResourceProperties.indexName, + }; + const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); + const res = await locationClient.deletePlaceIndex(params).promise(); + event.PhysicalResourceId = event.ResourceProperties.indexName; + console.log('delete resource response data' + JSON.stringify(res)); + await send(event, context, response.SUCCESS, res); + } + } catch (err) { + console.log(err.stack); + const res = { Error: err }; + await send(event, context, response.FAILED, res); + throw err; } - if (event.RequestType == 'Delete') { - const params = { - IndexName: event.ResourceProperties.indexName - }; - const locationClient = new aws.Location({ apiVersion: '2020-11-19', region: event.ResourceProperties.region }); - const res = await locationClient.deletePlaceIndex(params).promise(); - event.PhysicalResourceId = event.ResourceProperties.indexName; - console.log(\\"delete resource response data\\" + JSON.stringify(res)); - await send(event, context, response.SUCCESS, res); - } - } catch(err) { - console.log(err.stack); - const res = {Error: err}; - await send(event, context, response.FAILED, res); - throw err; - } }; function send(event, context, status, data) { - return new Promise(() => { response.send(event, context, status, data) }); + return new Promise(() => { + response.send(event, context, status, data); + }); } ", }, diff --git a/packages/amplify-category-geo/src/__tests__/service-stacks/mapStack.test.ts b/packages/amplify-category-geo/src/__tests__/service-stacks/mapStack.test.ts index 4c8f9f44d33..4e71aac97f9 100644 --- a/packages/amplify-category-geo/src/__tests__/service-stacks/mapStack.test.ts +++ b/packages/amplify-category-geo/src/__tests__/service-stacks/mapStack.test.ts @@ -3,33 +3,33 @@ import { MapStack } from '../../service-stacks/mapStack'; import { App } from '@aws-cdk/core'; describe('cdk stack creation for map service', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); + beforeEach(() => { + jest.clearAllMocks(); + }); - it('creates Map policy for Authorized users only access type', async() => { - const stackProps = { - accessType: AccessType.AuthorizedUsers, - RegionMapping: { - 'eu-west-2': { - locationServiceRegion: 'eu-central-1' - } - } - } - const mapStack = new MapStack(new App(), 'MapStack', stackProps); - expect(mapStack.toCloudFormation()).toMatchSnapshot(); - }); + it('creates Map policy for Authorized users only access type', async () => { + const stackProps = { + accessType: AccessType.AuthorizedUsers, + RegionMapping: { + 'eu-west-2': { + locationServiceRegion: 'eu-central-1', + }, + }, + }; + const mapStack = new MapStack(new App(), 'MapStack', stackProps); + expect(mapStack.toCloudFormation()).toMatchSnapshot(); + }); - it('creates Map policy for Authorized and Guest users access type', async() => { - const stackProps = { - accessType: AccessType.AuthorizedAndGuestUsers, - RegionMapping: { - 'eu-west-2': { - locationServiceRegion: 'eu-central-1' - } - } - } - const mapStack = new MapStack(new App(), 'MapStack', stackProps); - expect(mapStack.toCloudFormation()).toMatchSnapshot(); - }); -}); \ No newline at end of file + it('creates Map policy for Authorized and Guest users access type', async () => { + const stackProps = { + accessType: AccessType.AuthorizedAndGuestUsers, + RegionMapping: { + 'eu-west-2': { + locationServiceRegion: 'eu-central-1', + }, + }, + }; + const mapStack = new MapStack(new App(), 'MapStack', stackProps); + expect(mapStack.toCloudFormation()).toMatchSnapshot(); + }); +}); diff --git a/packages/amplify-category-geo/src/__tests__/service-stacks/placeIndexStack.test.ts b/packages/amplify-category-geo/src/__tests__/service-stacks/placeIndexStack.test.ts index f4815078f77..3706386a6aa 100644 --- a/packages/amplify-category-geo/src/__tests__/service-stacks/placeIndexStack.test.ts +++ b/packages/amplify-category-geo/src/__tests__/service-stacks/placeIndexStack.test.ts @@ -3,33 +3,33 @@ import { PlaceIndexStack } from '../../service-stacks/placeIndexStack'; import { App } from '@aws-cdk/core'; describe('cdk stack creation for place index service', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); + beforeEach(() => { + jest.clearAllMocks(); + }); - it('creates place index policy for Authorized users only access type', async() => { - const stackProps = { - accessType: AccessType.AuthorizedUsers, - RegionMapping: { - 'eu-west-2': { - locationServiceRegion: 'eu-central-1' - } - } - } - const mapStack = new PlaceIndexStack(new App(), 'PlaceIndexStack', stackProps); - expect(mapStack.toCloudFormation()).toMatchSnapshot(); - }); + it('creates place index policy for Authorized users only access type', async () => { + const stackProps = { + accessType: AccessType.AuthorizedUsers, + RegionMapping: { + 'eu-west-2': { + locationServiceRegion: 'eu-central-1', + }, + }, + }; + const mapStack = new PlaceIndexStack(new App(), 'PlaceIndexStack', stackProps); + expect(mapStack.toCloudFormation()).toMatchSnapshot(); + }); - it('creates place index policy for Authorized and Guest users access type', async() => { - const stackProps = { - accessType: AccessType.AuthorizedAndGuestUsers, - RegionMapping: { - 'eu-west-2': { - locationServiceRegion: 'eu-central-1' - } - } - } - const mapStack = new PlaceIndexStack(new App(), 'PlaceIndexStack', stackProps); - expect(mapStack.toCloudFormation()).toMatchSnapshot(); - }); -}); \ No newline at end of file + it('creates place index policy for Authorized and Guest users access type', async () => { + const stackProps = { + accessType: AccessType.AuthorizedAndGuestUsers, + RegionMapping: { + 'eu-west-2': { + locationServiceRegion: 'eu-central-1', + }, + }, + }; + const mapStack = new PlaceIndexStack(new App(), 'PlaceIndexStack', stackProps); + expect(mapStack.toCloudFormation()).toMatchSnapshot(); + }); +}); diff --git a/packages/amplify-category-geo/src/commands/geo/add.ts b/packages/amplify-category-geo/src/commands/geo/add.ts index fad2b63c425..b52b04bc184 100644 --- a/packages/amplify-category-geo/src/commands/geo/add.ts +++ b/packages/amplify-category-geo/src/commands/geo/add.ts @@ -7,10 +7,15 @@ import { printer } from 'amplify-prompts'; export const name = 'add'; -export const run = async(context: $TSContext) => { +export const run = async (context: $TSContext) => { const { amplify } = context; try { - const result: {service: string, providerName: string} = await amplify.serviceSelectionPrompt(context, category, supportedServices, chooseServiceMessageAdd); + const result: { service: string; providerName: string } = await amplify.serviceSelectionPrompt( + context, + category, + supportedServices, + chooseServiceMessageAdd, + ); if (result.providerName !== provider) { printer.error(`Provider ${result.providerName} not configured for this category`); @@ -18,7 +23,6 @@ export const run = async(context: $TSContext) => { } return await addResource(context, result.service); - } catch (error: $TSAny) { if (error.message) { printer.error(error.message); diff --git a/packages/amplify-category-geo/src/commands/geo/console.ts b/packages/amplify-category-geo/src/commands/geo/console.ts index 1a1f4a4c411..07b57fabe30 100644 --- a/packages/amplify-category-geo/src/commands/geo/console.ts +++ b/packages/amplify-category-geo/src/commands/geo/console.ts @@ -9,7 +9,7 @@ export const name = 'console'; export const run = async (context: $TSContext) => { const { amplify } = context; - const result: {service: string, providerName: string} = await amplify.serviceSelectionPrompt(context, category, supportedServices); + const result: { service: string; providerName: string } = await amplify.serviceSelectionPrompt(context, category, supportedServices); if (result.providerName !== provider) { printer.error(`Provider ${result.providerName} not configured for this category`); diff --git a/packages/amplify-category-geo/src/commands/geo/update.ts b/packages/amplify-category-geo/src/commands/geo/update.ts index c1a4ad3eb72..bd5161453c7 100644 --- a/packages/amplify-category-geo/src/commands/geo/update.ts +++ b/packages/amplify-category-geo/src/commands/geo/update.ts @@ -7,10 +7,15 @@ import { printer } from 'amplify-prompts'; export const name = 'update'; -export const run = async(context: $TSContext) => { +export const run = async (context: $TSContext) => { const { amplify } = context; try { - const result: {service: string, providerName: string} = await amplify.serviceSelectionPrompt(context, category, supportedServices, chooseServiceMessageUpdate); + const result: { service: string; providerName: string } = await amplify.serviceSelectionPrompt( + context, + category, + supportedServices, + chooseServiceMessageUpdate, + ); if (result.providerName !== provider) { printer.error(`Provider ${result.providerName} not configured for this category`); @@ -18,8 +23,7 @@ export const run = async(context: $TSContext) => { } return await updateResource(context, result.service); - - } catch (error:$TSAny) { + } catch (error: $TSAny) { if (error.message) { printer.error(error.message); } diff --git a/packages/amplify-category-geo/src/index.ts b/packages/amplify-category-geo/src/index.ts index 117aad71798..770377a1413 100644 --- a/packages/amplify-category-geo/src/index.ts +++ b/packages/amplify-category-geo/src/index.ts @@ -10,26 +10,26 @@ import { ServiceName } from './service-utils/constants'; import { printer } from 'amplify-prompts'; export const executeAmplifyCommand = async (context: $TSContext) => { - switch(context.input.command) { - case 'add': - await addCommand.run(context); - break; - case 'update': - await updateCommand.run(context); - break; - case 'remove': - await removeCommand.run(context); - break; - case 'console': - await consoleCommand.run(context); - break; - case 'help': - await helpCommand.run(context); - break; - default: - printer.error(`The subcommand ${context.input.command} is not supported for ${category} category`); - break; - } + switch (context.input.command) { + case 'add': + await addCommand.run(context); + break; + case 'update': + await updateCommand.run(context); + break; + case 'remove': + await removeCommand.run(context); + break; + case 'console': + await consoleCommand.run(context); + break; + case 'help': + await helpCommand.run(context); + break; + default: + printer.error(`The subcommand ${context.input.command} is not supported for ${category} category`); + break; + } }; export const handleAmplifyEvent = async (context: $TSContext, args: $TSAny) => { @@ -38,31 +38,26 @@ export const handleAmplifyEvent = async (context: $TSContext, args: $TSAny) => { }; export const getPermissionPolicies = (context: $TSContext, resourceOpsMapping: $TSObject) => { - const amplifyMeta = stateManager.getMeta()?.[category]; - const permissionPolicies: $TSObject[] = []; - const resourceAttributes: $TSObject[] = []; + const amplifyMeta = stateManager.getMeta()?.[category]; + const permissionPolicies: $TSObject[] = []; + const resourceAttributes: $TSObject[] = []; - Object.keys(resourceOpsMapping).forEach(resourceName => { - try { - const service: ServiceName = amplifyMeta[resourceName].service as ServiceName; + Object.keys(resourceOpsMapping).forEach(resourceName => { + try { + const service: ServiceName = amplifyMeta[resourceName].service as ServiceName; - const { policy, attributes } = getServicePermissionPolicies( - context, - service, - resourceName, - resourceOpsMapping[resourceName], - ); - if (Array.isArray(policy)) { - permissionPolicies.push(...policy); - } else { - permissionPolicies.push(policy); - } - resourceAttributes.push({ resourceName, attributes, category }); - } catch (e) { - printer.error(`Could not get policies for ${category}: ${resourceName}`); - throw e; + const { policy, attributes } = getServicePermissionPolicies(context, service, resourceName, resourceOpsMapping[resourceName]); + if (Array.isArray(policy)) { + permissionPolicies.push(...policy); + } else { + permissionPolicies.push(policy); } - }); + resourceAttributes.push({ resourceName, attributes, category }); + } catch (e) { + printer.error(`Could not get policies for ${category}: ${resourceName}`); + throw e; + } + }); - return { permissionPolicies, resourceAttributes }; -} + return { permissionPolicies, resourceAttributes }; +}; diff --git a/packages/amplify-category-geo/src/provider-controllers/index.ts b/packages/amplify-category-geo/src/provider-controllers/index.ts index 553780dca92..965db65ef9a 100644 --- a/packages/amplify-category-geo/src/provider-controllers/index.ts +++ b/packages/amplify-category-geo/src/provider-controllers/index.ts @@ -11,19 +11,11 @@ import { TemplateMappings } from '../service-stacks/baseStack'; /** * Entry point for creating a new Geo resource */ -export const addResource = async ( - context: $TSContext, - service: string -): Promise => { - if(!projectHasAuth()) { - if ( - await prompter.yesOrNo( - 'geo category resources require auth (Amazon Cognito). Do you want to add auth now?', - ) - ){ +export const addResource = async (context: $TSContext, service: string): Promise => { + if (!projectHasAuth()) { + if (await prompter.yesOrNo('geo category resources require auth (Amazon Cognito). Do you want to add auth now?')) { await context.amplify.invokePluginMethod(context, 'auth', undefined, 'add', [context]); - } - else { + } else { printer.info('Please add auth (Amazon Cognito) to your project using "amplify add auth"'); return; } @@ -42,10 +34,7 @@ export const addResource = async ( /** * Entry point for updating existing Geo resource */ -export const updateResource = async ( - context: $TSContext, - service: string -): Promise => { +export const updateResource = async (context: $TSContext, service: string): Promise => { switch (service) { case ServiceName.Map: return updateMapResource(context); @@ -59,10 +48,7 @@ export const updateResource = async ( /** * Entry point for removing existing Geo resource */ -export const removeResource = async ( - context: $TSContext, - service: string -): Promise => { +export const removeResource = async (context: $TSContext, service: string): Promise => { switch (service) { case ServiceName.Map: return removeMapResource(context); @@ -73,9 +59,8 @@ export const removeResource = async ( } }; -export const projectHasAuth = () => !!Object.values( - stateManager.getMeta()?.auth || {} -).find(meta => (meta as $TSObject)?.service === 'Cognito'); +export const projectHasAuth = () => + !!Object.values(stateManager.getMeta()?.auth || {}).find(meta => (meta as $TSObject)?.service === 'Cognito'); export const printNextStepsSuccessMessage = (context: $TSContext) => { printer.blankLine(); @@ -100,10 +85,10 @@ export const openConsole = (service: string) => { let selection: string | undefined; switch (service) { case ServiceName.Map: - selection = "maps"; + selection = 'maps'; break; case ServiceName.PlaceIndex: - selection = "places"; + selection = 'places'; break; default: selection = undefined; @@ -117,15 +102,15 @@ export const openConsole = (service: string) => { const badServiceError = (service: string) => { return new Error(`amplify-category-geo is not configured to provide service type ${service}`); -} +}; export const insufficientInfoForUpdateError = (service: ServiceName) => { new Error(`Insufficient information to update ${getServiceFriendlyName(service)}. Please re-try and provide all inputs.`); -} +}; export const getTemplateMappings = async (context: $TSContext): Promise => { const Mappings: TemplateMappings = { - RegionMapping: {} + RegionMapping: {}, }; const providerPlugins = context.amplify.getProviderPlugins(context); const providerPlugin = await import(providerPlugins[provider]); diff --git a/packages/amplify-category-geo/src/service-stacks/baseStack.ts b/packages/amplify-category-geo/src/service-stacks/baseStack.ts index 6ce52a5dfac..53026628191 100644 --- a/packages/amplify-category-geo/src/service-stacks/baseStack.ts +++ b/packages/amplify-category-geo/src/service-stacks/baseStack.ts @@ -3,35 +3,35 @@ import { prepareApp } from '@aws-cdk/core/lib/private/prepare-app'; import { $TSObject } from 'amplify-cli-core'; export type TemplateMappings = { - RegionMapping: $TSObject + RegionMapping: $TSObject; }; export class BaseStack extends cdk.Stack { - protected parameters: Map; - protected regionMapping: cdk.CfnMapping; + protected parameters: Map; + protected regionMapping: cdk.CfnMapping; - constructor(scope: cdk.Construct, id: string, props:TemplateMappings) { - super(scope, id); - this.parameters = new Map(); + constructor(scope: cdk.Construct, id: string, props: TemplateMappings) { + super(scope, id); + this.parameters = new Map(); - this.regionMapping = new cdk.CfnMapping(this, 'RegionMapping', { - mapping: props.RegionMapping - }); - } + this.regionMapping = new cdk.CfnMapping(this, 'RegionMapping', { + mapping: props.RegionMapping, + }); + } - // construct the stack CFN input parameters - constructInputParameters(parameterNames: Array): Map { - let parametersMap: Map = new Map(); - parameterNames.forEach(parameterName => { - const inputParameter = new cdk.CfnParameter(this, parameterName, { type: 'String' }) - parametersMap.set(parameterName, inputParameter); - }); - return parametersMap; - } + // construct the stack CFN input parameters + constructInputParameters(parameterNames: Array): Map { + let parametersMap: Map = new Map(); + parameterNames.forEach(parameterName => { + const inputParameter = new cdk.CfnParameter(this, parameterName, { type: 'String' }); + parametersMap.set(parameterName, inputParameter); + }); + return parametersMap; + } - toCloudFormation() { - prepareApp(this); - const cfn = this._toCloudFormation(); - return cfn; - } + toCloudFormation() { + prepareApp(this); + const cfn = this._toCloudFormation(); + return cfn; + } } diff --git a/packages/amplify-category-geo/src/service-stacks/mapStack.ts b/packages/amplify-category-geo/src/service-stacks/mapStack.ts index 5cc39cb9409..9825d052789 100644 --- a/packages/amplify-category-geo/src/service-stacks/mapStack.ts +++ b/packages/amplify-category-geo/src/service-stacks/mapStack.ts @@ -13,126 +13,118 @@ import * as fs from 'fs-extra'; type MapStackProps = Pick & TemplateMappings; export class MapStack extends BaseStack { - protected readonly accessType: string; - protected readonly mapResource: cdk.CustomResource; - protected readonly mapRegion: string - protected readonly mapName: string - - constructor(scope: cdk.Construct, id: string, private readonly props: MapStackProps) { - super(scope, id, props); - - this.accessType = this.props.accessType; - this.mapRegion = this.regionMapping.findInMap(cdk.Fn.ref('AWS::Region'), 'locationServiceRegion'); - - this.parameters = this.constructInputParameters([ - 'authRoleName', - 'unauthRoleName', - 'mapName', - 'mapStyle', - 'pricingPlan', - 'env', - 'isDefault' - ]); - - this.mapName = Fn.join('-', [ - this.parameters.get('mapName')!.valueAsString, - this.parameters.get('env')!.valueAsString - ]); - this.mapResource = this.constructMapResource(); - this.constructMapPolicyResource(this.mapResource); - this.constructOutputs(); + protected readonly accessType: string; + protected readonly mapResource: cdk.CustomResource; + protected readonly mapRegion: string; + protected readonly mapName: string; + + constructor(scope: cdk.Construct, id: string, private readonly props: MapStackProps) { + super(scope, id, props); + + this.accessType = this.props.accessType; + this.mapRegion = this.regionMapping.findInMap(cdk.Fn.ref('AWS::Region'), 'locationServiceRegion'); + + this.parameters = this.constructInputParameters([ + 'authRoleName', + 'unauthRoleName', + 'mapName', + 'mapStyle', + 'pricingPlan', + 'env', + 'isDefault', + ]); + + this.mapName = Fn.join('-', [this.parameters.get('mapName')!.valueAsString, this.parameters.get('env')!.valueAsString]); + this.mapResource = this.constructMapResource(); + this.constructMapPolicyResource(this.mapResource); + this.constructOutputs(); + } + + private constructOutputs() { + new cdk.CfnOutput(this, 'Name', { + value: this.mapResource.getAtt('MapName').toString(), + }); + new cdk.CfnOutput(this, 'Style', { + value: this.parameters.get('mapStyle')!.valueAsString, + }); + new cdk.CfnOutput(this, 'Region', { + value: this.mapRegion, + }); + new cdk.CfnOutput(this, 'Arn', { + value: this.mapResource.getAtt('MapArn').toString(), + }); + } + + private constructMapResource(): cdk.CustomResource { + const geoCreateMapStatement = new iam.PolicyStatement({ + effect: Effect.ALLOW, + }); + geoCreateMapStatement.addActions('geo:CreateMap'); + geoCreateMapStatement.addAllResources(); + + const mapARN = cdk.Fn.sub('arn:aws:geo:${region}:${account}:map/${mapName}', { + region: this.mapRegion, + account: cdk.Fn.ref('AWS::AccountId'), + mapName: this.mapName, + }); + + const geoUpdateDeleteMapStatement = new iam.PolicyStatement({ + effect: Effect.ALLOW, + }); + geoUpdateDeleteMapStatement.addActions('geo:UpdateMap', 'geo:DeleteMap'); + geoUpdateDeleteMapStatement.addResources(mapARN); + + const mapStyle = this.parameters.get('mapStyle')!.valueAsString; + + const mapPricingPlan = this.parameters.get('pricingPlan')!.valueAsString; + + const customMapLambdaCode = fs.readFileSync(customMapLambdaCodePath, 'utf-8'); + const customMapLambda = new lambda.Function(this, 'CustomMapLambda', { + code: lambda.Code.fromInline(customMapLambdaCode), + handler: 'index.handler', + runtime: Runtime.NODEJS_14_X, + timeout: Duration.seconds(300), + }); + customMapLambda.addToRolePolicy(geoCreateMapStatement); + customMapLambda.addToRolePolicy(geoUpdateDeleteMapStatement); + + const mapCustomResource = new cdk.CustomResource(this, 'CustomMap', { + serviceToken: customMapLambda.functionArn, + resourceType: 'Custom::LambdaCallout', + properties: { + mapName: this.mapName, + mapStyle: mapStyle, + pricingPlan: mapPricingPlan, + region: this.mapRegion, + env: cdk.Fn.ref('env'), + }, + }); + + return mapCustomResource; + } + + // Grant read-only access to the Map for Authorized and/or Guest users + private constructMapPolicyResource(mapResource: cdk.CustomResource): CfnResource { + let policy = new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['geo:GetMapStyleDescriptor', 'geo:GetMapGlyphs', 'geo:GetMapSprites', 'geo:GetMapTile'], + resources: [mapResource.getAtt('MapArn').toString()], + }), + ], + }); + + let cognitoRoles: Array = new Array(); + cognitoRoles.push(this.parameters.get('authRoleName')!.valueAsString); + if (this.accessType == AccessType.AuthorizedAndGuestUsers) { + cognitoRoles.push(this.parameters.get('unauthRoleName')!.valueAsString); } - private constructOutputs() { - new cdk.CfnOutput(this, 'Name', { - value: this.mapResource.getAtt('MapName').toString() - }); - new cdk.CfnOutput(this, 'Style', { - value: this.parameters.get('mapStyle')!.valueAsString - }); - new cdk.CfnOutput(this, 'Region', { - value: this.mapRegion - }); - new cdk.CfnOutput(this, 'Arn', { - value: this.mapResource.getAtt('MapArn').toString() - }); - } - - private constructMapResource(): cdk.CustomResource { - const geoCreateMapStatement = new iam.PolicyStatement({ - effect: Effect.ALLOW - }); - geoCreateMapStatement.addActions('geo:CreateMap'); - geoCreateMapStatement.addAllResources(); - - const mapARN = cdk.Fn.sub('arn:aws:geo:${region}:${account}:map/${mapName}', { - region: this.mapRegion, - account: cdk.Fn.ref('AWS::AccountId'), - mapName: this.mapName - }); - - const geoUpdateDeleteMapStatement = new iam.PolicyStatement({ - effect: Effect.ALLOW - }); - geoUpdateDeleteMapStatement.addActions('geo:UpdateMap', 'geo:DeleteMap'); - geoUpdateDeleteMapStatement.addResources(mapARN); - - const mapStyle = this.parameters.get('mapStyle')!.valueAsString; - - const mapPricingPlan = this.parameters.get('pricingPlan')!.valueAsString; - - const customMapLambdaCode = fs.readFileSync(customMapLambdaCodePath, 'utf-8'); - const customMapLambda = new lambda.Function(this, 'CustomMapLambda', { - code: lambda.Code.fromInline(customMapLambdaCode), - handler: 'index.handler', - runtime: Runtime.NODEJS_14_X, - timeout: Duration.seconds(300) - }); - customMapLambda.addToRolePolicy(geoCreateMapStatement); - customMapLambda.addToRolePolicy(geoUpdateDeleteMapStatement); - - const mapCustomResource = new cdk.CustomResource(this, 'CustomMap', { - serviceToken: customMapLambda.functionArn, - resourceType: 'Custom::LambdaCallout', - properties: { - mapName: this.mapName, - mapStyle: mapStyle, - pricingPlan: mapPricingPlan, - region: this.mapRegion, - env: cdk.Fn.ref('env'), - }, - }); - - return mapCustomResource; - } - - // Grant read-only access to the Map for Authorized and/or Guest users - private constructMapPolicyResource(mapResource: cdk.CustomResource): CfnResource { - let policy = new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "geo:GetMapStyleDescriptor", - "geo:GetMapGlyphs", - "geo:GetMapSprites", - "geo:GetMapTile" - ], - resources: [mapResource.getAtt('MapArn').toString()], - }) - ], - }); - - let cognitoRoles: Array = new Array(); - cognitoRoles.push(this.parameters.get('authRoleName')!.valueAsString); - if (this.accessType == AccessType.AuthorizedAndGuestUsers) { - cognitoRoles.push(this.parameters.get('unauthRoleName')!.valueAsString); - } - - return new iam.CfnPolicy(this, 'MapPolicy', { - policyName: `${this.mapName}Policy`, - roles: cognitoRoles, - policyDocument: policy - }); - } + return new iam.CfnPolicy(this, 'MapPolicy', { + policyName: `${this.mapName}Policy`, + roles: cognitoRoles, + policyDocument: policy, + }); + } } diff --git a/packages/amplify-category-geo/src/service-stacks/placeIndexStack.ts b/packages/amplify-category-geo/src/service-stacks/placeIndexStack.ts index 70dc8d57c5d..eeb4da381c8 100644 --- a/packages/amplify-category-geo/src/service-stacks/placeIndexStack.ts +++ b/packages/amplify-category-geo/src/service-stacks/placeIndexStack.ts @@ -15,8 +15,8 @@ type PlaceIndexStackProps = Pick & TemplateM export class PlaceIndexStack extends BaseStack { protected readonly accessType: string; protected readonly placeIndexResource: cdk.CustomResource; - protected readonly placeIndexRegion: string - protected readonly placeIndexName: string + protected readonly placeIndexRegion: string; + protected readonly placeIndexName: string; constructor(scope: cdk.Construct, id: string, private readonly props: PlaceIndexStackProps) { super(scope, id, props); @@ -32,13 +32,10 @@ export class PlaceIndexStack extends BaseStack { 'dataSourceIntendedUse', 'pricingPlan', 'env', - 'isDefault' + 'isDefault', ]); - this.placeIndexName = Fn.join('-', [ - this.parameters.get('indexName')!.valueAsString, - this.parameters.get('env')!.valueAsString - ]); + this.placeIndexName = Fn.join('-', [this.parameters.get('indexName')!.valueAsString, this.parameters.get('env')!.valueAsString]); this.placeIndexResource = this.constructIndexResource(); this.constructIndexPolicyResource(this.placeIndexResource); @@ -47,19 +44,19 @@ export class PlaceIndexStack extends BaseStack { private constructOutputs() { new cdk.CfnOutput(this, 'Name', { - value: this.placeIndexResource.getAtt('IndexName').toString() + value: this.placeIndexResource.getAtt('IndexName').toString(), }); new cdk.CfnOutput(this, 'Region', { - value: this.placeIndexRegion + value: this.placeIndexRegion, }); new cdk.CfnOutput(this, 'Arn', { - value: this.placeIndexResource.getAtt('IndexArn').toString() + value: this.placeIndexResource.getAtt('IndexArn').toString(), }); } private constructIndexResource(): cdk.CustomResource { const geoCreateIndexStatement = new iam.PolicyStatement({ - effect: Effect.ALLOW + effect: Effect.ALLOW, }); geoCreateIndexStatement.addActions('geo:CreatePlaceIndex'); geoCreateIndexStatement.addAllResources(); @@ -67,11 +64,11 @@ export class PlaceIndexStack extends BaseStack { const placeIndexARN = cdk.Fn.sub('arn:aws:geo:${region}:${account}:place-index/${indexName}', { region: this.placeIndexRegion, account: cdk.Fn.ref('AWS::AccountId'), - indexName: this.placeIndexName + indexName: this.placeIndexName, }); const geoUpdateDeleteIndexStatement = new iam.PolicyStatement({ - effect: Effect.ALLOW + effect: Effect.ALLOW, }); geoUpdateDeleteIndexStatement.addActions('geo:UpdatePlaceIndex', 'geo:DeletePlaceIndex'); geoUpdateDeleteIndexStatement.addResources(placeIndexARN); @@ -87,7 +84,7 @@ export class PlaceIndexStack extends BaseStack { code: lambda.Code.fromInline(customPlaceIndexLambdaCode), handler: 'index.handler', runtime: Runtime.NODEJS_14_X, - timeout: Duration.seconds(300) + timeout: Duration.seconds(300), }); customPlaceIndexLambda.addToRolePolicy(geoCreateIndexStatement); customPlaceIndexLambda.addToRolePolicy(geoUpdateDeleteIndexStatement); @@ -102,7 +99,7 @@ export class PlaceIndexStack extends BaseStack { pricingPlan: indexPricingPlan, region: this.placeIndexRegion, env: cdk.Fn.ref('env'), - } + }, }); return placeIndexCustomResource; @@ -111,28 +108,25 @@ export class PlaceIndexStack extends BaseStack { // Grant read-only access to the Place Index for Authorized and/or Guest users private constructIndexPolicyResource(indexResource: cdk.CustomResource): CfnResource { let policy = new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "geo:SearchPlaceIndexForPosition", - "geo:SearchPlaceIndexForText" - ], - resources: [indexResource.getAtt('IndexArn').toString()], - }) - ], + statements: [ + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['geo:SearchPlaceIndexForPosition', 'geo:SearchPlaceIndexForText'], + resources: [indexResource.getAtt('IndexArn').toString()], + }), + ], }); let cognitoRoles: Array = new Array(); cognitoRoles.push(this.parameters.get('authRoleName')!.valueAsString); if (this.accessType == AccessType.AuthorizedAndGuestUsers) { - cognitoRoles.push(this.parameters.get('unauthRoleName')!.valueAsString); + cognitoRoles.push(this.parameters.get('unauthRoleName')!.valueAsString); } return new iam.CfnPolicy(this, 'PlaceIndexPolicy', { - policyName: `${this.placeIndexName}Policy`, - roles: cognitoRoles, - policyDocument: policy + policyName: `${this.placeIndexName}Policy`, + roles: cognitoRoles, + policyDocument: policy, }); } } diff --git a/packages/amplify-category-geo/src/service-utils/constants.ts b/packages/amplify-category-geo/src/service-utils/constants.ts index 5be5540ad83..778077ed4a7 100644 --- a/packages/amplify-category-geo/src/service-utils/constants.ts +++ b/packages/amplify-category-geo/src/service-utils/constants.ts @@ -1,10 +1,10 @@ import * as path from 'path'; export const apiDocs = { - mapStyles: "https://docs.aws.amazon.com/location-maps/latest/APIReference/API_MapConfiguration.html", - pricingPlan: "https://aws.amazon.com/location/pricing/", - dataSourceUsage: "https://docs.aws.amazon.com/location-places/latest/APIReference/API_DataSourceConfiguration.html" -} + mapStyles: 'https://docs.aws.amazon.com/location-maps/latest/APIReference/API_MapConfiguration.html', + pricingPlan: 'https://aws.amazon.com/location/pricing/', + dataSourceUsage: 'https://docs.aws.amazon.com/location-places/latest/APIReference/API_DataSourceConfiguration.html', +}; export const previewBanner = 'Amplify Geo category is in developer preview and not intended for production use at this time.'; export const chooseServiceMessageAdd = 'Select which capability you want to add:'; @@ -20,5 +20,5 @@ export enum ServiceName { Map = 'Map', PlaceIndex = 'PlaceIndex', GeofenceCollection = 'GeofenceCollection', - Tracker = 'Tracker' + Tracker = 'Tracker', } diff --git a/packages/amplify-category-geo/src/service-utils/mapUtils.ts b/packages/amplify-category-geo/src/service-utils/mapUtils.ts index 46b47818bdb..85ff0f91a03 100644 --- a/packages/amplify-category-geo/src/service-utils/mapUtils.ts +++ b/packages/amplify-category-geo/src/service-utils/mapUtils.ts @@ -4,7 +4,14 @@ import _ from 'lodash'; import { parametersFileName, provider, ServiceName } from './constants'; import { category } from '../constants'; import { MapStack } from '../service-stacks/mapStack'; -import { updateParametersFile, getGeoServiceMeta, generateTemplateFile, updateDefaultResource, readResourceMetaParameters, checkAuthConfig } from './resourceUtils'; +import { + updateParametersFile, + getGeoServiceMeta, + generateTemplateFile, + updateDefaultResource, + readResourceMetaParameters, + checkAuthConfig, +} from './resourceUtils'; import { App } from '@aws-cdk/core'; import { getTemplateMappings } from '../provider-controllers'; @@ -26,57 +33,43 @@ export const createMapResource = async (context: $TSContext, parameters: MapPara await updateDefaultResource(context, ServiceName.Map); } - context.amplify.updateamplifyMetaAfterResourceAdd( - category, - parameters.name, - mapMetaParameters - ); + context.amplify.updateamplifyMetaAfterResourceAdd(category, parameters.name, mapMetaParameters); }; -export const modifyMapResource = async ( - context: $TSContext, - parameters: Pick - ) => { +export const modifyMapResource = async (context: $TSContext, parameters: Pick) => { // allow unauth access for identity pool if guest access is enabled await checkAuthConfig(context, parameters, ServiceName.Map); // generate CFN files const templateMappings = await getTemplateMappings(context); - const mapStack = new MapStack(new App(), 'MapStack', { ...parameters, ...templateMappings}); + const mapStack = new MapStack(new App(), 'MapStack', { ...parameters, ...templateMappings }); generateTemplateFile(mapStack, parameters.name); // update the default map if (parameters.isDefault) { - await updateDefaultResource(context, ServiceName.Map , parameters.name); + await updateDefaultResource(context, ServiceName.Map, parameters.name); } const paramsToUpdate = ['accessType']; paramsToUpdate.forEach(param => { - context.amplify.updateamplifyMetaAfterResourceUpdate( - category, - parameters.name, - param, - (parameters as $TSObject)[param] - ); + context.amplify.updateamplifyMetaAfterResourceUpdate(category, parameters.name, param, (parameters as $TSObject)[param]); }); }; -function saveCFNParameters( - parameters: Pick -) { - const params = { - authRoleName: { - "Ref": "AuthRoleName" - }, - unauthRoleName: { - "Ref": "UnauthRoleName" - }, - mapName: parameters.name, - mapStyle: getGeoMapStyle(parameters.dataProvider, parameters.mapStyleType), - pricingPlan: parameters.pricingPlan, - isDefault: parameters.isDefault - }; - updateParametersFile(params, parameters.name, parametersFileName); +function saveCFNParameters(parameters: Pick) { + const params = { + authRoleName: { + Ref: 'AuthRoleName', + }, + unauthRoleName: { + Ref: 'UnauthRoleName', + }, + mapName: parameters.name, + mapStyle: getGeoMapStyle(parameters.dataProvider, parameters.mapStyleType), + pricingPlan: parameters.pricingPlan, + isDefault: parameters.isDefault, + }; + updateParametersFile(params, parameters.name, parametersFileName); } /** @@ -89,7 +82,7 @@ export const constructMapMetaParameters = (params: MapParameters): MapMetaParame service: ServiceName.Map, mapStyle: getGeoMapStyle(params.dataProvider, params.mapStyleType), pricingPlan: params.pricingPlan, - accessType: params.accessType + accessType: params.accessType, }; return result; }; @@ -101,16 +94,16 @@ export type MapMetaParameters = Pick> => { - const currentMapMetaParameters = await readResourceMetaParameters(ServiceName.Map, mapName) as MapMetaParameters; + const currentMapMetaParameters = (await readResourceMetaParameters(ServiceName.Map, mapName)) as MapMetaParameters; return { mapStyleType: getMapStyleComponents(currentMapMetaParameters.mapStyle).mapStyleType, dataProvider: getMapStyleComponents(currentMapMetaParameters.mapStyle).dataProvider, pricingPlan: currentMapMetaParameters.pricingPlan, accessType: currentMapMetaParameters.accessType, - isDefault: currentMapMetaParameters.isDefault + isDefault: currentMapMetaParameters.isDefault, }; }; @@ -127,10 +120,7 @@ export const getMapFriendlyNames = async (mapNames: string[]): Promise }); }; -export const getMapIamPolicies = ( - resourceName: string, - crudOptions: string[] -): { policy: $TSObject[], attributes: string[] } => { +export const getMapIamPolicies = (resourceName: string, crudOptions: string[]): { policy: $TSObject[]; attributes: string[] } => { const policy = []; const actions = new Set(); @@ -169,7 +159,7 @@ export const getMapIamPolicies = ( ':map/', { Ref: `${category}${resourceName}Name`, - } + }, ], ], }, @@ -179,4 +169,4 @@ export const getMapIamPolicies = ( const attributes = ['Name']; return { policy, attributes }; -} +}; diff --git a/packages/amplify-category-geo/src/service-utils/placeIndexUtils.ts b/packages/amplify-category-geo/src/service-utils/placeIndexUtils.ts index e2154b3b24f..6761594e9cd 100644 --- a/packages/amplify-category-geo/src/service-utils/placeIndexUtils.ts +++ b/packages/amplify-category-geo/src/service-utils/placeIndexUtils.ts @@ -4,7 +4,13 @@ import _ from 'lodash'; import { parametersFileName, provider, ServiceName } from './constants'; import { category } from '../constants'; import { PlaceIndexStack } from '../service-stacks/placeIndexStack'; -import { updateParametersFile, generateTemplateFile, updateDefaultResource, readResourceMetaParameters, checkAuthConfig } from './resourceUtils'; +import { + updateParametersFile, + generateTemplateFile, + updateDefaultResource, + readResourceMetaParameters, + checkAuthConfig, +} from './resourceUtils'; import { App } from '@aws-cdk/core'; import { getTemplateMappings } from '../provider-controllers'; @@ -26,17 +32,13 @@ export const createPlaceIndexResource = async (context: $TSContext, parameters: await updateDefaultResource(context, ServiceName.PlaceIndex); } - context.amplify.updateamplifyMetaAfterResourceAdd( - category, - parameters.name, - placeIndexMetaParameters - ); + context.amplify.updateamplifyMetaAfterResourceAdd(category, parameters.name, placeIndexMetaParameters); }; export const modifyPlaceIndexResource = async ( context: $TSContext, - parameters: Pick - ) => { + parameters: Pick, +) => { // allow unauth access for identity pool if guest access is enabled await checkAuthConfig(context, parameters, ServiceName.PlaceIndex); @@ -47,37 +49,32 @@ export const modifyPlaceIndexResource = async ( // update the default place index if (parameters.isDefault) { - await updateDefaultResource(context, ServiceName.PlaceIndex , parameters.name); + await updateDefaultResource(context, ServiceName.PlaceIndex, parameters.name); } const paramsToUpdate = ['accessType']; paramsToUpdate.forEach(param => { - context.amplify.updateamplifyMetaAfterResourceUpdate( - category, - parameters.name, - param, - (parameters as $TSObject)[param] - ); + context.amplify.updateamplifyMetaAfterResourceUpdate(category, parameters.name, param, (parameters as $TSObject)[param]); }); }; function saveCFNParameters( - parameters: Pick + parameters: Pick, ) { - const params = { - authRoleName: { - "Ref": "AuthRoleName" - }, - unauthRoleName: { - "Ref": "UnauthRoleName" - }, - indexName: parameters.name, - dataProvider: parameters.dataProvider, - dataSourceIntendedUse: parameters.dataSourceIntendedUse, - pricingPlan: parameters.pricingPlan, - isDefault: parameters.isDefault - }; - updateParametersFile(params, parameters.name, parametersFileName); + const params = { + authRoleName: { + Ref: 'AuthRoleName', + }, + unauthRoleName: { + Ref: 'UnauthRoleName', + }, + indexName: parameters.name, + dataProvider: parameters.dataProvider, + dataSourceIntendedUse: parameters.dataSourceIntendedUse, + pricingPlan: parameters.pricingPlan, + isDefault: parameters.isDefault, + }; + updateParametersFile(params, parameters.name, parametersFileName); } /** @@ -91,7 +88,7 @@ export const constructPlaceIndexMetaParameters = (params: PlaceIndexParameters): dataProvider: params.dataProvider, dataSourceIntendedUse: params.dataSourceIntendedUse, pricingPlan: params.pricingPlan, - accessType: params.accessType + accessType: params.accessType, }; return result; }; @@ -99,28 +96,26 @@ export const constructPlaceIndexMetaParameters = (params: PlaceIndexParameters): /** * The Meta information stored for a Place Index Resource */ -export type PlaceIndexMetaParameters = Pick & { +export type PlaceIndexMetaParameters = Pick< + PlaceIndexParameters, + 'isDefault' | 'pricingPlan' | 'accessType' | 'dataSourceIntendedUse' | 'dataProvider' +> & { providerPlugin: string; service: string; -} +}; export const getCurrentPlaceIndexParameters = async (indexName: string): Promise> => { - const currentIndexMetaParameters = await readResourceMetaParameters(ServiceName.PlaceIndex, indexName) as PlaceIndexMetaParameters; + const currentIndexMetaParameters = (await readResourceMetaParameters(ServiceName.PlaceIndex, indexName)) as PlaceIndexMetaParameters; return { dataProvider: currentIndexMetaParameters.dataProvider, dataSourceIntendedUse: currentIndexMetaParameters.dataSourceIntendedUse, pricingPlan: currentIndexMetaParameters.pricingPlan, accessType: currentIndexMetaParameters.accessType, - isDefault: currentIndexMetaParameters.isDefault + isDefault: currentIndexMetaParameters.isDefault, }; }; -export const getPlaceIndexIamPolicies = ( - resourceName: string, - crudOptions: string[] -): { policy: $TSObject[], attributes: string[] } => { +export const getPlaceIndexIamPolicies = (resourceName: string, crudOptions: string[]): { policy: $TSObject[]; attributes: string[] } => { const policy = []; const actions = new Set(); @@ -157,7 +152,7 @@ export const getPlaceIndexIamPolicies = ( ':place-index/', { Ref: `${category}${resourceName}Name`, - } + }, ], ], }, @@ -167,4 +162,4 @@ export const getPlaceIndexIamPolicies = ( const attributes = ['Name']; return { policy, attributes }; -} +}; diff --git a/packages/amplify-category-storage/package.json b/packages/amplify-category-storage/package.json index 219f9c2bf2d..4b64004d9c8 100644 --- a/packages/amplify-category-storage/package.json +++ b/packages/amplify-category-storage/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "amplify-cli-core": "1.31.1", + "amplify-headless-interface": "1.10.0", "amplify-prompts": "1.2.0", "amplify-util-import": "1.5.15", "chalk": "^4.1.1", @@ -39,7 +40,7 @@ "inquirer": "^7.3.3", "lodash": "^4.17.21", "promise-sequential": "^1.1.1", - "uuid": "^3.4.0" + "uuid": "^8.3.2" }, "devDependencies": { "aws-sdk": "^2.963.0", diff --git a/packages/amplify-category-storage/src/__tests__/provider-utils/awscloudformation/cdk-stack-builder/ddb-stack-transform.test.ts b/packages/amplify-category-storage/src/__tests__/provider-utils/awscloudformation/cdk-stack-builder/ddb-stack-transform.test.ts index 2ed25da6c73..0a57ba0c0fb 100644 --- a/packages/amplify-category-storage/src/__tests__/provider-utils/awscloudformation/cdk-stack-builder/ddb-stack-transform.test.ts +++ b/packages/amplify-category-storage/src/__tests__/provider-utils/awscloudformation/cdk-stack-builder/ddb-stack-transform.test.ts @@ -74,10 +74,6 @@ describe('Test DDB transform generates correct CFN template', () => { jest.spyOn(DynamoDBInputState.prototype, 'getCliInputPayload').mockImplementation(() => cliInputsJSON); const ddbTransform = new DDBStackTransform(resourceName); await ddbTransform.transform(); - - console.log(ddbTransform._cfn); - console.log(ddbTransform._cfnInputParams); - expect(ddbTransform._cfn).toMatchSnapshot(); expect(ddbTransform._cfnInputParams).toMatchSnapshot(); }); diff --git a/packages/amplify-category-storage/src/commands/storage/import.ts b/packages/amplify-category-storage/src/commands/storage/import.ts index 3c5a97a6742..f5b3b6ca0d9 100644 --- a/packages/amplify-category-storage/src/commands/storage/import.ts +++ b/packages/amplify-category-storage/src/commands/storage/import.ts @@ -11,7 +11,7 @@ export const run = async (context: $TSContext) => { const servicesMetadata = ((await import('../../provider-utils/supported-services')) as $TSAny).supportedServices; const serviceSelection = await context.amplify.serviceSelectionPrompt(context, categoryName, servicesMetadata, undefined, nameOverrides); - const providerController = require(`../../provider-utils/${serviceSelection.providerName}`); + const providerController = await import(`../../provider-utils/${serviceSelection.providerName}`); if (!providerController) { printer.error('Provider not configured for this category'); diff --git a/packages/amplify-category-storage/src/constants.ts b/packages/amplify-category-storage/src/constants.ts index 99598b0983a..99cad8c7081 100644 --- a/packages/amplify-category-storage/src/constants.ts +++ b/packages/amplify-category-storage/src/constants.ts @@ -1,15 +1,4 @@ export const categoryName = 'storage'; - -export enum ServiceName { - S3 = 'S3', - DynamoDB = 'DynamoDB', -} - -// keep in sync with ServiceName in amplify-category-function, but probably it will not change -export const FunctionServiceNameLambdaFunction = 'Lambda'; - -export const storageParamsFilename = 'storage-params.json'; -export const templateFilenameMap = { - [ServiceName.S3]: 's3-cloudformation-template.json.ejs', - [ServiceName.DynamoDB]: 'dynamoDb-cloudformation-template.json.ejs', -}; +export const apiCategoryName = 'api'; +export const authCategoryName = 'auth'; +export const functionCategoryName = 'function'; diff --git a/packages/amplify-category-storage/src/index.ts b/packages/amplify-category-storage/src/index.ts index 8566fcd2c9a..f81d531d9fb 100644 --- a/packages/amplify-category-storage/src/index.ts +++ b/packages/amplify-category-storage/src/index.ts @@ -1,15 +1,26 @@ +import { $TSAny, $TSContext, AmplifyCategories, AmplifySupportedService, IAmplifyResource } from 'amplify-cli-core'; +import { + validateAddStorageRequest, + validateImportStorageRequest, + validateRemoveStorageRequest, + validateUpdateStorageRequest, +} from 'amplify-util-headless-input'; import * as path from 'path'; import sequential from 'promise-sequential'; import { printer } from 'amplify-prompts'; import { updateConfigOnEnvInit } from './provider-utils/awscloudformation'; -import { $TSContext, AmplifyCategories, IAmplifyResource, pathManager } from 'amplify-cli-core'; import { DDBStackTransform } from './provider-utils/awscloudformation/cdk-stack-builder/ddb-stack-transform'; -import { DynamoDBInputState } from './provider-utils/awscloudformation/service-walkthroughs/dynamoDB-input-state'; import { transformS3ResourceStack } from './provider-utils/awscloudformation/cdk-stack-builder/s3-stack-transform'; -import { AmplifySupportedService } from 'amplify-cli-core'; +import { DynamoDBInputState } from './provider-utils/awscloudformation/service-walkthroughs/dynamoDB-input-state'; +import { + headlessAddStorage, + headlessImportStorage, + headlessRemoveStorage, + headlessUpdateStorage, +} from './provider-utils/awscloudformation/storage-configuration-helpers'; +export { categoryName as category } from './constants'; export { AmplifyDDBResourceTemplate } from './provider-utils/awscloudformation/cdk-stack-builder/types'; - async function add(context: any, providerName: any, service: any) { const options = { service, @@ -64,7 +75,7 @@ async function migrateStorageCategory(context: any) { } async function transformCategoryStack(context: $TSContext, resource: IAmplifyResource) { - if (resource.service === AmplifySupportedService.DYNAMODB ) { + if (resource.service === AmplifySupportedService.DYNAMODB) { if (canResourceBeTransformed(resource.resourceName)) { const stackGenerator = new DDBStackTransform(resource.resourceName); await stackGenerator.transform(); @@ -137,7 +148,26 @@ async function executeAmplifyCommand(context: any) { await commandModule.run(context); } -async function handleAmplifyEvent(context: any, args: any) { +export const executeAmplifyHeadlessCommand = async (context: $TSContext, headlessPayload: string) => { + switch (context.input.command) { + case 'add': + await headlessAddStorage(context, await validateAddStorageRequest(headlessPayload)); + break; + case 'update': + await headlessUpdateStorage(context, await validateUpdateStorageRequest(headlessPayload)); + break; + case 'remove': + await headlessRemoveStorage(context, await validateRemoveStorageRequest(headlessPayload)); + break; + case 'import': + await headlessImportStorage(context, await validateImportStorageRequest(headlessPayload)); + break; + default: + printer.error(`Headless mode for ${context.input.command} storage is not implemented yet`); + } +}; + +export async function handleAmplifyEvent(context: $TSContext, args: $TSAny) { printer.info(`${AmplifyCategories.STORAGE} handleAmplifyEvent to be implemented`); printer.info(`Received event args ${args}`); } diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/default-values/dynamoDb-defaults.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/default-values/dynamoDb-defaults.ts index b082c39b563..7e4c18761ab 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/default-values/dynamoDb-defaults.ts +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/default-values/dynamoDb-defaults.ts @@ -1,4 +1,4 @@ -import uuid from 'uuid'; +import { v4 as uuid } from 'uuid'; const getAllDefaults = (project: any) => { const name = project.projectConfig.projectName.toLowerCase(); diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/default-values/s3-defaults.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/default-values/s3-defaults.ts index a62ef72e942..b12802f03ca 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/default-values/s3-defaults.ts +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/default-values/s3-defaults.ts @@ -1,9 +1,9 @@ import { $TSAny } from 'amplify-cli-core'; -import uuid from 'uuid'; +import { v4 as uuid } from 'uuid'; import { S3AccessType, S3UserInputs } from '../service-walkthrough-types/s3-user-input-types'; -export const getAllDefaults = (project: $TSAny, shortId : string) : S3UserInputs => { +export const getAllDefaults = (project: Project, shortId : string) : S3UserInputs => { const name = project.projectConfig.projectName.toLowerCase(); const defaults : S3UserInputs = { resourceName: `s3${shortId}`, @@ -19,6 +19,4 @@ export const getAllDefaults = (project: $TSAny, shortId : string) : S3UserInputs return defaults; }; -module.exports = { - getAllDefaults, -}; +type Project = { projectConfig: { projectName: string } }; diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/import/import-s3.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/import/import-s3.ts index 64fdac84e30..7da35a204de 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/import/import-s3.ts +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/import/import-s3.ts @@ -4,8 +4,9 @@ import { IS3Service } from 'amplify-util-import'; import { Bucket } from 'aws-sdk/clients/s3'; import Enquirer from 'enquirer'; import _ from 'lodash'; -import uuid from 'uuid'; -import { checkIfAuthExists, resourceAlreadyExists } from '../service-walkthroughs/s3-walkthrough'; +import { v4 as uuid } from 'uuid'; +import { resourceAlreadyExists } from '../service-walkthroughs/s3-walkthrough'; +import { checkIfAuthExists } from '../storage-configuration-helpers'; import { importMessages } from './messages'; import { ImportS3HeadlessParameters, @@ -26,7 +27,7 @@ export const importS3 = async ( providerPluginInstance?: ProviderUtils, printSuccessMessage: boolean = true, ): Promise<{ envSpecificParameters: S3EnvSpecificResourceParameters } | undefined> => { - let resourceName: string | undefined = resourceAlreadyExists(context); + let resourceName: string | undefined = resourceAlreadyExists(); if (resourceName && !previousResourceParameters) { const errMessage = 'Amazon S3 storage was already added to your project.'; @@ -206,7 +207,7 @@ const createParameters = (providerName: string, bucketList: Bucket[]): S3ImportP return questionParameters; }; -const updateStateFiles = async ( +export const updateStateFiles = async ( context: $TSContext, questionParameters: S3ImportParameters, answers: S3ImportAnswers, @@ -392,8 +393,6 @@ const headlessImport = async ( // Validate required parameters' presence and merge into parameters const currentEnvSpecificParameters = ensureHeadlessParameters(resourceParameters, headlessParams); - const amplifyMeta = stateManager.getMeta(); - // Validate the parameters, generate the missing ones and import the resource. const questionParameters: S3ImportParameters = { providerName, diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/provider-constants.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/provider-constants.ts new file mode 100644 index 00000000000..e13eed450fb --- /dev/null +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/provider-constants.ts @@ -0,0 +1,15 @@ +export enum ServiceName { + S3 = 'S3', + DynamoDB = 'DynamoDB', +} + +export const storageParamsFilename = 'storage-params.json'; +export const templateFilenameMap = { + [ServiceName.S3]: 's3-cloudformation-template.json.ejs', + [ServiceName.DynamoDB]: 'dynamoDb-cloudformation-template.json.ejs', +}; + +// keep in sync with ServiceName in amplify-category-function, but probably it will not change +export const FunctionServiceNameLambdaFunction = 'Lambda'; + +export const providerName = 'awscloudformation'; diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/s3-trigger-helpers.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/s3-trigger-helpers.ts new file mode 100644 index 00000000000..8c7ade7af2b --- /dev/null +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/s3-trigger-helpers.ts @@ -0,0 +1,515 @@ +import { $TSAny, $TSContext, $TSObject, pathManager, readCFNTemplate, stateManager, writeCFNTemplate } from 'amplify-cli-core'; +import { printer, prompter } from 'amplify-prompts'; +import _ from 'lodash'; +import * as path from 'path'; +import { v4 as uuid } from 'uuid'; +import { categoryName, functionCategoryName } from '../../constants'; +import { FunctionServiceNameLambdaFunction, providerName } from './provider-constants'; + +export async function removeTrigger(context: $TSContext, resourceName: string, triggerFunctionName: string) { + // Update Cloudformtion file + const projectRoot = pathManager.findProjectRoot(); + const resourceDirPath = pathManager.getResourceDirectoryPath(projectRoot, categoryName, resourceName); + const storageCFNFilePath = path.join(resourceDirPath, 's3-cloudformation-template.json'); + const { cfnTemplate: storageCFNFile }: { cfnTemplate: $TSAny } = await readCFNTemplate(storageCFNFilePath); + const bucketParameters = stateManager.getResourceParametersJson(projectRoot, categoryName, resourceName); + const adminTrigger = bucketParameters.adminTriggerFunction; + + delete storageCFNFile.Parameters[`function${triggerFunctionName}Arn`]; + delete storageCFNFile.Parameters[`function${triggerFunctionName}Name`]; + delete storageCFNFile.Parameters[`function${triggerFunctionName}LambdaExecutionRole`]; + delete storageCFNFile.Resources.TriggerPermissions; + + if (!adminTrigger) { + // Remove reference for old triggerFunctionName + delete storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration; + delete storageCFNFile.Resources.S3TriggerBucketPolicy; + delete storageCFNFile.Resources.S3Bucket.DependsOn; + } else { + const lambdaConfigurations: $TSAny = []; + + storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration.LambdaConfigurations.forEach((triggers: $TSAny) => { + if ( + triggers.Filter && + typeof triggers.Filter.S3Key.Rules[0].Value === 'string' && + triggers.Filter.S3Key.Rules[0].Value.includes('index-faces') + ) { + lambdaConfigurations.push(triggers); + } + }); + + storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration.LambdaConfigurations = lambdaConfigurations; + + const index = storageCFNFile.Resources.S3Bucket.DependsOn.indexOf('TriggerPermissions'); + + if (index > -1) { + storageCFNFile.Resources.S3Bucket.DependsOn.splice(index, 1); + } + + const roles: $TSAny[] = []; + + storageCFNFile.Resources.S3TriggerBucketPolicy.Properties.Roles.forEach((role: $TSAny) => { + if (!role.Ref.includes(triggerFunctionName)) { + roles.push(role); + } + }); + + storageCFNFile.Resources.S3TriggerBucketPolicy.Properties.Roles = roles; + } + + await writeCFNTemplate(storageCFNFile, storageCFNFilePath); + + const meta = stateManager.getMeta(projectRoot); + const s3DependsOnResources = meta.storage[resourceName].dependsOn; + const s3Resources: $TSAny[] = []; + + s3DependsOnResources.forEach((resource: $TSAny) => { + if (resource.resourceName !== triggerFunctionName) { + s3Resources.push(resource); + } + }); + + context.amplify.updateamplifyMetaAfterResourceUpdate(categoryName, resourceName, 'dependsOn', s3Resources); +} + +/* + * When updating, remove the old trigger before adding a new one + * This function has a side effect of updating the dependsOn array + */ +export async function addTrigger( + context: $TSContext, + resourceName: string, + triggerFunction: $TSAny, + adminTriggerFunction: $TSAny, + options: { dependsOn?: $TSAny[]; headlessTrigger?: { name: string; mode: 'new' | 'existing' } }, +) { + const triggerTypeChoices = ['Choose an existing function from the project', 'Create a new function']; + const [shortId] = uuid().split('-'); + let functionName = `S3Trigger${shortId}`; + + let useExistingFunction: boolean; + + if (options?.headlessTrigger) { + functionName = options.headlessTrigger.name; + useExistingFunction = options.headlessTrigger.mode === 'existing'; + } else { + const triggerTypeQuestion = { + message: 'Select from the following options', + choices: triggerTypeChoices, + }; + useExistingFunction = (await prompter.pick(triggerTypeQuestion.message, triggerTypeQuestion.choices)) === triggerTypeChoices[0]; + + if (useExistingFunction) { + let lambdaResources = await getLambdaFunctions(context); + + if (triggerFunction) { + lambdaResources = lambdaResources.filter((lambdaResource: $TSAny) => lambdaResource !== triggerFunction); + } + + if (lambdaResources.length === 0) { + throw new Error("No functions were found in the project. Use 'amplify add function' to add a new function."); + } + + const triggerOptionQuestion = { + message: 'Select from the following options', + choices: lambdaResources, + }; + + functionName = await prompter.pick(triggerOptionQuestion.message, triggerOptionQuestion.choices); + } + } + + // Update Lambda CFN + const functionCFNFilePath = path.join( + pathManager.getResourceDirectoryPath(undefined, functionCategoryName, functionName), + `${functionName}-cloudformation-template.json`, + ); + + if (useExistingFunction) { + const { cfnTemplate: functionCFNFile }: { cfnTemplate: $TSAny } = await readCFNTemplate(functionCFNFilePath); + + functionCFNFile.Outputs.LambdaExecutionRole = { + Value: { + Ref: 'LambdaExecutionRole', + }, + }; + + // Update the function resource's CFN template + + await writeCFNTemplate(functionCFNFile, functionCFNFilePath); + + printer.success(`Successfully updated resource ${functionName} locally`); + } else { + // Create a new lambda trigger + + const targetDir = pathManager.getBackendDirPath(); + const pluginDir = __dirname; + + const defaults = { + functionName, + roleName: `${functionName}LambdaRole${shortId}`, + }; + + const copyJobs = [ + { + dir: pluginDir, + template: path.join('..', '..', '..', 'resources', 'triggers', 's3', 'lambda-cloudformation-template.json.ejs'), + target: path.join(targetDir, functionCategoryName, functionName, `${functionName}-cloudformation-template.json`), + }, + { + dir: pluginDir, + template: path.join('..', '..', '..', 'resources', 'triggers', 's3', 'event.json'), + target: path.join(targetDir, functionCategoryName, functionName, 'src', 'event.json'), + }, + { + dir: pluginDir, + template: path.join('..', '..', '..', 'resources', 'triggers', 's3', 'index.js'), + target: path.join(targetDir, functionCategoryName, functionName, 'src', 'index.js'), + }, + { + dir: pluginDir, + template: path.join('..', '..', '..', 'resources', 'triggers', 's3', 'package.json.ejs'), + target: path.join(targetDir, functionCategoryName, functionName, 'src', 'package.json'), + }, + ]; + + // copy over the files + await context.amplify.copyBatch(context, copyJobs, defaults, !!options?.headlessTrigger); + + // Update amplify-meta and backend-config + + const backendConfigs = { + service: FunctionServiceNameLambdaFunction, + providerPlugin: providerName, + build: true, + }; + + await context.amplify.updateamplifyMetaAfterResourceAdd(functionCategoryName, functionName, backendConfigs); + + printer.success(`Successfully added resource ${functionName} locally`); + + if ( + !options?.headlessTrigger && + (await context.amplify.confirmPrompt(`Do you want to edit the local ${functionName} lambda function now?`)) + ) { + await context.amplify.openEditor(context, path.join(targetDir, functionCategoryName, functionName, 'src', 'index.js')); + } + } + + // If updating an already existing S3 resource + if (resourceName) { + // Update Cloudformtion file + const projectBackendDirPath = pathManager.getBackendDirPath(); + const storageCFNFilePath = path.join(projectBackendDirPath, categoryName, resourceName, 's3-cloudformation-template.json'); + const { cfnTemplate: storageCFNFile }: { cfnTemplate: $TSAny } = await readCFNTemplate(storageCFNFilePath); + const amplifyMetaFile = stateManager.getMeta(); + + // Remove reference for old triggerFunction + if (triggerFunction) { + delete storageCFNFile.Parameters[`function${triggerFunction}Arn`]; + delete storageCFNFile.Parameters[`function${triggerFunction}Name`]; + delete storageCFNFile.Parameters[`function${triggerFunction}LambdaExecutionRole`]; + } + + // Add reference for the new triggerFunction + + storageCFNFile.Parameters[`function${functionName}Arn`] = { + Type: 'String', + Default: `function${functionName}Arn`, + }; + + storageCFNFile.Parameters[`function${functionName}Name`] = { + Type: 'String', + Default: `function${functionName}Name`, + }; + + storageCFNFile.Parameters[`function${functionName}LambdaExecutionRole`] = { + Type: 'String', + Default: `function${functionName}LambdaExecutionRole`, + }; + + storageCFNFile.Parameters.triggerFunction = { + Type: 'String', + }; + + if (adminTriggerFunction && !triggerFunction) { + storageCFNFile.Resources.S3Bucket.DependsOn.push('TriggerPermissions'); + storageCFNFile.Resources.S3TriggerBucketPolicy.Properties.Roles.push({ + Ref: `function${functionName}LambdaExecutionRole`, + }); + + // eslint-disable-next-line max-len + let lambdaConf = storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration.LambdaConfigurations; + + lambdaConf = lambdaConf.concat( + getTriggersForLambdaConfiguration('private', functionName), + getTriggersForLambdaConfiguration('protected', functionName), + getTriggersForLambdaConfiguration('public', functionName), + ); + + // eslint-disable-next-line max-len + storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration.LambdaConfigurations = lambdaConf; + + const dependsOnResources = amplifyMetaFile.storage[resourceName].dependsOn; + + dependsOnResources.push({ + category: functionCategoryName, + resourceName: functionName, + attributes: ['Name', 'Arn', 'LambdaExecutionRole'], + }); + + context.amplify.updateamplifyMetaAfterResourceUpdate(categoryName, resourceName, 'dependsOn', dependsOnResources); + } else if (adminTriggerFunction && triggerFunction !== 'NONE') { + storageCFNFile.Resources.S3TriggerBucketPolicy.Properties.Roles.forEach((role: $TSAny) => { + if (role.Ref.includes(triggerFunction)) { + role.Ref = `function${functionName}LambdaExecutionRole`; + } + }); + + storageCFNFile.Resources.TriggerPermissions.Properties.FunctionName.Ref = `function${functionName}Name`; + + // eslint-disable-next-line max-len + storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration.LambdaConfigurations.forEach((lambdaConf: $TSAny) => { + if ( + !(typeof lambdaConf.Filter.S3Key.Rules[0].Value === 'string' && lambdaConf.Filter.S3Key.Rules[0].Value.includes('index-faces')) + ) { + lambdaConf.Function.Ref = `function${functionName}Arn`; + } + }); + + const dependsOnResources = amplifyMetaFile.storage[resourceName].dependsOn; + + dependsOnResources.forEach((resource: $TSAny) => { + if (resource.resourceName === triggerFunction) { + resource.resourceName = functionName; + } + }); + + context.amplify.updateamplifyMetaAfterResourceUpdate(categoryName, resourceName, 'dependsOn', dependsOnResources); + } else { + storageCFNFile.Resources.S3Bucket.Properties.NotificationConfiguration = { + LambdaConfigurations: [ + { + Event: 's3:ObjectCreated:*', + Function: { + Ref: `function${functionName}Arn`, + }, + }, + { + Event: 's3:ObjectRemoved:*', + Function: { + Ref: `function${functionName}Arn`, + }, + }, + ], + }; + + storageCFNFile.Resources.S3Bucket.DependsOn = ['TriggerPermissions']; + + storageCFNFile.Resources.S3TriggerBucketPolicy = { + Type: 'AWS::IAM::Policy', + DependsOn: ['S3Bucket'], + Properties: { + PolicyName: 's3-trigger-lambda-execution-policy', + Roles: [ + { + Ref: `function${functionName}LambdaExecutionRole`, + }, + ], + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: ['s3:PutObject', 's3:GetObject', 's3:DeleteObject'], + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'S3Bucket', + }, + '/*', + ], + ], + }, + ], + }, + { + Effect: 'Allow', + Action: 's3:ListBucket', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'S3Bucket', + }, + ], + ], + }, + ], + }, + ], + }, + }, + }; + + // Update DependsOn + const dependsOnResources = options.dependsOn || amplifyMetaFile.storage[resourceName].dependsOn || []; + + dependsOnResources.filter((resource: $TSAny) => resource.resourceName !== triggerFunction); + + dependsOnResources.push({ + category: functionCategoryName, + resourceName: functionName, + attributes: ['Name', 'Arn', 'LambdaExecutionRole'], + }); + + context.amplify.updateamplifyMetaAfterResourceUpdate(categoryName, resourceName, 'dependsOn', dependsOnResources); + } + + storageCFNFile.Resources.TriggerPermissions = { + Type: 'AWS::Lambda::Permission', + Properties: { + Action: 'lambda:InvokeFunction', + FunctionName: { + Ref: `function${functionName}Name`, + }, + Principal: 's3.amazonaws.com', + SourceAccount: { + Ref: 'AWS::AccountId', + }, + SourceArn: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + 'Fn::If': [ + 'ShouldNotCreateEnvResources', + { + Ref: 'bucketName', + }, + { + 'Fn::Join': [ + '', + [ + { + Ref: 'bucketName', + }, + { + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + '-', + { + Ref: 'AWS::StackName', + }, + ], + }, + ], + }, + '-', + { + Ref: 'env', + }, + ], + ], + }, + ], + }, + ], + ], + }, + }, + }; + + await writeCFNTemplate(storageCFNFile, storageCFNFilePath); + } else { + // New resource + if (!options.dependsOn) { + options.dependsOn = []; + } + + options.dependsOn.push({ + category: functionCategoryName, + resourceName: functionName, + attributes: ['Name', 'Arn', 'LambdaExecutionRole'], + }); + } + + return functionName; +} + +function getTriggersForLambdaConfiguration(protectionLevel: string, functionName: string) { + const triggers = [ + { + Event: 's3:ObjectCreated:*', + Filter: { + S3Key: { + Rules: [ + { + Name: 'prefix', + Value: { + 'Fn::Join': [ + '', + [ + `${protectionLevel}/`, + { + Ref: 'AWS::Region', + }, + ], + ], + }, + }, + ], + }, + }, + Function: { + Ref: `function${functionName}Arn`, + }, + }, + { + Event: 's3:ObjectRemoved:*', + Filter: { + S3Key: { + Rules: [ + { + Name: 'prefix', + Value: { + 'Fn::Join': [ + '', + [ + `${protectionLevel}/`, + { + Ref: 'AWS::Region', + }, + ], + ], + }, + }, + ], + }, + }, + Function: { + Ref: `function${functionName}Arn`, + }, + }, + ]; + return triggers; +} + +async function getLambdaFunctions(context: $TSContext) { + const { allResources } = await context.amplify.getResourceStatus(); + const lambdaResources = allResources + .filter((resource: $TSObject) => resource.service === FunctionServiceNameLambdaFunction) + .map((resource: $TSObject) => resource.resourceName); + + return lambdaResources; +} diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.ts index 4cf8b806622..21212c44a9b 100644 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.ts +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/service-walkthroughs/s3-walkthrough.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import * as fs from 'fs-extra'; -import uuid from 'uuid'; +import { v4 as uuid } from 'uuid'; import { printer, prompter } from 'amplify-prompts'; import { exitOnNextTick, @@ -55,8 +55,8 @@ export async function addWalkthrough(context: $TSContext, defaultValuesFilename: //Migrate auth category if required try { - await migrateAuthDependencyResource(context) - } catch ( error ){ + await migrateAuthDependencyResource(context); + } catch (error) { await printErrorAuthResourceMigrationFailed(context); exitOnNextTick(0); } @@ -136,7 +136,7 @@ export async function updateWalkthrough(context: $TSContext) { if (!cliInputsState.cliInputFileExists()) { if (context.exeInfo?.forcePush || (await prompter.confirmContinue('File migration required to continue. Do you want to continue?'))) { //migrate auth and storage - await cliInputsState.migrate( context ); + await cliInputsState.migrate(context); const stackGenerator = new AmplifyS3ResourceStackTransform(resourceName, context); await stackGenerator.transform(CLISubCommandType.UPDATE); //generates cloudformation } else { @@ -195,7 +195,7 @@ export async function migrateStorageCategory(context: $TSContext, resourceName: let cliInputsState = new S3InputState(resourceName, undefined); //Check if migration is required if (!cliInputsState.cliInputFileExists()) { - await cliInputsState.migrate( context ); + await cliInputsState.migrate(context); const stackGenerator = new AmplifyS3ResourceStackTransform(resourceName, context); await stackGenerator.transform(CLISubCommandType.MIGRATE); return stackGenerator.getCFN(); @@ -614,7 +614,7 @@ async function getLambdaFunctionList(context: $TSAny) { : []; return lambdaResources ? lambdaResources : []; } -export const resourceAlreadyExists = (_context: $TSContext) => { +export const resourceAlreadyExists = () => { const amplifyMeta = stateManager.getMeta(); let resourceName; diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/storage-configuration-helpers.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/storage-configuration-helpers.ts new file mode 100644 index 00000000000..641d0845cab --- /dev/null +++ b/packages/amplify-category-storage/src/provider-utils/awscloudformation/storage-configuration-helpers.ts @@ -0,0 +1,621 @@ +import { + $TSAny, + $TSContext, + $TSMeta, + $TSObject, + isResourceNameUnique, + JSONUtilities, + pathManager, + readCFNTemplate, + ResourceAlreadyExistsError, + ResourceDoesNotExistError, + stateManager, + writeCFNTemplate, +} from 'amplify-cli-core'; +import { + AddStorageRequest, + CrudOperation, + ImportStorageRequest, + LambdaTriggerConfig, + RemoveStorageRequest, + S3Permissions, + UpdateStorageRequest, +} from 'amplify-headless-interface'; +import { printer } from 'amplify-prompts'; +import * as fs from 'fs-extra'; +import _ from 'lodash'; +import * as path from 'path'; +import { v4 as uuid } from 'uuid'; +import { authCategoryName, categoryName, functionCategoryName } from '../../constants'; +import { ProviderUtils } from '../awscloudformation/import/types'; +import { getAllDefaults } from './default-values/s3-defaults'; +import { updateStateFiles } from './import/import-s3'; +import { addTrigger, removeTrigger } from './s3-trigger-helpers'; +import { providerName, ServiceName, storageParamsFilename, templateFilenameMap } from './provider-constants'; +import { resourceAlreadyExists } from './service-walkthroughs/s3-walkthrough'; + +const enum S3IamPolicy { + GET = 's3:GetObject', + PUT = 's3:PutObject', + LIST = 's3:ListBucket', + DELETE = 's3:DeleteObject', +} + +const headlessCrudOperationToIamPoliciesMap = { + [CrudOperation.CREATE_AND_UPDATE]: [S3IamPolicy.PUT], + [CrudOperation.READ]: [S3IamPolicy.GET, S3IamPolicy.LIST], + [CrudOperation.DELETE]: [S3IamPolicy.DELETE], +}; + +// map of s3 actions corresponding to CRUD verbs +// 'create/update' have been consolidated since s3 only has put concept +export const permissionMap: $TSObject = { + 'create/update': ['s3:PutObject'], + read: ['s3:GetObject', 's3:ListBucket'], + delete: ['s3:DeleteObject'], +}; + +const replaceAndWithSlash = (crudOperation: CrudOperation) => crudOperation.replace('_AND_', '/').toLowerCase(); + +const convertHeadlessPayloadToParameters = (arr: S3IamPolicy[], op: CrudOperation) => arr.concat(headlessCrudOperationToIamPoliciesMap[op]); + +export async function headlessAddStorage(context: $TSContext, storageRequest: AddStorageRequest) { + if (!checkIfAuthExists()) { + const error = new Error( + 'Cannot headlessly add storage resource without an existing auth resource. It can be added with "amplify add auth"', + ); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + if (storageRequest.serviceConfiguration.serviceName === ServiceName.S3) { + if (resourceAlreadyExists()) { + const error = new ResourceAlreadyExistsError('Amazon S3 storage was already added to your project.'); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + const meta = stateManager.getMeta(); + + if (storageRequest.serviceConfiguration.permissions.groups && !doUserPoolGroupsExist(meta)) { + const error = new Error('No user pool groups found in amplify-meta.json.'); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + await createS3StorageArtifacts(context, storageRequest); + } else if (storageRequest.serviceConfiguration.serviceName === ServiceName.DynamoDB) { + const error = new Error('Headless support for DynamoDB resources is not yet implemented.'); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } +} + +export async function headlessUpdateStorage(context: $TSContext, storageRequest: UpdateStorageRequest) { + if (storageRequest.serviceModification.serviceName === ServiceName.S3) { + const { + serviceModification: { permissions, resourceName }, + } = storageRequest; + + const meta = stateManager.getMeta(); + const storageResource = meta[categoryName][resourceName]; + + if (!storageResource || storageResource.service !== ServiceName.S3) { + const error = new ResourceDoesNotExistError(`No S3 resource '${resourceName}' found in amplify-meta.json.`); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + if (storageResource.mobileHubMigrated === true) { + const error = new Error(`Updating storage resources migrated from mobile hub is not supported.`); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + if (storageResource.serviceType === 'imported') { + const error = new Error('Updating an imported storage resource is not supported.'); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + if (permissions.groups && !doUserPoolGroupsExist(meta)) { + const error = new Error('No user pool groups found in amplify-meta.json.'); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + await updateS3StorageArtifacts(context, storageRequest, storageResource); + } else if (storageRequest.serviceModification.serviceName === ServiceName.DynamoDB) { + const error = new Error('Headless support for DynamoDB resources is not yet implemented.'); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } +} + +export async function headlessImportStorage(context: $TSContext, storageRequest: ImportStorageRequest) { + const { + serviceConfiguration: { bucketName, serviceName }, + } = storageRequest; + + if (!checkIfAuthExists()) { + const error = new Error( + 'Cannot headlessly import storage resource without an existing auth resource. It can be added with "amplify add auth"', + ); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + if (storageRequest.serviceConfiguration.serviceName === ServiceName.S3) { + if (resourceAlreadyExists()) { + const error = new ResourceAlreadyExistsError('Amazon S3 storage was already added to your project.'); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } + + const serviceMetadata = ((await import('../supported-services')) as $TSAny).supportedServices[serviceName]; + const { provider } = serviceMetadata; + + const providerUtils = context.amplify.getPluginInstance(context, provider) as ProviderUtils; + const s3 = await providerUtils.createS3Service(context); + + const bucketExists = await s3.bucketExists(bucketName); + + if (!bucketExists) { + const error = new Error(`The specified bucket: "${bucketName}" does not exist.`); + await context.usageData.emitError(error); + throw error; + } + + const bucketRegion = await s3.getBucketLocation(bucketName!); + + const projectConfig = context.amplify.getProjectConfig(); + const [shortId] = uuid().split('-'); + const projectName = projectConfig.projectName.toLowerCase().replace(/[^A-Za-z0-9_]+/g, '_'); + const resourceName = `${projectName}${shortId}`; + + const questionParameters = { + providerName: provider, + bucketList: [], + region: bucketRegion, + }; + + const answers = { + resourceName, + bucketName, + }; + + // As this is a resource add, we need to update environment specific parameters + await updateStateFiles(context, questionParameters, answers, true); + } else if (storageRequest.serviceConfiguration.serviceName === ServiceName.DynamoDB) { + const error = new Error('Headless support for importing DynamoDB resources is not yet implemented.'); + await context.usageData.emitError(error); + error.stack = undefined; + throw error; + } +} + +export async function headlessRemoveStorage(context: $TSContext, storageRequest: RemoveStorageRequest) { + const { resourceName, deleteBucketAndContents } = storageRequest.serviceConfiguration; + + if (deleteBucketAndContents === true) { + throw new Error('deleteBucketAndContents is set to true, but the functionality is not yet implemented.'); + } + + try { + await context.amplify.removeResource(context, categoryName, resourceName, { headless: true }); + } catch (error: $TSAny) { + printer.error(`An error occurred when headlessly removing the storage resource "${resourceName}": ${error.message || error}`); + + await context.usageData.emitError(error); + + process.exitCode = 1; + } +} + +function constructParametersJson(parameters: $TSAny, permissions: S3Permissions, lambdaTrigger?: LambdaTriggerConfig) { + parameters.triggerFunction = lambdaTrigger?.name ?? 'NONE'; + + parameters.selectedAuthenticatedPermissions = permissions.auth.reduce(convertHeadlessPayloadToParameters, []); + parameters.selectedGuestPermissions = (permissions.guest || []).reduce(convertHeadlessPayloadToParameters, []); + + const isGuestAllowedAccess = (permissions?.guest?.length ?? 0) > 0; + parameters.storageAccess = isGuestAllowedAccess ? 'authAndGuest' : authCategoryName; + createPermissionKeys('Authenticated', parameters, parameters.selectedAuthenticatedPermissions); + if (isGuestAllowedAccess) { + createPermissionKeys('Guest', parameters, parameters.selectedGuestPermissions); + } + + removeNotStoredParameters(parameters); + return parameters; +} + +async function createS3StorageArtifacts(context: $TSContext, storageRequest: AddStorageRequest) { + const [shortId] = uuid().split('-'); + const { + serviceConfiguration: { permissions, lambdaTrigger }, + } = storageRequest; + let { + serviceConfiguration: { bucketName, resourceName }, + } = storageRequest; + let parameters = getAllDefaults(context.amplify.getProjectDetails(), shortId); + + if (!resourceName) { + ({ resourceName } = parameters); + } + + if (!bucketName) { + ({ bucketName } = parameters); + } + + if (isResourceNameUnique(categoryName, resourceName!)) { + const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, categoryName, resourceName!); + fs.ensureDirSync(resourceDirPath); + + // create parameters.json + parameters.bucketName = bucketName; + parameters.resourceName = resourceName; + + parameters = constructParametersJson(parameters, permissions, lambdaTrigger); + stateManager.setResourceParametersJson(undefined, categoryName, resourceName!, parameters); + + // create storage-params.json + const storageParamsPermissions: { [k: string]: string[] } = {}; + Object.entries(permissions.groups || {}).forEach(([cognitoUserGroupName, crudOperations]: [string, CrudOperation[]]) => { + storageParamsPermissions[cognitoUserGroupName] = crudOperations.map(replaceAndWithSlash); + }); + writeToStorageParamsFile(resourceName!, { groupPermissionMap: storageParamsPermissions }); + + // construct dependsOn array + let dependsOn: $TSAny[] = []; + + if (storageRequest.serviceConfiguration.permissions.groups) { + dependsOn.push({ + category: authCategoryName, + resourceName: await getAuthResourceName(context), + attributes: ['UserPoolId'], + }); + + for (const group of Object.keys(storageRequest.serviceConfiguration.permissions.groups)) { + dependsOn.push({ + category: authCategoryName, + resourceName: 'userPoolGroups', + attributes: [`${group}GroupRole`], + }); + } + } + + if (storageRequest.serviceConfiguration?.lambdaTrigger) { + const options = { headlessTrigger: storageRequest.serviceConfiguration.lambdaTrigger, dependsOn }; + await addTrigger(context, parameters.resourceName!, undefined, undefined, options); + } + + // create cfn + await copyCfnTemplate(context, categoryName, resourceName!, { ...parameters, dependsOn }, true); + + // update meta + context.amplify.updateamplifyMetaAfterResourceAdd(categoryName, resourceName!, { + service: ServiceName.S3, + providerPlugin: providerName, + dependsOn, + }); + } +} + +async function updateS3StorageArtifacts(context: $TSContext, storageRequest: UpdateStorageRequest, storageResource: $TSAny) { + const { + serviceModification: { permissions, resourceName, lambdaTrigger }, + } = storageRequest; + + let { dependsOn } = storageResource; + + // update parameters.json + let parameters = stateManager.getResourceParametersJson(undefined, categoryName, resourceName); + parameters = constructParametersJson(parameters, permissions, lambdaTrigger); + stateManager.setResourceParametersJson(undefined, categoryName, resourceName, parameters); + + // update storage-params.json + const { groupPermissionMap } = readStorageParamsFileSafe(resourceName); + const storageParamsPermissions: { [k: string]: string[] } = {}; + Object.entries(permissions.groups || {}).forEach(([cognitoUserGroupName, crudOperations]: [string, CrudOperation[]]) => { + storageParamsPermissions[cognitoUserGroupName] = crudOperations.map(replaceAndWithSlash); + }); + writeToStorageParamsFile(resourceName, { groupPermissionMap: storageParamsPermissions }); + + Object.entries(storageParamsPermissions).forEach(([groupName, perms]: [string, string[]]) => { + storageParamsPermissions[groupName] = []; + for (const p of perms) { + storageParamsPermissions[groupName] = storageParamsPermissions[groupName].concat(permissionMap[p]); + } + }); + + // update cfn + await updateCfnTemplateWithGroups( + context, + Object.keys(groupPermissionMap || {}), + Object.keys(storageParamsPermissions), + storageParamsPermissions, + resourceName, + await getAuthResourceName(context), + ); + + const trigger: { category: string; resourceName: string; attributes: $TSAny[] } | undefined = _.first( + dependsOn.filter((d: $TSAny) => d.category === functionCategoryName), + ); + + if (trigger) { + removeTrigger(context, resourceName, trigger.resourceName); + } + + // update dependsOn array + dependsOn = []; + if (storageRequest.serviceModification.permissions.groups) { + dependsOn.push({ + category: authCategoryName, + resourceName: await getAuthResourceName(context), + attributes: ['UserPoolId'], + }); + + for (const group of Object.keys(storageRequest.serviceModification.permissions.groups)) { + dependsOn.push({ + category: authCategoryName, + resourceName: 'userPoolGroups', + attributes: [`${group}GroupRole`], + }); + } + } + + if (storageRequest.serviceModification?.lambdaTrigger) { + const options = { headlessTrigger: storageRequest.serviceModification.lambdaTrigger, dependsOn }; + await addTrigger(context, resourceName, undefined, parameters.adminTriggerFunction, options); + } + + // update meta + const meta = stateManager.getMeta(); + meta[categoryName][resourceName].dependsOn = dependsOn; + stateManager.setMeta(undefined, meta); +} + +function doUserPoolGroupsExist(meta: $TSMeta) { + const { userPoolGroups } = meta[authCategoryName]; + return userPoolGroups && userPoolGroups.service === 'Cognito-UserPool-Groups'; +} + +export const checkIfAuthExists = () => { + const amplifyMeta = stateManager.getMeta(); + let authExists = false; + const authServiceName = 'Cognito'; + const authCategory = authCategoryName; + + if (amplifyMeta[authCategory] && Object.keys(amplifyMeta[authCategory]).length > 0) { + const categoryResources = amplifyMeta[authCategory]; + + Object.keys(categoryResources).forEach(resource => { + if (categoryResources[resource].service === authServiceName) { + authExists = true; + } + }); + } + + return authExists; +}; + +export async function getAuthResourceName(context: $TSContext) { + let authResources = (await context.amplify.getResourceStatus(authCategoryName)).allResources; + + authResources = authResources.filter((resource: $TSAny) => resource.service === 'Cognito'); + + if (authResources.length === 0) { + throw new Error('No auth resource found. Please add it using amplify add auth'); + } + + return authResources[0].resourceName; +} + +export async function updateCfnTemplateWithGroups( + context: $TSContext, + oldGroupList: $TSAny[], + newGroupList: $TSAny[], + newGroupPolicyMap: $TSObject, + s3ResourceName: string, + authResourceName: string, +) { + const groupsToBeDeleted = _.difference(oldGroupList, newGroupList); + + // Update Cloudformtion file + const projectRoot = pathManager.findProjectRoot(); + const resourceDirPath = pathManager.getResourceDirectoryPath(projectRoot, categoryName, s3ResourceName); + const storageCFNFilePath = path.join(resourceDirPath, 's3-cloudformation-template.json'); + + const { cfnTemplate: storageCFNFile }: { cfnTemplate: $TSAny } = await readCFNTemplate(storageCFNFilePath); + + const meta = stateManager.getMeta(projectRoot); + + let s3DependsOnResources = meta.storage[s3ResourceName].dependsOn || []; + + s3DependsOnResources = s3DependsOnResources.filter((resource: $TSAny) => resource.category !== authCategoryName); + + if (newGroupList.length > 0) { + s3DependsOnResources.push({ + category: authCategoryName, + resourceName: authResourceName, + attributes: ['UserPoolId'], + }); + } + + storageCFNFile.Parameters[`auth${authResourceName}UserPoolId`] = { + Type: 'String', + Default: `auth${authResourceName}UserPoolId`, + }; + + groupsToBeDeleted.forEach(group => { + delete storageCFNFile.Parameters[`authuserPoolGroups${group}GroupRole`]; + delete storageCFNFile.Resources[`${group}GroupPolicy`]; + }); + + newGroupList.forEach(group => { + s3DependsOnResources.push({ + category: authCategoryName, + resourceName: 'userPoolGroups', + attributes: [`${group}GroupRole`], + }); + + storageCFNFile.Parameters[`authuserPoolGroups${group}GroupRole`] = { + Type: 'String', + Default: `authuserPoolGroups${group}GroupRole`, + }; + + storageCFNFile.Resources[`${group}GroupPolicy`] = { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: `${group}-group-s3-policy`, + Roles: [ + { + 'Fn::Join': [ + '', + [ + { + Ref: `auth${authResourceName}UserPoolId`, + }, + `-${group}GroupRole`, + ], + ], + }, + ], + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: newGroupPolicyMap[group], + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'S3Bucket', + }, + '/*', + ], + ], + }, + ], + }, + ], + }, + }, + }; + }); + + // added a new policy for the user group to make action on buckets + newGroupList.forEach(group => { + if (newGroupPolicyMap[group].includes('s3:ListBucket') === true) { + storageCFNFile.Resources[`${group}GroupPolicy`].Properties.PolicyDocument.Statement.push({ + Effect: 'Allow', + Action: 's3:ListBucket', + Resource: [ + { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'S3Bucket', + }, + ], + ], + }, + ], + }); + } + }); + + context.amplify.updateamplifyMetaAfterResourceUpdate(categoryName, s3ResourceName, 'dependsOn', s3DependsOnResources); + + await writeCFNTemplate(storageCFNFile, storageCFNFilePath); +} + +export function removeNotStoredParameters(defaults: $TSAny) { + delete defaults.resourceName; + delete defaults.storageAccess; + delete defaults.groupPolicyMap; + delete defaults.groupList; + delete defaults.authResourceName; +} + +export function readStorageParamsFileSafe(resourceName: string) { + return JSONUtilities.readJson<$TSObject>(getStorageParamsFilePath(resourceName), { throwIfNotExist: false }) || {}; +} + +export function writeToStorageParamsFile(resourceName: string, storageParams: $TSAny) { + JSONUtilities.writeJson(getStorageParamsFilePath(resourceName), storageParams); +} + +export function createPermissionKeys(userType: string, parameters: $TSAny, selectedPermissions: string[]) { + const [policyId] = uuid().split('-'); + + // max arrays represent highest possibly privileges for particular S3 keys + const maxPermissions = ['s3:GetObject', 's3:PutObject', 's3:DeleteObject']; + const maxPublic = maxPermissions; + const maxUploads = ['s3:PutObject']; + const maxPrivate = userType === 'Authenticated' ? maxPermissions : []; + const maxProtected = userType === 'Authenticated' ? maxPermissions : ['s3:GetObject']; + + function addPermissionKeys(key: string, possiblePermissions: string[]) { + const permissions = _.intersection(selectedPermissions, possiblePermissions).join(); + + parameters[`s3Permissions${userType}${key}`] = !permissions ? 'DISALLOW' : permissions; + parameters[`s3${key}Policy`] = `${key}_policy_${policyId}`; + } + + addPermissionKeys('Public', maxPublic); + addPermissionKeys('Uploads', maxUploads); + + if (userType !== 'Guest') { + addPermissionKeys('Protected', maxProtected); + addPermissionKeys('Private', maxPrivate); + } + + parameters[`${userType}AllowList`] = selectedPermissions.includes('s3:GetObject') ? 'ALLOW' : 'DISALLOW'; + parameters.s3ReadPolicy = `read_policy_${policyId}`; + + // double-check to make sure guest is denied + if (parameters.storageAccess !== 'authAndGuest') { + parameters.s3PermissionsGuestPublic = 'DISALLOW'; + parameters.s3PermissionsGuestUploads = 'DISALLOW'; + parameters.GuestAllowList = 'DISALLOW'; + } +} + +export async function copyCfnTemplate(context: $TSContext, categoryName: string, resourceName: string, options: $TSAny, headless = false) { + const targetDir = pathManager.getBackendDirPath(); + const pluginDir = __dirname; + + const copyJobs = [ + { + dir: pluginDir, + template: path.join('..', '..', '..', 'resources', 'cloudformation-templates', templateFilenameMap[ServiceName.S3]), + target: path.join(targetDir, categoryName, resourceName, 's3-cloudformation-template.json'), + }, + ]; + + // copy over the files + return await context.amplify.copyBatch(context, copyJobs, options, headless); +} + +function getStorageParamsFilePath(resourceName: string) { + const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, categoryName, resourceName); + return path.join(resourceDirPath, storageParamsFilename); +} diff --git a/packages/amplify-category-storage/src/provider-utils/awscloudformation/storage-state-management.ts b/packages/amplify-category-storage/src/provider-utils/awscloudformation/storage-state-management.ts deleted file mode 100644 index 028ad1e7280..00000000000 --- a/packages/amplify-category-storage/src/provider-utils/awscloudformation/storage-state-management.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { $TSAny, $TSObject, JSONUtilities, pathManager } from 'amplify-cli-core'; -import * as path from 'path'; - -import { categoryName, storageParamsFilename } from '../../constants'; - -export function readStorageParamsFileSafe(resourceName: string) { - return JSONUtilities.readJson<$TSObject>(getStorageParamsFilePath(resourceName), { throwIfNotExist: false }) || {}; -} - -export function writeToStorageParamsFile(resourceName: string, storageParams: $TSAny) { - JSONUtilities.writeJson(getStorageParamsFilePath(resourceName), storageParams); -} - -function getStorageParamsFilePath(resourceName: string) { - const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, categoryName, resourceName); - return path.join(resourceDirPath, storageParamsFilename); -} diff --git a/packages/amplify-category-storage/tsconfig.json b/packages/amplify-category-storage/tsconfig.json index ccaf04df74f..7439532badf 100644 --- a/packages/amplify-category-storage/tsconfig.json +++ b/packages/amplify-category-storage/tsconfig.json @@ -12,6 +12,7 @@ ], "references": [ { "path": "../amplify-cli-core" }, + { "path": "../amplify-headless-interface" }, { "path": "../amplify-prompts" }, { "path": "../amplify-util-import" } ] diff --git a/packages/amplify-cli-core/src/__tests__/featureFlags.test.ts b/packages/amplify-cli-core/src/__tests__/featureFlags.test.ts index 9b8edb34a84..35437439aad 100644 --- a/packages/amplify-cli-core/src/__tests__/featureFlags.test.ts +++ b/packages/amplify-cli-core/src/__tests__/featureFlags.test.ts @@ -48,7 +48,7 @@ describe('feature flags', () => { await fs.copyFile(templateConfigFileName, projectConfigFileName); - await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, undefined, getTestFlags()); + await FeatureFlags.initialize({ getCurrentEnvName: () => 'dev' } as unknown as CLIEnvironmentProvider, undefined, getTestFlags()); await FeatureFlags.ensureDefaultFeatureFlags(true); @@ -78,7 +78,7 @@ describe('feature flags', () => { await fs.copyFile(templateConfigFileName, projectConfigFileName); - await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, undefined, getTestFlags()); + await FeatureFlags.initialize({ getCurrentEnvName: () => 'dev' } as unknown as CLIEnvironmentProvider, undefined, getTestFlags()); await FeatureFlags.ensureDefaultFeatureFlags(true); const originalConfig = (await fs.readFile(templateConfigFileName)).toString(); @@ -103,7 +103,7 @@ describe('feature flags', () => { await fs.mkdirs(path.dirname(projectConfigFileName)); - await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, undefined, getTestFlags()); + await FeatureFlags.initialize({ getCurrentEnvName: () => 'dev' } as unknown as CLIEnvironmentProvider, undefined, getTestFlags()); await FeatureFlags.ensureDefaultFeatureFlags(true); const createdConfig = (await fs.readFile(projectConfigFileName)).toString(); @@ -131,7 +131,7 @@ describe('feature flags', () => { await fs.copyFile(templateConfigFileName, projectConfigFileName); - await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider); + await FeatureFlags.initialize({ getCurrentEnvName: () => 'dev' } as unknown as CLIEnvironmentProvider); expect(FeatureFlags.getBoolean('graphQLTransformer.validateTypeNameReservedWords')).toBe(false); } finally { @@ -141,7 +141,7 @@ describe('feature flags', () => { test('missing environmentProvider argument', async () => { await expect(async () => { - await FeatureFlags.initialize((undefined as unknown) as CLIContextEnvironmentProvider, undefined, getTestFlags()); + await FeatureFlags.initialize(undefined as unknown as CLIContextEnvironmentProvider, undefined, getTestFlags()); }).rejects.toThrowError(`'environmentProvider' argument is required`); }); @@ -162,10 +162,10 @@ describe('feature flags', () => { await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); - const transformerVersion = FeatureFlags.getNumber('graphQLTransformer.transformerVersion'); + const customTransformerVersion = FeatureFlags.getNumber('graphQLTransformer.customTransformerVersion'); const isDefaultQueryEnabled = FeatureFlags.getBoolean('keyTransformer.defaultQuery'); - expect(transformerVersion).toBe(4); + expect(customTransformerVersion).toBe(4); expect(isDefaultQueryEnabled).toBe(true); }); @@ -178,12 +178,12 @@ describe('feature flags', () => { process.chdir(tempProjectDir); - await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, true, getTestFlags()); + await FeatureFlags.initialize({ getCurrentEnvName: () => 'dev' } as unknown as CLIEnvironmentProvider, true, getTestFlags()); - const transformerVersion = FeatureFlags.getNumber('graphQLTransformer.transformerVersion'); + const customTransformerVersion = FeatureFlags.getNumber('graphQLTransformer.customTransformerVersion'); const isDefaultQueryEnabled = FeatureFlags.getBoolean('keyTransformer.defaultQuery'); - expect(transformerVersion).toBe(5); + expect(customTransformerVersion).toBe(5); expect(isDefaultQueryEnabled).toBe(true); } finally { rimraf.sync(tempProjectDir); @@ -232,7 +232,7 @@ describe('feature flags', () => { expect(effectiveFlags).toMatchObject({ graphqltransformer: { - transformerversion: 3, + customtransformerversion: 3, }, keytransformer: { defaultquery: false, @@ -260,7 +260,7 @@ describe('feature flags', () => { expect(effectiveFlags).toMatchObject({ graphqltransformer: { - transformerversion: 4, + customtransformerversion: 4, }, keytransformer: { defaultquery: true, @@ -288,7 +288,7 @@ describe('feature flags', () => { expect(effectiveFlags).toMatchObject({ graphqltransformer: { - transformerversion: 5, + customtransformerversion: 5, }, keytransformer: { defaultquery: false, @@ -316,7 +316,7 @@ describe('feature flags', () => { expect(effectiveFlags).toMatchObject({ graphqltransformer: { - transformerversion: 6, + customtransformerversion: 6, }, keytransformer: { defaultquery: true, @@ -401,7 +401,7 @@ describe('feature flags', () => { await expect(async () => { await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); - }).rejects.toThrowError(`Invalid number value: 'invalid' for 'transformerversion' in section 'graphqltransformer'`); + }).rejects.toThrowError(`Invalid number value: 'invalid' for 'customtransformerversion' in section 'graphqltransformer'`); }); test('initialize feature flag provider fail unknown flags', async () => { @@ -425,7 +425,7 @@ describe('feature flags', () => { message: 'Invalid feature flag configuration', name: 'JSONValidationError', unknownFlags: ['graphqltransformer.foo', 'graphqltransformer.bar'], - otherErrors: ['graphqltransformer.transformerversion: should be number'], + otherErrors: ['graphqltransformer.customtransformerversion: should be number'], }); }); @@ -433,7 +433,7 @@ describe('feature flags', () => { return { graphQLTransformer: [ { - name: 'transformerVersion', + name: 'customTransformerVersion', type: 'number', defaultValueForExistingProjects: 4, defaultValueForNewProjects: 5, @@ -533,7 +533,7 @@ describe('feature flags', () => { test('error with empty field returns unknown', () => { expect(() => { - throw new EnvVarFormatError((undefined as unknown) as string); + throw new EnvVarFormatError(undefined as unknown as string); }).toThrowError(`Invalid variable name format: ''`); }); @@ -594,13 +594,13 @@ describe('feature flags', () => { expect(features).toMatchObject({ project: { graphqltransformer: { - transformerversion: 5, + customtransformerversion: 5, }, }, environments: { dev: { graphqltransformer: { - transformerversion: 6, + customtransformerversion: 6, }, }, }, @@ -626,7 +626,7 @@ describe('feature flags', () => { expect(features).toMatchObject({ project: { graphqltransformer: { - transformerversion: 5, + customtransformerversion: 5, }, }, environments: {}, @@ -676,7 +676,7 @@ describe('feature flags', () => { environments: { dev: { graphqltransformer: { - transformerversion: 6, + customtransformerversion: 6, }, }, }, diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/project-with-features/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/project-with-features/cli.json index 172e0170131..a569518b750 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/project-with-features/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/project-with-features/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": 5 + "customTransformerVersion": 5 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.dev.json index c1e72c0686a..c3e71ec06cd 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.dev.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.dev.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 6 + "customTransformerVersion": 6 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.json index 172e0170131..a569518b750 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": 5 + "customTransformerVersion": 5 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.json index c1ab57e9bf9..a924c38b54a 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": 3 + "customTransformerVersion": 3 }, "keyTransformer": { "defaultQuery": false diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.prod.json index 90370515ce8..0928dfc16fd 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.prod.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.prod.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 10 + "customTransformerVersion": 10 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.dev.json index 30bdc1bb82d..795b5264b69 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.dev.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.dev.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 4 + "customTransformerVersion": 4 }, "keyTransformer": { "defaultQuery": true diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.json index c1ab57e9bf9..a924c38b54a 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": 3 + "customTransformerVersion": 3 }, "keyTransformer": { "defaultQuery": false diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.prod.json index 90370515ce8..0928dfc16fd 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.prod.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.prod.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 10 + "customTransformerVersion": 10 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/.env b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/.env index 9cb18771c40..144a30f4904 100755 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/.env +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/.env @@ -1,4 +1,4 @@ -AMPLIFYCLI_GRAPHQLTRANSFORMER__TRANSFORMERVERSION=5 -#AMPLIFYCLI-DEV_GRAPHQLTRANSFORMER__TRANSFORMERVERSION=6 +AMPLIFYCLI_GRAPHQLTRANSFORMER__CUSTOMTRANSFORMERVERSION=5 +#AMPLIFYCLI-DEV_GRAPHQLTRANSFORMER__CUSTOMTRANSFORMERVERSION=6 AMPLIFYCLI_KEYTRANSFORMER__defaultquery=false #AMPLIFYCLI-DEV_KEYTRANSFORMER__defaultquery=true diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.dev.json index 30bdc1bb82d..795b5264b69 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.dev.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.dev.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 4 + "customTransformerVersion": 4 }, "keyTransformer": { "defaultQuery": true diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.json index c1ab57e9bf9..a924c38b54a 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": 3 + "customTransformerVersion": 3 }, "keyTransformer": { "defaultQuery": false diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.prod.json index 90370515ce8..0928dfc16fd 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.prod.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.prod.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 10 + "customTransformerVersion": 10 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/.env b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/.env index 9bc959d3f2c..c8950b84e09 100755 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/.env +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/.env @@ -1,4 +1,4 @@ -AMPLIFYCLI_GRAPHQLTRANSFORMER__TRANSFORMERVERSION=5 -AMPLIFYCLI-DEV_GRAPHQLTRANSFORMER__TRANSFORMERVERSION=6 +AMPLIFYCLI_GRAPHQLTRANSFORMER__CUSTOMTRANSFORMERVERSION=5 +AMPLIFYCLI-DEV_GRAPHQLTRANSFORMER__CUSTOMTRANSFORMERVERSION=6 AMPLIFYCLI_KEYTRANSFORMER__defaultquery=false AMPLIFYCLI-DEV_KEYTRANSFORMER__defaultquery=true diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.dev.json index 30bdc1bb82d..795b5264b69 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.dev.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.dev.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 4 + "customTransformerVersion": 4 }, "keyTransformer": { "defaultQuery": true diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.json index c1ab57e9bf9..a924c38b54a 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": 3 + "customTransformerVersion": 3 }, "keyTransformer": { "defaultQuery": false diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.prod.json index 90370515ce8..0928dfc16fd 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.prod.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.prod.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 10 + "customTransformerVersion": 10 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-env-number/.env b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-env-number/.env index 9267ed98b07..4d15c2cd256 100755 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-env-number/.env +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-env-number/.env @@ -1 +1 @@ -AMPLIFYCLI_GRAPHQLTRANSFORMER__TRANSFORMERVERSION=invalid +AMPLIFYCLI_GRAPHQLTRANSFORMER__CUSTOMTRANSFORMERVERSION=invalid diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-unknown-flag/amplify/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-unknown-flag/amplify/cli.json index 034acde6a76..32125b39413 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-unknown-flag/amplify/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-unknown-flag/amplify/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": "it-is-a-string", + "customTransformerVersion": "it-is-a-string", "foo": "unknown", "bar": "unknown" } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/.env b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/.env index 623304b2aaf..6cab13933d5 100755 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/.env +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/.env @@ -1,4 +1,4 @@ -#AMPLIFYCLI_GRAPHQLTRANSFORMER__TRANSFORMERVERSION=5 -#AMPLIFYCLI-DEV_GRAPHQLTRANSFORMER__TRANSFORMERVERSION=6 +#AMPLIFYCLI_GRAPHQLTRANSFORMER__CUSTOMTRANSFORMERVERSION=5 +#AMPLIFYCLI-DEV_GRAPHQLTRANSFORMER__CUSTOMTRANSFORMERVERSION=6 #AMPLIFYCLI_KEYTRANSFORMER__defaultquery=false #AMPLIFYCLI-DEV_KEYTRANSFORMER__defaultquery=true diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.dev.json index 30bdc1bb82d..795b5264b69 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.dev.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.dev.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 4 + "customTransformerVersion": 4 }, "keyTransformer": { "defaultQuery": true diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.json index c1ab57e9bf9..a924c38b54a 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": 3 + "customTransformerVersion": 3 }, "keyTransformer": { "defaultQuery": false diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.prod.json index 90370515ce8..0928dfc16fd 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.prod.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.prod.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 10 + "customTransformerVersion": 10 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-env/amplify/cli.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-env/amplify/cli.json index 172e0170131..a569518b750 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-env/amplify/cli.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-env/amplify/cli.json @@ -4,7 +4,7 @@ }, "features": { "graphQLTransformer": { - "transformerVersion": 5 + "customTransformerVersion": 5 } } } diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-project/amplify/cli.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-project/amplify/cli.dev.json index c1e72c0686a..c3e71ec06cd 100644 --- a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-project/amplify/cli.dev.json +++ b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-project/amplify/cli.dev.json @@ -1,7 +1,7 @@ { "features": { "graphQLTransformer": { - "transformerVersion": 6 + "customTransformerVersion": 6 } } } diff --git a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts index 5b1fd4180c2..f37cafd712b 100644 --- a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts +++ b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts @@ -551,6 +551,12 @@ export class FeatureFlags { defaultValueForExistingProjects: false, defaultValueForNewProjects: true, }, + { + name: 'transformerVersion', + type: 'number', + defaultValueForExistingProjects: 1, + defaultValueForNewProjects: 1, + }, ]); this.registerFlag('frontend-ios', [ diff --git a/packages/amplify-cli-core/src/index.ts b/packages/amplify-cli-core/src/index.ts index bf8ab8af206..d964af1cb6d 100644 --- a/packages/amplify-cli-core/src/index.ts +++ b/packages/amplify-cli-core/src/index.ts @@ -25,10 +25,9 @@ export * from './cliRemoveResourcePrompt'; export * from './cliViewAPI'; export * from './overrides-manager'; export * from './hooks'; -export * from './cliViewAPI'; -export * from './customPoliciesUtils' export * from './cliConstants'; export * from './category-interfaces'; +export * from './customPoliciesUtils'; // Temporary types until we can finish full type definition across the whole CLI @@ -257,6 +256,7 @@ interface AmplifyToolkit { category?: string, resourceName?: string, filteredResources?: { category: string; resourceName: string }[], + rebuild?: boolean, ) => $TSAny; storeCurrentCloudBackend: () => $TSAny; readJsonFile: (fileName: string) => $TSAny; diff --git a/packages/amplify-cli-core/src/state-manager/pathManager.ts b/packages/amplify-cli-core/src/state-manager/pathManager.ts index cbbdefefc74..74f5bd2490e 100644 --- a/packages/amplify-cli-core/src/state-manager/pathManager.ts +++ b/packages/amplify-cli-core/src/state-manager/pathManager.ts @@ -43,6 +43,7 @@ export const PathConstants = { HooksShellSampleFileName: 'post-push.sh.sample', HooksJsSampleFileName: 'pre-push.js.sample', HooksReadmeFileName: 'hooks-readme.md', + OverrideFileName: 'override.ts', LocalEnvFileName: 'local-env-info.json', LocalAWSInfoFileName: 'local-aws-info.json', diff --git a/packages/amplify-cli/package.json b/packages/amplify-cli/package.json index d84d7c93e6c..4bf3cb25248 100644 --- a/packages/amplify-cli/package.json +++ b/packages/amplify-cli/package.json @@ -98,7 +98,7 @@ "promise-sequential": "^1.1.1", "semver": "^7.3.5", "tar-fs": "^2.1.1", - "update-notifier": "^4.1.0", + "update-notifier": "^5.1.0", "uuid": "^3.4.0", "which": "^2.0.2", "winston": "^3.3.3" @@ -116,7 +116,7 @@ "@types/progress": "^2.0.3", "@types/promise-sequential": "^1.1.0", "@types/tar-fs": "^2.0.0", - "@types/update-notifier": "^4.1.0", + "@types/update-notifier": "^5.1.0", "amplify-function-plugin-interface": "1.9.1", "nock": "^12.0.3" }, diff --git a/packages/amplify-cli/src/__tests__/commands/status.test.ts b/packages/amplify-cli/src/__tests__/commands/status.test.ts index d22a798a354..30f9dc59605 100644 --- a/packages/amplify-cli/src/__tests__/commands/status.test.ts +++ b/packages/amplify-cli/src/__tests__/commands/status.test.ts @@ -1,5 +1,3 @@ -import { UnknownArgumentError } from 'amplify-cli-core'; - describe('amplify status: ', () => { const { run } = require('../../commands/status'); const runStatusCmd = run; @@ -11,17 +9,10 @@ describe('amplify status: ', () => { }); it('status run method should call context.amplify.showStatusTable', async () => { - const cliInput = { - command: 'status', - subCommands: [], - options: { - verbose: true, - }, - }; - const mockContextNoCLArgs = { amplify: { showStatusTable: jest.fn(), + showGlobalSandboxModeWarning: jest.fn(), showHelpfulProviderLinks: jest.fn(), getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: mockPath }), }, @@ -43,6 +34,7 @@ describe('amplify status: ', () => { const mockContextWithVerboseOptionAndCLArgs = { amplify: { showStatusTable: jest.fn(), + showGlobalSandboxModeWarning: jest.fn(), showHelpfulProviderLinks: jest.fn(), getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }), }, @@ -61,6 +53,7 @@ describe('amplify status: ', () => { const mockContextWithVerboseOptionWithCategoriesAndCLArgs = { amplify: { showStatusTable: jest.fn(), + showGlobalSandboxModeWarning: jest.fn(), showHelpfulProviderLinks: jest.fn(), getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }), }, @@ -82,6 +75,7 @@ describe('amplify status: ', () => { const mockContextWithHelpSubcommandAndCLArgs = { amplify: { showStatusTable: jest.fn(), + showGlobalSandboxModeWarning: jest.fn(), showHelpfulProviderLinks: jest.fn(), getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }), }, @@ -94,5 +88,4 @@ describe('amplify status: ', () => { //TBD: to move ViewResourceTableParams into a separate file for mocking instance functions. expect(mockContextWithHelpSubcommandAndCLArgs.amplify.showStatusTable.mock.calls.length).toBe(0); }); - }); diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-all-category-pluginInfos.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-all-category-pluginInfos.test.ts index 4a1c47467df..a11095ed69d 100644 --- a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-all-category-pluginInfos.test.ts +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-all-category-pluginInfos.test.ts @@ -17,7 +17,7 @@ test('getAllCategoryPluginInfo', () => { const mockInput = new Input(mockProcessArgv); const mockContext = constructContext(mockPluginPlatform, mockInput); - const categoryPluginInfoList = (getAllCategoryPluginInfo(mockContext) as unknown) as PluginCollection; + const categoryPluginInfoList = getAllCategoryPluginInfo(mockContext) as unknown as PluginCollection; expect(categoryPluginInfoList.hosting).toBeDefined(); expect(Object.keys(categoryPluginInfoList.hosting).length).toEqual(2); }); diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-project-details.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-project-details.test.ts new file mode 100644 index 00000000000..3db2fd3fdea --- /dev/null +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-project-details.test.ts @@ -0,0 +1,78 @@ +import { getProjectDetails } from '../../../extensions/amplify-helpers/get-project-details'; +import { stateManager } from 'amplify-cli-core'; + +const stateManagerMock = stateManager as jest.Mocked; + +jest.mock('../../../extensions/amplify-helpers/get-env-info', () => ({ + getEnvInfo: jest.fn().mockReturnValue({ envName: 'test' }), +})); + +jest.mock('amplify-cli-core', () => ({ + stateManager: { + getLocalEnvInfo: jest.fn(), + getProjectConfig: jest.fn(), + metaFileExists: jest.fn(), + getMeta: jest.fn().mockReturnValue({ + providers: { + awscloudformation: {}, + }, + }), + teamProviderInfoExists: jest.fn(), + getTeamProviderInfo: jest.fn().mockReturnValue({ production: 'test', develop: 'test', staging: 'test' }), + }, +})); + +const mockProjectConfig = { + projectName: 'mockProjectName', + version: '2.0', + frontend: 'javascript', + javascript: { + framework: 'none', + config: { + SourceDir: 'src', + DistributionDir: 'dist', + BuildCommand: 'npm run-script build', + StartCommand: 'npm run-script start', + }, + }, + providers: ['awscloudformation'], +}; + +describe('getProjectDetails', () => { + beforeEach(() => { + stateManagerMock.getProjectConfig.mockReturnValue(mockProjectConfig); + }); + it('should return correctly if there is not amplify-meta.json and team-provider.json', () => { + stateManagerMock.metaFileExists.mockReturnValue(false); + stateManagerMock.teamProviderInfoExists.mockReturnValue(false); + + const response = getProjectDetails(); + expect(response).toStrictEqual({ + amplifyMeta: {}, + projectConfig: mockProjectConfig, + localEnvInfo: { + envName: 'test', + }, + teamProviderInfo: {}, + }); + }); + it('should return correctly if amplify-meta.json and team-provider-info.json exist', () => { + stateManagerMock.metaFileExists.mockReturnValue(true); + stateManagerMock.teamProviderInfoExists.mockReturnValue(true); + const response = getProjectDetails(); + expect(stateManagerMock.getMeta.mock.calls.length).toBe(1); + expect(stateManagerMock.getTeamProviderInfo.mock.calls.length).toBe(1); + expect(response).toStrictEqual({ + amplifyMeta: { + providers: { + awscloudformation: {}, + }, + }, + projectConfig: mockProjectConfig, + localEnvInfo: { + envName: 'test', + }, + teamProviderInfo: { develop: 'test', production: 'test', staging: 'test' }, + }); + }); +}); diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/remove-resource.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/remove-resource.test.ts index ef36a1eb47b..e2f1f8e2781 100644 --- a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/remove-resource.test.ts +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/remove-resource.test.ts @@ -1,9 +1,10 @@ +import { stateManager, exitOnNextTick, ResourceDoesNotExistError } from 'amplify-cli-core'; +import { printer } from 'amplify-prompts'; +import * as inquirer from 'inquirer'; import * as path from 'path'; +import { removeResourceParameters } from '../../../extensions/amplify-helpers/envResourceParams'; import { removeResource, forceRemoveResource } from '../../../extensions/amplify-helpers/remove-resource'; -import { stateManager, exitOnNextTick, ResourceDoesNotExistError, MissingParametersError } from 'amplify-cli-core'; -import * as inquirer from 'inquirer'; import { updateBackendConfigAfterResourceRemove } from '../../../extensions/amplify-helpers/update-backend-config'; -import { removeResourceParameters } from '../../../extensions/amplify-helpers/envResourceParams'; jest.mock('../../../extensions/amplify-helpers/envResourceParams'); jest.mock('../../../extensions/amplify-helpers/update-backend-config'); @@ -12,7 +13,6 @@ jest.mock('inquirer', () => ({ prompt: jest.fn().mockResolvedValue({ resource: 'lambda1' }), })); -const backendDirPathStub = 'backendDirPath'; jest.mock('amplify-cli-core', () => ({ ...(jest.requireActual('amplify-cli-core') as {}), stateManager: { @@ -23,7 +23,7 @@ jest.mock('amplify-cli-core', () => ({ setTeamProviderInfo: jest.fn(), }, pathManager: { - getBackendDirPath: jest.fn(() => backendDirPathStub), + getResourceDirectoryPath: jest.fn((_, categoryName, resourceName) => path.join('backendDirPath', categoryName, resourceName)), }, exitOnNextTick: jest.fn().mockImplementation(() => { throw 'process.exit mock'; @@ -33,7 +33,7 @@ jest.mock('amplify-cli-core', () => ({ const stateManagerMock = stateManager as jest.Mocked; const inquirerMock = inquirer as jest.Mocked; -jest.mock('amplify-cli-core'); +jest.mock('amplify-prompts'); describe('remove-resource', () => { let context; @@ -46,11 +46,6 @@ describe('remove-resource', () => { filesystem: { remove: jest.fn(), }, - print: { - info: jest.fn(), - error: jest.fn(), - success: jest.fn(), - }, usageData: { emitError: jest.fn(), }, @@ -118,7 +113,7 @@ describe('remove-resource', () => { it('emit an error when the resource of the specified category does not exist', async () => { await expect(removeResource(context as any, 'api', 'test')).rejects.toBe('process.exit mock'); - expect(context.print.error).toBeCalledWith('No resources added for this category'); + expect(printer.error).toBeCalledWith('No resources added for this category'); expect(context.usageData.emitError).toBeCalledWith(new ResourceDoesNotExistError()); expect(exitOnNextTick).toBeCalledWith(1); }); @@ -127,7 +122,7 @@ describe('remove-resource', () => { await expect(removeResource(context as any, 'function', 'lambda2')).rejects.toBe('process.exit mock'); const errorMessage = 'Resource lambda2 has not been added to function'; - expect(context.print.error).toBeCalledWith(errorMessage); + expect(printer.error).toBeCalledWith(errorMessage); expect(context.usageData.emitError).toBeCalledWith(new ResourceDoesNotExistError(errorMessage)); expect(exitOnNextTick).toBeCalledWith(1); }); @@ -193,7 +188,7 @@ describe('remove-resource', () => { }, ]); - expect(context.print.info).toBeCalledWith('lambdaLayer deletion info message'); + expect(printer.info).toBeCalledWith('lambdaLayer deletion info message'); }); it('remove resource when the resource of the specified resource name does exist', async () => { @@ -219,7 +214,7 @@ describe('remove-resource', () => { expect(context.filesystem.remove).toBeCalledWith(path.join('backendDirPath', 'function', 'lambda1')); expect(removeResourceParameters).toBeCalledWith(context, 'function', 'lambda1'); expect(updateBackendConfigAfterResourceRemove).toBeCalledWith('function', 'lambda1'); - expect(context.print.success).toBeCalledWith('Successfully removed resource'); + expect(printer.success).toBeCalledWith('Successfully removed resource'); }); it('not remove resource when confirm prompt returns false', async () => { @@ -236,9 +231,9 @@ describe('remove-resource', () => { it('throw an error when the dependent resources has a specified resource', async () => { await expect(removeResource(context as any, 'function', 'lambdaLayer1')).resolves.toBeUndefined(); - expect(context.print.error).toBeCalledWith('Resource cannot be removed because it has a dependency on another resource'); - expect(context.print.error).toBeCalledWith('Dependency: Lambda - lambda1'); - expect(context.print.error).toBeCalledWith('An error occurred when removing the resources from the local directory'); + expect(printer.error).toBeCalledWith('Resource cannot be removed because it has a dependency on another resource'); + expect(printer.error).toBeCalledWith('Dependency: Lambda - lambda1'); + expect(printer.error).toBeCalledWith('An error occurred when removing the resources from the local directory'); expect(context.usageData.emitError).toBeCalledWith( new Error('Resource cannot be removed because it has a dependency on another resource'), ); @@ -288,7 +283,7 @@ describe('remove-resource', () => { expect(context.filesystem.remove).toBeCalledWith('backendDirPath/function/lambdaLayer1'); expect(removeResourceParameters).toBeCalledWith(context, 'function', 'lambdaLayer1'); expect(updateBackendConfigAfterResourceRemove).toBeCalledWith('function', 'lambdaLayer1'); - expect(context.print.success).toBeCalledWith('Successfully removed resource'); + expect(printer.success).toBeCalledWith('Successfully removed resource'); }); it('emit an error when the resource of the specified category does not exist', async () => { @@ -296,19 +291,11 @@ describe('remove-resource', () => { forceRemoveResource(context as any, 'hosting', 'S3AndCloudFront', 'backendDirPath/hosting/S3AndCloudFront'), ).rejects.toBe('process.exit mock'); - expect(context.print.error).toBeCalledWith('No resources added for this category'); + expect(printer.error).toBeCalledWith('No resources added for this category'); expect(context.usageData.emitError).toBeCalledWith(new ResourceDoesNotExistError()); expect(exitOnNextTick).toBeCalledWith(1); }); - it('emit an error when parameters missing', async () => { - await expect(forceRemoveResource(context as any, 'function', 'lambdaLayer1', null)).rejects.toBe('process.exit mock'); - - expect(context.print.error).toBeCalledWith('Unable to force removal of resource: missing parameters'); - expect(context.usageData.emitError).toBeCalledWith(new MissingParametersError()); - expect(exitOnNextTick).toBeCalledWith(1); - }); - it('returns undefined when error deleting files', async () => { context.filesystem.remove.mockImplementation(() => { throw new Error('mock remove file error'); @@ -316,7 +303,7 @@ describe('remove-resource', () => { await expect( forceRemoveResource(context as any, 'function', 'lambdaLayer1', 'backendDirPath/function/lambdaLayer1'), ).resolves.toBeUndefined(); - expect(context.print.error).toBeCalledWith('Unable to force removal of resource: error deleting files'); + expect(printer.error).toBeCalledWith('Unable to force removal of resource: error deleting files'); }); }); }); diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta-2.json b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta-2.json new file mode 100644 index 00000000000..9bd3f8293be --- /dev/null +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta-2.json @@ -0,0 +1,25 @@ +{ + "api": { + "ampapp": { + "service": "AppSync", + "providerPlugin": "awscloudformation", + "output": { + "authConfig": { + "defaultAuthentication": { + "authenticationType": "AWS_IAM" + }, + "additionalAuthenticationProviders": [ + { + "authenticationType": "API_KEY", + "apiKeyConfig": { + "apiKeyExpirationDays": 2, + "apiKeyExpirationDate": "2021-08-20T20:38:07.585Z", + "description": "" + } + } + ] + } + } + } + } +} diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta-3.json b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta-3.json new file mode 100644 index 00000000000..7aa92695860 --- /dev/null +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta-3.json @@ -0,0 +1,16 @@ +{ + "api": { + "ampapp": { + "service": "AppSync", + "providerPlugin": "awscloudformation", + "output": { + "authConfig": { + "defaultAuthentication": { + "authenticationType": "AWS_IAM" + }, + "additionalAuthenticationProviders": [] + } + } + } + } +} diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta.json b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta.json new file mode 100644 index 00000000000..cb9a35d4025 --- /dev/null +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/testData/mockLocalCloud/amplify-meta.json @@ -0,0 +1,28 @@ +{ + "api": { + "ampapp": { + "service": "AppSync", + "providerPlugin": "awscloudformation", + "output": { + "authConfig": { + "defaultAuthentication": { + "authenticationType": "AWS_IAM" + }, + "additionalAuthenticationProviders": [ + { + "authenticationType": "API_KEY", + "apiKeyConfig": { + "apiKeyExpirationDays": 2, + "apiKeyExpirationDate": "2021-08-20T20:38:07.585Z", + "description": "" + } + } + ] + }, + "globalSandboxModeConfig": { + "env": "dev" + } + } + } + } +} diff --git a/packages/amplify-cli/src/__tests__/plugin-helpers/compare-plugins.test.ts b/packages/amplify-cli/src/__tests__/plugin-helpers/compare-plugins.test.ts new file mode 100644 index 00000000000..cce8a2543fb --- /dev/null +++ b/packages/amplify-cli/src/__tests__/plugin-helpers/compare-plugins.test.ts @@ -0,0 +1,56 @@ +import * as path from 'path'; +import { PluginInfo } from '../../domain/plugin-info'; +import { PluginManifest } from '../../domain/plugin-manifest'; +import { twoPluginsAreTheSame } from '../../plugin-helpers/compare-plugins'; + +describe('compare-plugins', () => { + describe('twoPluginsAreTheSame', () => { + it('returns true when packageLocation is same', () => { + const p1 = new PluginInfo( + 'amplify-util-plugin', + '1.0.0', + path.join('path', 'to', 'package@1.0.0'), + new PluginManifest('util-plugin', 'util'), + ); + const p2 = new PluginInfo( + 'amplify-util-plugin', + '1.0.0', + path.join('path', 'to', 'package@1.0.0'), + new PluginManifest('util-plugin', 'util'), + ); + expect(twoPluginsAreTheSame(p1, p2)).toBe(true); + }); + + it('returns true when both packageName and packageVersion is same', () => { + const p1 = new PluginInfo( + 'amplify-util-plugin', + '1.0.0', + path.join('path', 'to', 'package1'), + new PluginManifest('util-plugin', 'util'), + ); + const p2 = new PluginInfo( + 'amplify-util-plugin', + '1.0.0', + path.join('path', 'to', 'package2'), + new PluginManifest('util-plugin', 'util'), + ); + expect(twoPluginsAreTheSame(p1, p2)).toBe(true); + }); + + it('returns false when packageLocation, packageName and packageVersion is not same', () => { + const p1 = new PluginInfo( + 'amplify-util-plugin', + '1.0.0', + path.join('path', 'to', 'package1'), + new PluginManifest('util-plugin', 'util'), + ); + const p2 = new PluginInfo( + 'amplify-util-plugin', + '2.0.0', + path.join('path', 'to', 'package2'), + new PluginManifest('util-plugin', 'util'), + ); + expect(twoPluginsAreTheSame(p1, p2)).toBe(false); + }); + }); +}); diff --git a/packages/amplify-cli/src/__tests__/plugin-helpers/platform-health-check.test.ts b/packages/amplify-cli/src/__tests__/plugin-helpers/platform-health-check.test.ts new file mode 100644 index 00000000000..a7b88024f8e --- /dev/null +++ b/packages/amplify-cli/src/__tests__/plugin-helpers/platform-health-check.test.ts @@ -0,0 +1,167 @@ +import { JSONUtilities } from '../../../../amplify-cli-core/lib'; +import { PluginInfo } from '../../domain/plugin-info'; +import { PluginManifest } from '../../domain/plugin-manifest'; +import { PluginPlatform } from '../../domain/plugin-platform'; +import { checkPlatformHealth, getOfficialPlugins } from '../../plugin-helpers/platform-health-check'; + +jest.mock('chalk', () => ({ + yellow: jest.fn().mockImplementation(input => input), +})); + +const corePackageJson = { + name: '@aws-amplify/cli', + version: '5.4.0', + amplify: { + officialPlugins: { + core: { + name: 'core', + type: 'core', + packageName: '@aws-amplify/cli', + }, + api: { + name: 'api', + type: 'category', + packageName: 'amplify-category-api', + }, + hosting: [ + { + name: 'hosting', + type: 'category', + packageName: 'amplify-category-hosting', + }, + { + name: 'hosting', + type: 'category', + packageName: 'amplify-console-hosting', + }, + ], + codegen: { + name: 'codegen', + type: 'util', + packageName: 'amplify-codegen', + }, + }, + }, + dependencies: { + 'amplify-category-api': '2.31.20', + 'amplify-category-hosting': '2.7.18', + 'amplify-codegen': '^2.23.1', + 'amplify-console-hosting': '1.9.9', + 'amplify-container-hosting': '1.3.20', + }, +}; + +jest.mock('amplify-cli-core', () => ({ + JSONUtilities: { + readJson: jest.fn(), + }, +})); + +describe('platform-health-check', () => { + beforeAll(() => { + const JSONUtilitiesMock = JSONUtilities as jest.Mocked; + JSONUtilitiesMock.readJson.mockReturnValue(corePackageJson); + }); + + describe('getOfficialPlugins', () => { + it('returns official plugin descriptions', () => { + expect(getOfficialPlugins()).toEqual({ + core: { + name: 'core', + packageName: '@aws-amplify/cli', + packageVersion: '5.4.0', + type: 'core', + }, + api: { + name: 'api', + packageName: 'amplify-category-api', + packageVersion: '2.31.20', + type: 'category', + }, + codegen: { + name: 'codegen', + packageName: 'amplify-codegen', + packageVersion: '^2.23.1', + type: 'util', + }, + hosting: [ + { + name: 'hosting', + packageName: 'amplify-category-hosting', + packageVersion: '2.7.18', + type: 'category', + }, + { + name: 'hosting', + packageName: 'amplify-console-hosting', + packageVersion: '1.9.9', + type: 'category', + }, + ], + }); + }); + }); + + describe('checkPlatformHealth', () => { + beforeEach(() => { + console.log = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('returns true when every official plugins is active and match plugin description', async () => { + const pluginPlatform: PluginPlatform = new PluginPlatform(); + pluginPlatform.plugins = { + core: [new PluginInfo('@aws-amplify/cli', '5.4.0', '', new PluginManifest('core', 'core'))], + hosting: [ + new PluginInfo('amplify-category-hosting', '2.7.18', '', new PluginManifest('hosting', 'category')), + new PluginInfo('amplify-console-hosting', '1.9.9', '', new PluginManifest('hosting', 'category')), + ], + codegen: [new PluginInfo('amplify-codegen', '2.27.0', '', new PluginManifest('codegen', 'util'))], + api: [new PluginInfo('amplify-category-api', '2.31.20', '', new PluginManifest('api', 'category'))], + }; + expect(await checkPlatformHealth(pluginPlatform)).toBe(true); + }); + + it('returns false when mismatch plugins exists', async () => { + const pluginPlatform: PluginPlatform = new PluginPlatform(); + pluginPlatform.plugins = { + core: [new PluginInfo('@aws-amplify/cli', '5.4.0', '', new PluginManifest('core', 'core'))], + hosting: [ + new PluginInfo('amplify-category-hosting', '2.7.18', '', new PluginManifest('hosting', 'category')), + new PluginInfo('amplify-console-hosting', '1.9.9', '', new PluginManifest('hosting', 'category')), + ], + codegen: [ + // version mismatch + new PluginInfo('amplify-codegen', '1.30.0', '', new PluginManifest('codegen', 'util')), + ], + api: [new PluginInfo('amplify-category-api', '2.31.20', '', new PluginManifest('api', 'category'))], + }; + + expect(await checkPlatformHealth(pluginPlatform)).toBe(false); + expect(console.log).toBeCalledWith('The following official plugins have mismatched packages:'); + expect(console.log).toBeCalledWith('Expected:'); + expect(console.log).toBeCalledWith(' codegen: util | amplify-codegen@^2.23.1'); + }); + + it('returns false when missing or inactive plugins exists', async () => { + const pluginPlatform: PluginPlatform = new PluginPlatform(); + pluginPlatform.plugins = { + core: [new PluginInfo('@aws-amplify/cli', '5.4.0', '', new PluginManifest('core', 'core'))], + hosting: [ + new PluginInfo('amplify-category-hosting', '2.7.18', '', new PluginManifest('hosting', 'category')), + // missing 'amplify-console-hosting' + ], + codegen: [new PluginInfo('amplify-codegen', '2.27.0', '', new PluginManifest('codegen', 'util'))], + // missing 'api' + }; + + expect(await checkPlatformHealth(pluginPlatform)).toBe(false); + expect(console.log).toBeCalledWith('The following official plugins are missing or inactive:'); + expect(console.log).toBeCalledWith(' hosting: category | amplify-console-hosting@1.9.9'); + expect(console.log).toBeCalledWith(' api: category | amplify-category-api@2.31.20'); + }); + }); +}); diff --git a/packages/amplify-cli/src/commands/status.ts b/packages/amplify-cli/src/commands/status.ts index 3e11576346a..b9dad7bf397 100644 --- a/packages/amplify-cli/src/commands/status.ts +++ b/packages/amplify-cli/src/commands/status.ts @@ -1,26 +1,27 @@ -import { ViewResourceTableParams, CLIParams, $TSContext } from "amplify-cli-core"; +import { ViewResourceTableParams, CLIParams, $TSContext } from 'amplify-cli-core'; +export const run = async (context: $TSContext) => { + const cliParams: CLIParams = { + cliCommand: context?.input?.command, + cliSubcommands: context?.input?.subCommands, + cliOptions: context?.input?.options, + }; -export const run = async (context : $TSContext) => { - const cliParams:CLIParams = { cliCommand : context?.input?.command, - cliSubcommands: context?.input?.subCommands, - cliOptions : context?.input?.options } - - const view = new ViewResourceTableParams( cliParams ); - if ( context?.input?.subCommands?.includes("help")){ - context.print.info( view.getStyledHelp() ); + const view = new ViewResourceTableParams(cliParams); + if (context?.input?.subCommands?.includes('help')) { + context.print.info(view.getStyledHelp()); } else { try { - await context.amplify.showStatusTable( view ); + await context.amplify.showStatusTable(view); await context.amplify.showHelpfulProviderLinks(context); await showAmplifyConsoleHostingStatus(context); - } catch ( e ){ + } catch (e) { view.logErrorException(e, context); } } }; -async function showAmplifyConsoleHostingStatus( context) { +async function showAmplifyConsoleHostingStatus(context) { const pluginInfo = context.amplify.getCategoryPluginInfo(context, 'hosting', 'amplifyhosting'); if (pluginInfo && pluginInfo.packageLocation) { const { status } = await import(pluginInfo.packageLocation); diff --git a/packages/amplify-cli/src/domain/amplify-toolkit.ts b/packages/amplify-cli/src/domain/amplify-toolkit.ts index 55f1b41b753..046f9fbbb3f 100644 --- a/packages/amplify-cli/src/domain/amplify-toolkit.ts +++ b/packages/amplify-cli/src/domain/amplify-toolkit.ts @@ -261,8 +261,7 @@ export class AmplifyToolkit { } get showStatusTable(): any { - this._showStatusTable = - this._showStatusTable || require(path.join(this._amplifyHelpersDirPath, 'resource-status')).showStatusTable; + this._showStatusTable = this._showStatusTable || require(path.join(this._amplifyHelpersDirPath, 'resource-status')).showStatusTable; return this._showStatusTable; } diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/push-resources.ts b/packages/amplify-cli/src/extensions/amplify-helpers/push-resources.ts index a7f844ccfac..2dd56bfd05d 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/push-resources.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/push-resources.ts @@ -4,15 +4,16 @@ import { onCategoryOutputsChange } from './on-category-outputs-change'; import { initializeEnv } from '../../initialize-env'; import { getProviderPlugins } from './get-provider-plugins'; import { getEnvInfo } from './get-env-info'; -import { EnvironmentDoesNotExistError, exitOnNextTick, stateManager, $TSAny, $TSContext, CustomPoliciesFormatError, IAmplifyResource } from 'amplify-cli-core'; -import { printer } from 'amplify-prompts'; +import { EnvironmentDoesNotExistError, exitOnNextTick, stateManager, $TSAny, $TSContext, IAmplifyResource } from 'amplify-cli-core'; import { getResources } from '../../commands/build-override'; +import { printer } from 'amplify-prompts'; export async function pushResources( context: $TSContext, category?: string, resourceName?: string, filteredResources?: { category: string; resourceName: string }[], + rebuild: boolean = false, ) { if (context.parameters.options['iterative-rollback']) { // validate --iterative-rollback with --force @@ -58,13 +59,14 @@ export async function pushResources( const hasChanges = await showResourceTable(category, resourceName, filteredResources); // no changes detected - if (!hasChanges && !context.exeInfo.forcePush) { + if (!hasChanges && !context.exeInfo.forcePush && !rebuild) { context.print.info('\nNo changes detected'); return context; } - let continueToPush = context.exeInfo && context.exeInfo.inputParams && context.exeInfo.inputParams.yes; + // rebuild has an upstream confirmation prompt so no need to prompt again here + let continueToPush = (context.exeInfo && context.exeInfo.inputParams && context.exeInfo.inputParams.yes) || rebuild; if (!continueToPush) { if (context.exeInfo.iterativeRollback) { @@ -73,20 +75,28 @@ export async function pushResources( continueToPush = await context.amplify.confirmPrompt('Are you sure you want to continue?'); } + let retryPush; if (continueToPush) { - try { - // Get current-cloud-backend's amplify-meta - const currentAmplifyMeta = stateManager.getCurrentMeta(); - - await providersPush(context, category, resourceName, filteredResources); - await onCategoryOutputsChange(context, currentAmplifyMeta); - } catch (err) { - // Handle the errors and print them nicely for the user. - if (!(err instanceof CustomPoliciesFormatError)) { - printer.error(`\n${err.message}`); + do { + retryPush = false; + try { + // Get current-cloud-backend's amplify-meta + const currentAmplifyMeta = stateManager.getCurrentMeta(); + + await providersPush(context, rebuild, category, resourceName, filteredResources); + await onCategoryOutputsChange(context, currentAmplifyMeta); + } catch (err) { + if (await isValidGraphQLAuthError(err.message)) { + retryPush = await handleValidGraphQLAuthError(context, err.message); + } + if (!retryPush) { + // Handle the errors and print them nicely for the user. + printer.blankLine(); + printer.error(err.message); + throw err; + } } - throw err; - } + } while (retryPush); } else { // there's currently no other mechanism to stop the execution of the postPush workflow in this case, so exiting here exitOnNextTick(1); @@ -95,7 +105,68 @@ export async function pushResources( return continueToPush; } -async function providersPush(context: $TSContext, category, resourceName, filteredResources) { +async function isValidGraphQLAuthError(message: string) { + if ( + message === `@auth directive with 'iam' provider found, but the project has no IAM authentication provider configured.` || + message === + `@auth directive with 'userPools' provider found, but the project has no Cognito User Pools authentication provider configured.` || + message === `@auth directive with 'oidc' provider found, but the project has no OPENID_CONNECT authentication provider configured.` || + message === `@auth directive with 'apiKey' provider found, but the project has no API Key authentication provider configured.` || + message === `@auth directive with 'function' provider found, but the project has no Lambda authentication provider configured.` + ) { + return true; + } +} + +async function handleValidGraphQLAuthError(context: $TSContext, message: string) { + if (message === `@auth directive with 'iam' provider found, but the project has no IAM authentication provider configured.`) { + await addGraphQLAuthRequirement(context, 'AWS_IAM'); + return true; + } else if (!context?.parameters?.options?.yes) { + if ( + message === + `@auth directive with 'userPools' provider found, but the project has no Cognito User Pools authentication provider configured.` + ) { + await addGraphQLAuthRequirement(context, 'AMAZON_COGNITO_USER_POOLS'); + return true; + } else if ( + message === `@auth directive with 'oidc' provider found, but the project has no OPENID_CONNECT authentication provider configured.` + ) { + await addGraphQLAuthRequirement(context, 'OPENID_CONNECT'); + return true; + } else if ( + message === `@auth directive with 'apiKey' provider found, but the project has no API Key authentication provider configured.` + ) { + await addGraphQLAuthRequirement(context, 'API_KEY'); + return true; + } else if ( + message === `@auth directive with 'function' provider found, but the project has no Lambda authentication provider configured.` + ) { + await addGraphQLAuthRequirement(context, 'AWS_LAMBDA'); + return true; + } + } + return false; +} + +async function addGraphQLAuthRequirement(context, authType) { + return await context.amplify.invokePluginMethod(context, 'api', undefined, 'addGraphQLAuthorizationMode', [ + context, + { + authType: authType, + printLeadText: true, + authSettings: undefined, + }, + ]); +} + +async function providersPush( + context: $TSContext, + rebuild: boolean = false, + category?: string, + resourceName?: string, + filteredResources?: { category: string; resourceName: string }[], +) { const { providers } = getProjectConfig(); const providerPlugins = getProviderPlugins(context); const providerPromises: (() => Promise<$TSAny>)[] = []; @@ -103,7 +174,7 @@ async function providersPush(context: $TSContext, category, resourceName, filter for (const provider of providers) { const providerModule = require(providerPlugins[provider]); const resourceDefiniton = await context.amplify.getResourceStatus(category, resourceName, provider, filteredResources); - providerPromises.push(providerModule.pushResources(context, resourceDefiniton)); + providerPromises.push(providerModule.pushResources(context, resourceDefiniton, rebuild)); } await Promise.all(providerPromises); diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/remove-resource.ts b/packages/amplify-cli/src/extensions/amplify-helpers/remove-resource.ts index a3af78698f5..4ee3d36c9e0 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/remove-resource.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/remove-resource.ts @@ -1,40 +1,33 @@ import { $TSContext, exitOnNextTick, - MissingParametersError, pathManager, promptConfirmationRemove, ResourceDoesNotExistError, stateManager, } from 'amplify-cli-core'; +import { printer } from 'amplify-prompts'; import * as inquirer from 'inquirer'; import _ from 'lodash'; -import * as path from 'path'; import { removeResourceParameters } from './envResourceParams'; import { updateBackendConfigAfterResourceRemove } from './update-backend-config'; -export async function forceRemoveResource(context: $TSContext, category, name, dir) { +export async function forceRemoveResource(context: $TSContext, category: string, resourceName: string, resourceDir: string) { const amplifyMeta = stateManager.getMeta(); if (!amplifyMeta[category] || Object.keys(amplifyMeta[category]).length === 0) { - context.print.error('No resources added for this category'); + printer.error('No resources added for this category'); await context.usageData.emitError(new ResourceDoesNotExistError()); exitOnNextTick(1); } - if (!context || !category || !name || !dir) { - context.print.error('Unable to force removal of resource: missing parameters'); - await context.usageData.emitError(new MissingParametersError()); - exitOnNextTick(1); - } - - context.print.info(`Removing resource ${name}...`); + printer.info(`Removing resource ${resourceName}...`); let response; try { - response = await deleteResourceFiles(context, category, name, dir, true); + response = await deleteResourceFiles(context, category, resourceName, resourceDir, true); } catch (e) { - context.print.error('Unable to force removal of resource: error deleting files'); + printer.error('Unable to force removal of resource: error deleting files'); } return response; @@ -42,9 +35,9 @@ export async function forceRemoveResource(context: $TSContext, category, name, d export async function removeResource( context: $TSContext, - category, - resourceName, - questionOptions: { serviceSuffix?; serviceDeletionInfo?: {} } = {}, + category: string, + resourceName?: string, + options: { headless?: boolean; serviceSuffix?: { [serviceName: string]: string }; serviceDeletionInfo?: {} } = { headless: false }, resourceNameCallback?: (resourceName: string) => Promise, ) { const amplifyMeta = stateManager.getMeta(); @@ -53,7 +46,7 @@ export async function removeResource( !amplifyMeta[category] || Object.keys(amplifyMeta[category]).filter(r => amplifyMeta[category][r].mobileHubMigrated !== true).length === 0 ) { - context.print.error('No resources added for this category'); + printer.error('No resources added for this category'); await context.usageData.emitError(new ResourceDoesNotExistError()); exitOnNextTick(1); } @@ -65,15 +58,15 @@ export async function removeResource( if (resourceName) { if (!enabledCategoryResources.includes(resourceName)) { const errMessage = `Resource ${resourceName} has not been added to ${category}`; - context.print.error(errMessage); + printer.error(errMessage); await context.usageData.emitError(new ResourceDoesNotExistError(errMessage)); exitOnNextTick(1); } } else { - if (questionOptions.serviceSuffix) { + if (options.serviceSuffix) { enabledCategoryResources = enabledCategoryResources.map(resource => { let service = _.get(amplifyMeta, [category, resource, 'service']); - let suffix = _.get(questionOptions, ['serviceSuffix', service], ''); + let suffix = _.get(options, ['serviceSuffix', service], ''); return { name: `${resource} ${suffix}`, value: resource }; }); } @@ -87,42 +80,44 @@ export async function removeResource( ]; const answer = await inquirer.prompt(question); - resourceName = answer.resource; + resourceName = answer.resource as string; } if (resourceNameCallback) { await resourceNameCallback(resourceName); } - context.print.info(''); - const service = _.get(amplifyMeta, [category, resourceName, 'service']); - const serviceType = _.get(amplifyMeta, [category, resourceName, 'serviceType']); + const resourceDir = pathManager.getResourceDirectoryPath(undefined, category, resourceName); - if (_.has(questionOptions, ['serviceDeletionInfo', service])) { - context.print.info(questionOptions.serviceDeletionInfo![service]); - } + if (options.headless !== true) { + printer.blankLine(); + const service = _.get(amplifyMeta, [category, resourceName, 'service']); + const serviceType = _.get(amplifyMeta, [category, resourceName, 'serviceType']); - const resourceDir = path.normalize(path.join(pathManager.getBackendDirPath(), category, resourceName)); + if (options?.serviceDeletionInfo?.[service]) { + printer.info(options.serviceDeletionInfo[service]); + } - const confirm = await promptConfirmationRemove(context, serviceType); + const confirm = await promptConfirmationRemove(context, serviceType); - if (!confirm) { - return; + if (!confirm) { + return; + } } try { return await deleteResourceFiles(context, category, resourceName, resourceDir); } catch (err) { if (err.stack) { - context.print.info(err.stack); + printer.info(err.stack); } - context.print.error('An error occurred when removing the resources from the local directory'); + printer.error('An error occurred when removing the resources from the local directory'); await context.usageData.emitError(err); process.exitCode = 1; } } -const deleteResourceFiles = async (context, category, resourceName, resourceDir, force?) => { +const deleteResourceFiles = async (context: $TSContext, category: string, resourceName: string, resourceDir: string, force = false) => { const amplifyMeta = stateManager.getMeta(); if (!force) { const { allResources } = await context.amplify.getResourceStatus(); @@ -130,8 +125,8 @@ const deleteResourceFiles = async (context, category, resourceName, resourceDir, if (resourceItem.dependsOn) { resourceItem.dependsOn.forEach(dependsOnItem => { if (dependsOnItem.category === category && dependsOnItem.resourceName === resourceName) { - context.print.error('Resource cannot be removed because it has a dependency on another resource'); - context.print.error(`Dependency: ${resourceItem.service} - ${resourceItem.resourceName}`); + printer.error('Resource cannot be removed because it has a dependency on another resource'); + printer.error(`Dependency: ${resourceItem.service} - ${resourceItem.resourceName}`); const error = new Error('Resource cannot be removed because it has a dependency on another resource'); error.stack = undefined; throw error; @@ -156,6 +151,6 @@ const deleteResourceFiles = async (context, category, resourceName, resourceDir, removeResourceParameters(context, category, resourceName); updateBackendConfigAfterResourceRemove(category, resourceName); - context.print.success('Successfully removed resource'); + printer.success('Successfully removed resource'); return resourceValues; }; diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/trigger-flow.ts b/packages/amplify-cli/src/extensions/amplify-helpers/trigger-flow.ts index bc8aed36cd0..787d81ccf1b 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/trigger-flow.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/trigger-flow.ts @@ -3,7 +3,7 @@ import chalk from 'chalk'; import * as fs from 'fs-extra'; import * as path from 'path'; import _ from 'lodash'; -import { JSONUtilities, $TSAny } from 'amplify-cli-core'; +import { exitOnNextTick, JSONUtilities, $TSAny } from 'amplify-cli-core'; import Separator from 'inquirer/lib/objects/separator'; // keep in sync with ServiceName in amplify-category-function, but probably it will not change @@ -193,10 +193,12 @@ export const updateTrigger = async triggerOptions => { await cleanFunctions(key, values, category, context, targetPath); } - context.print.success('Successfully updated the Lambda function locally'); + context.print.success('Successfully updated the Cognito trigger locally'); return null; - } catch (e) { - throw new Error('Unable to update lambda function'); + } catch (err: $TSAny) { + context.print.error(`Error updating the Cognito trigger: ${err.message}`); + await context.usageData.emitError(err); + exitOnNextTick(1); } }; diff --git a/packages/amplify-cli/src/index.ts b/packages/amplify-cli/src/index.ts index e7ac6a0cdd2..f60113a1bdc 100644 --- a/packages/amplify-cli/src/index.ts +++ b/packages/amplify-cli/src/index.ts @@ -194,7 +194,7 @@ export async function run() { context.usageData.emitInvoke(); // For mobile hub migrated project validate project and command to be executed - if (!ensureMobileHubCommandCompatibility((context as unknown) as $TSContext)) { + if (!ensureMobileHubCommandCompatibility(context as unknown as $TSContext)) { // Double casting until we have properly typed context return 1; } diff --git a/packages/amplify-cli/src/plugin-helpers/platform-health-check.ts b/packages/amplify-cli/src/plugin-helpers/platform-health-check.ts index 83256137c34..69e8b393bbf 100644 --- a/packages/amplify-cli/src/plugin-helpers/platform-health-check.ts +++ b/packages/amplify-cli/src/plugin-helpers/platform-health-check.ts @@ -16,7 +16,7 @@ const indent = ' '; export async function checkPlatformHealth(pluginPlatform: PluginPlatform): Promise { const activePlugins = pluginPlatform.plugins; - const officialPlugins: { [key: string]: PluginDescription | Array } = getOfficialPlugins(); + const officialPlugins = getOfficialPlugins(); const missingOfficialPlugins: Array = []; const mismatchedOfficialPlugins: Array = []; @@ -91,7 +91,7 @@ function isMatching(pluginDescription: PluginDescription, pluginInfo: PluginInfo return result; } -export function getOfficialPlugins() { +export function getOfficialPlugins(): { [key: string]: PluginDescription | Array } { const packageJsonFilePath = path.normalize(path.join(__dirname, '../../package.json')); const packageJson = JSONUtilities.readJson<$TSAny>(packageJsonFilePath); const { officialPlugins } = packageJson.amplify; @@ -99,13 +99,16 @@ export function getOfficialPlugins() { const dependencies: { [key: string]: string } = packageJson.dependencies; Object.keys(officialPlugins).forEach((plugin: string) => { - const { packageName } = officialPlugins[plugin]; - if (dependencies[packageName]) { - const version = dependencies[packageName]; - officialPlugins[plugin].packageVersion = version; - } else { - delete officialPlugins[plugin].packageVersion; - } + const plugins = Array.isArray(officialPlugins[plugin]) ? officialPlugins[plugin] : [officialPlugins[plugin]]; + plugins.forEach(officialPlugin => { + const { packageName } = officialPlugin; + if (dependencies[packageName]) { + const version = dependencies[packageName]; + officialPlugin.packageVersion = version; + } else { + delete officialPlugin.packageVersion; + } + }); }); const coreVersion = packageJson.version; diff --git a/packages/amplify-console-hosting/utils/table-utils.js b/packages/amplify-console-hosting/utils/table-utils.js index fa9b2780dff..db6a40fc7d5 100644 --- a/packages/amplify-console-hosting/utils/table-utils.js +++ b/packages/amplify-console-hosting/utils/table-utils.js @@ -19,9 +19,10 @@ async function generateTableContentForApp(context, appId) { .promise(); for (const branch of branches) { - const { branchName } = branch; + const { branchName, displayName } = branch; + const validDisplayName = displayName || branchName; domainMap[branchName] = []; - domainMap[branchName].push(`https://${branchName}.${appId}.amplifyapp.com`); + domainMap[branchName].push(`https://${validDisplayName}.${appId}.amplifyapp.com`); } } while (nextToken != null); diff --git a/packages/amplify-dynamodb-simulator/__test__/index.test.js b/packages/amplify-dynamodb-simulator/__test__/index.test.js index 26a264c375a..3dba263cbdd 100644 --- a/packages/amplify-dynamodb-simulator/__test__/index.test.js +++ b/packages/amplify-dynamodb-simulator/__test__/index.test.js @@ -39,19 +39,26 @@ describe('emulator operations', () => { const ensureNoDbPath = () => { if (fs.existsSync(dbPath)) { - fs.removeSync(dbPath); + try { + fs.removeSync(dbPath); + } + catch(err) { + console.log(err); + } } }; - beforeEach(ensureNoDbPath); - afterEach(ensureNoDbPath); - let emulators; - beforeEach(() => { + beforeEach(async () => { + ensureNoDbPath(); emulators = []; jest.setTimeout(40 * 1000); }); - afterEach(async () => await Promise.all(emulators.map(emu => emu.terminate()))); + + afterEach(async () => { + await Promise.all(emulators.map(emu => emu.terminate())); + ensureNoDbPath(); + }); it('should support in memory operations', async () => { const emu = await ddbSimulator.launch(); @@ -73,7 +80,7 @@ describe('emulator operations', () => { }) .promise(); await emuOne.terminate(); - + emulators = []; const emuTwo = await ddbSimulator.launch({ dbPath }); emulators.push(emuTwo); const dynamoTwo = await ddbSimulator.getClient(emuTwo); @@ -81,7 +88,6 @@ describe('emulator operations', () => { expect(t).toEqual({ TableNames: ['foo'], }); - emuTwo.terminate(); }); it('should start on specific port', async () => { diff --git a/packages/amplify-e2e-core/src/asciinema-recorder.ts b/packages/amplify-e2e-core/src/asciinema-recorder.ts index 49b8a908058..3e878077fc5 100644 --- a/packages/amplify-e2e-core/src/asciinema-recorder.ts +++ b/packages/amplify-e2e-core/src/asciinema-recorder.ts @@ -58,7 +58,9 @@ export class Recorder { cols: this.cols, rows: this.rows, cwd: this.cwd, - useConpty: false, + shell: true, + // Do not set useConpty. node-pty is smart enough to set it to true only on versions of Windows that support it. + // useConpty: true, ...this.options, }); this.addFrame(this.renderPrompt(this.cwd, this.cmd, this.args)); diff --git a/packages/amplify-e2e-core/src/categories/api.ts b/packages/amplify-e2e-core/src/categories/api.ts index facc6219d3a..c685336c4f5 100644 --- a/packages/amplify-e2e-core/src/categories/api.ts +++ b/packages/amplify-e2e-core/src/categories/api.ts @@ -27,30 +27,27 @@ export function apiGqlCompile(cwd: string, testingWithLatestCodebase: boolean = interface AddApiOptions { apiName: string; + testingWithLatestCodebase: boolean; } const defaultOptions: AddApiOptions = { - apiName: EOL, + apiName: '\r', + testingWithLatestCodebase: true, }; -export function addApiWithoutSchema(cwd: string, opts: Partial = {}) { +export function addApiWithoutSchema(cwd: string, opts: Partial = {}) { const options = _.assign(defaultOptions, opts); return new Promise((resolve, reject) => { - spawn(getCLIPath(), ['add', 'api'], { cwd, stripColors: true }) + spawn(getCLIPath(options.testingWithLatestCodebase), ['add', 'api'], { cwd, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendKeyUp(3) + .sendCarriageReturn() .wait('Provide API name:') .sendLine(options.apiName) - .wait(/.*Choose the default authorization type for the API.*/) - .sendCarriageReturn() - .wait(/.*Enter a description for the API key.*/) - .sendCarriageReturn() - .wait(/.*After how many days from now the API key should expire.*/) - .sendCarriageReturn() - .wait(/.*Do you want to configure advanced settings for the GraphQL API.*/) + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) .sendCarriageReturn() - .wait('Do you have an annotated GraphQL schema?') - .sendConfirmNo() .wait('Choose a schema template:') .sendCarriageReturn() .wait('Do you want to edit the schema now?') @@ -68,30 +65,28 @@ export function addApiWithoutSchema(cwd: string, opts: Partial = }); } -export function addApiWithSchema(cwd: string, schemaFile: string, opts: Partial = {}) { +export function addApiWithBlankSchema(cwd: string, opts: Partial = {}) { const options = _.assign(defaultOptions, opts); - const schemaPath = getSchemaPath(schemaFile); return new Promise((resolve, reject) => { - spawn(getCLIPath(), ['add', 'api'], { cwd, stripColors: true }) + spawn(getCLIPath(options.testingWithLatestCodebase), ['add', 'api'], { cwd, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendKeyUp(3) + .sendCarriageReturn() .wait('Provide API name:') .sendLine(options.apiName) - .wait(/.*Choose the default authorization type for the API.*/) + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) .sendCarriageReturn() - .wait(/.*Enter a description for the API key.*/) - .sendCarriageReturn() - .wait(/.*After how many days from now the API key should expire.*/) - .sendLine(opts.apiKeyExpirationDays ? opts.apiKeyExpirationDays.toString() : '1') - .wait(/.*Do you want to configure advanced settings for the GraphQL API.*/) + .wait('Choose a schema template:') + .sendKeyDown(2) .sendCarriageReturn() - .wait('Do you have an annotated GraphQL schema?') - .sendConfirmYes() - .wait('Provide your schema file path:') - .sendLine(schemaPath) + .wait('Do you want to edit the schema now?') + .sendLine('n') .wait( '"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud', ) + .sendEof() .run((err: Error) => { if (!err) { resolve(); @@ -102,32 +97,25 @@ export function addApiWithSchema(cwd: string, schemaFile: string, opts: Partial< }); } -export function addApiWithSchemaAndConflictDetection(cwd: string, schemaFile: string) { - const schemaPath = getSchemaPath(schemaFile); +export function addApiWithBlankSchemaAndConflictDetection(cwd: string) { return new Promise((resolve, reject) => { - spawn(getCLIPath(), ['add', 'api'], { cwd, stripColors: true }) + spawn(getCLIPath(defaultOptions.testingWithLatestCodebase), ['add', 'api'], { cwd, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() - .wait('Provide API name:') - .sendCarriageReturn() - .wait(/.*Choose the default authorization type for the API.*/) - .sendCarriageReturn() - .wait(/.*Enter a description for the API key.*/) + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendKeyUp() .sendCarriageReturn() - .wait(/.*After how many days from now the API key should expire.*/) - .sendCarriageReturn() - .wait(/.*Do you want to configure advanced settings for the GraphQL API.*/) - .sendLine(KEY_DOWN_ARROW) // Down - .wait(/.*Configure additional auth types.*/) - .sendConfirmNo() .wait(/.*Enable conflict detection.*/) .sendConfirmYes() .wait(/.*Select the default resolution strategy.*/) .sendCarriageReturn() - .wait(/.*Do you have an annotated GraphQL schema.*/) - .sendConfirmYes() - .wait('Provide your schema file path:') - .sendLine(schemaPath) + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendCarriageReturn() + .wait('Choose a schema template:') + .sendKeyDown(2) + .sendCarriageReturn() + .wait('Do you want to edit the schema now?') + .sendLine('n') .wait( '"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud', ) @@ -155,7 +143,7 @@ export function updateApiWithMultiAuth(cwd: string, settings: any) { spawn(getCLIPath(settings.testingWithLatestCodebase), ['update', 'api'], { cwd, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() - .wait('Select from the options below') + .wait(/.*Select a setting to edit.*/) .sendCarriageReturn() .wait(/.*Choose the default authorization type for the API.*/) .sendCarriageReturn() @@ -163,8 +151,6 @@ export function updateApiWithMultiAuth(cwd: string, settings: any) { .sendLine('description') .wait(/.*After how many days from now the API key should expire.*/) .sendLine('300') - .wait(/.*Do you want to configure advanced settings for the GraphQL API.*/) - .sendLine(KEY_DOWN_ARROW) // Down .wait(/.*Configure additional auth types.*/) .sendConfirmYes() .wait(/.*Choose the additional authorization types you want to configure for the API.*/) @@ -187,7 +173,29 @@ export function updateApiWithMultiAuth(cwd: string, settings: any) { .sendLine('1000') .wait(/.*Enter the number of milliseconds a token is valid after being authenticated.*/) .sendLine('2000') - .wait('Enable conflict detection?') + .wait(/.*Successfully updated resource.*/) + .sendEof() + .run((err: Error) => { + if (!err) { + resolve(); + } else { + reject(err); + } + }); + }); +} + +export function apiEnableDataStore(cwd: string, settings: any) { + return new Promise((resolve, reject) => { + spawn(getCLIPath(settings.testingWithLatestCodebase), ['update', 'api'], { cwd, stripColors: true }) + .wait('Select from one of the below mentioned services:') + .sendCarriageReturn() + .wait(/.*Select a setting to edit.*/) + .sendKeyDown() + .sendCarriageReturn() + .wait(/.*Select the default resolution strategy.*/) + .sendCarriageReturn() + .wait(/.*Do you want to override default per model settings?.*/) .sendConfirmNo() .wait(/.*Successfully updated resource.*/) .sendEof() @@ -201,14 +209,14 @@ export function updateApiWithMultiAuth(cwd: string, settings: any) { }); } -export function apiUpdateToggleDataStore(cwd: string, settings: any) { +export function apiDisableDataStore(cwd: string, settings: any) { return new Promise((resolve, reject) => { spawn(getCLIPath(settings.testingWithLatestCodebase), ['update', 'api'], { cwd, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() - .wait('Select from the options below') - .send(KEY_DOWN_ARROW) - .sendLine(KEY_DOWN_ARROW) // select enable datastore for the api + .wait(/.*Select a setting to edit.*/) + .sendKeyDown(2) // Disable conflict detection + .sendCarriageReturn() .wait(/.*Successfully updated resource.*/) .sendEof() .run((err: Error) => { @@ -221,28 +229,41 @@ export function apiUpdateToggleDataStore(cwd: string, settings: any) { }); } -export function updateAPIWithResolutionStrategy(cwd: string, settings: any) { +export function updateAPIWithResolutionStrategyWithoutModels(cwd: string, settings: any) { return new Promise((resolve, reject) => { spawn(getCLIPath(settings.testingWithLatestCodebase), ['update', 'api'], { cwd, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() - .wait('Select from the options below') + .wait(/.*Select a setting to edit.*/) + .sendKeyDown() .sendCarriageReturn() - .wait(/.*Choose the default authorization type for the API.*/) + .wait(/.*Select the default resolution strategy.*/) + .sendKeyDown() .sendCarriageReturn() - .wait(/.*Enter a description for the API key.*/) + .wait(/.*Successfully updated resource.*/) + .sendEof() + .run((err: Error) => { + if (!err) { + resolve(); + } else { + reject(err); + } + }); + }); +} + +export function updateAPIWithResolutionStrategyWithModels(cwd: string, settings: any) { + return new Promise((resolve, reject) => { + spawn(getCLIPath(settings.testingWithLatestCodebase), ['update', 'api'], { cwd, stripColors: true }) + .wait('Please select from one of the below mentioned services:') .sendCarriageReturn() - .wait(/.*After how many days from now the API key should expire.*/) + .wait(/.*Select a setting to edit.*/) + .sendKeyDown() .sendCarriageReturn() - .wait(/.*Do you want to configure advanced settings for the GraphQL API.*/) - .sendLine(KEY_DOWN_ARROW) // Down - .wait(/.*Configure additional auth types.*/) - .sendConfirmNo() - .wait(/.*Enable conflict detection.*/) - .sendConfirmYes() .wait(/.*Select the default resolution strategy.*/) - .sendLine(KEY_DOWN_ARROW) // Down - .wait(/.*Do you want to override default per model settings.*/) + .sendKeyDown() + .sendCarriageReturn() + .wait(/.*Do you want to override default per model settings?.*/) .sendConfirmNo() .wait(/.*Successfully updated resource.*/) .sendEof() @@ -385,28 +406,26 @@ const allAuthTypes = ['API key', 'Amazon Cognito User Pool', 'IAM', 'OpenID Conn export function addApi(projectDir: string, settings?: any) { let authTypesToSelectFrom = allAuthTypes.slice(); return new Promise((resolve, reject) => { - let chain = spawn(getCLIPath(), ['add', 'api'], { cwd: projectDir, stripColors: true }) + let chain = spawn(getCLIPath(defaultOptions.testingWithLatestCodebase), ['add', 'api'], { cwd: projectDir, stripColors: true }) .wait('Select from one of the below mentioned services:') - .sendCarriageReturn() - .wait('Provide API name:') .sendCarriageReturn(); if (settings && Object.keys(settings).length > 0) { const authTypesToAdd = Object.keys(settings); const defaultType = authTypesToAdd[0]; + chain + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendKeyUp(2) + .sendCarriageReturn(); + singleSelect(chain.wait('Choose the default authorization type for the API'), defaultType, authTypesToSelectFrom); setupAuthType(defaultType, chain, settings); if (authTypesToAdd.length > 1) { authTypesToAdd.shift(); - chain - .wait('Do you want to configure advanced settings for the GraphQL API') - .send(KEY_DOWN_ARROW) //yes - .sendCarriageReturn() - .wait('Configure additional auth types?') - .sendConfirmYes(); + chain.wait('Configure additional auth types?').sendConfirmYes(); authTypesToSelectFrom = authTypesToSelectFrom.filter(x => x !== defaultType); @@ -419,21 +438,14 @@ export function addApi(projectDir: string, settings?: any) { authTypesToAdd.forEach(authType => { setupAuthType(authType, chain, settings); }); - - chain.wait('Enable conflict detection?').sendCarriageReturn(); //No } else { - chain.wait('Do you want to configure advanced settings for the GraphQL API').sendCarriageReturn(); //No + chain.wait('Configure additional auth types?').sendLine('n'); } - } else { - chain.wait('Choose the default authorization type for the API').sendCarriageReturn(); - setupAPIKey(chain); - - chain.wait('Do you want to configure advanced settings for the GraphQL API').sendCarriageReturn(); //No } chain - .wait('Do you have an annotated GraphQL schema?') - .sendConfirmNo() + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendCarriageReturn() .wait('Choose a schema template:') .sendCarriageReturn() .wait('Do you want to edit the schema now?') @@ -512,18 +524,19 @@ function setupOIDC(chain: any, settings?: any) { export function addApiWithCognitoUserPoolAuthTypeWhenAuthExists(projectDir: string) { return new Promise((resolve, reject) => { - spawn(getCLIPath(), ['add', 'api'], { cwd: projectDir, stripColors: true }) + spawn(getCLIPath(defaultOptions.testingWithLatestCodebase), ['add', 'api'], { cwd: projectDir, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() - .wait('Provide API name:') + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendKeyUp(2) .sendCarriageReturn() - .wait('Choose the default authorization type for the API') - .send(KEY_DOWN_ARROW) + .wait(/.*Choose the default authorization type for the API.*/) + .sendKeyDown(1) .sendCarriageReturn() - .wait('Do you want to configure advanced settings for the GraphQL AP') + .wait(/.*Configure additional auth types.*/) + .sendLine('n') + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) .sendCarriageReturn() - .wait('Do you have an annotated GraphQL schema?') - .sendConfirmNo() .wait('Choose a schema template:') .sendCarriageReturn() .wait('Do you want to edit the schema now?') @@ -570,6 +583,16 @@ export function addRestContainerApi(projectDir: string) { }); } +export function rebuildApi(projDir: string, apiName: string) { + return new Promise((resolve, reject) => { + spawn(getCLIPath(), ['rebuild', 'api'], { cwd: projDir, stripColors: true }) + .wait('Type the name of the API to confirm you want to continue') + .sendLine(apiName) + .wait('All resources are updated in the cloud') + .run(err => (err ? reject(err) : resolve())); + }); +} + export function addRestContainerApiForCustomPolicies(projectDir: string, settings: { name: string }) { return new Promise((resolve, reject) => { spawn(getCLIPath(), ['add', 'api'], { cwd: projectDir, stripColors: true }) diff --git a/packages/amplify-e2e-core/src/categories/auth.ts b/packages/amplify-e2e-core/src/categories/auth.ts index 8f935ec2665..027e53e5fe8 100644 --- a/packages/amplify-e2e-core/src/categories/auth.ts +++ b/packages/amplify-e2e-core/src/categories/auth.ts @@ -119,12 +119,23 @@ export function addAuthWithGroupTrigger(cwd: string, settings: any): Promise { return new Promise((resolve, reject) => { - spawn(getCLIPath(), ['add', 'api'], { cwd, stripColors: true }) + spawn(getCLIPath(defaultOptions.testingWithLatestCodebase), ['add', 'api'], { cwd, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() - .wait('Provide API name') + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendKeyUp(2) .sendCarriageReturn() .wait('Choose the default authorization type for the API') .send(KEY_DOWN_ARROW) @@ -148,10 +159,9 @@ export function addAuthViaAPIWithTrigger(cwd: string, settings: any): Promise { return new Promise((resolve, reject) => { - spawn(getCLIPath(), ['add', 'api'], { cwd, stripColors: true }) + spawn(getCLIPath(defaultOptions.testingWithLatestCodebase), ['add', 'api'], { cwd, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn() - .wait('Provide API name') + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) + .sendKeyUp(2) .sendCarriageReturn() .wait('Choose the default authorization type for the API') .send(KEY_DOWN_ARROW) @@ -235,10 +246,9 @@ export function addAuthwithUserPoolGroupsViaAPIWithTrigger(cwd: string, settings .sendCarriageReturn() .wait('Do you want to edit your add-to-group function now?') .sendConfirmNo() - .wait(/.*Do you want to configure advanced settings for the GraphQL API.*/) - .sendCarriageReturn() - .wait('Do you have an annotated GraphQL schema?') + .wait(/.*Configure additional auth types.*/) .sendConfirmNo() + .wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/) .sendCarriageReturn() .wait('Choose a schema template:') .sendCarriageReturn() @@ -872,12 +882,78 @@ export function addAuthUserPoolOnly(cwd: string, settings: any): Promise { }); } +// creates 2 groups: Admins, Users +export function addAuthWithGroups(cwd: string): Promise { + return new Promise((resolve, reject) => { + spawn(getCLIPath(), ['add', 'auth'], { cwd, stripColors: true }) + .wait('Do you want to use the default authentication and security configuration') + .sendKeyDown(2) + .sendCarriageReturn() // Manual configuration + .wait('Select the authentication/authorization services that you want to use') + .sendCarriageReturn() // for sign-up/-in and IAM controls + .wait('Provide a friendly name for your resource that will be used') + .sendCarriageReturn() // Default + .wait('Enter a name for your identity pool') + .sendCarriageReturn() // Default + .wait('Allow unauthenticated logins') + .sendCarriageReturn() // No + .wait('Do you want to enable 3rd party authentication providers') + .sendKeyDown() + .sendCarriageReturn() // No + .wait('Provide a name for your user pool') + .sendCarriageReturn() // Default + .wait('Warning: you will not be able to edit these selections') + .wait('How do you want users to be able to sign in') + .sendCarriageReturn() // Username + .wait('Do you want to add User Pool Groups') + .sendCarriageReturn() // Yes + .wait('Provide a name for your user pool group') + .sendLine('Admins') + .wait('Do you want to add another User Pool Group') + .sendConfirmYes() + .wait('Provide a name for your user pool group') + .sendLine('Users') + .wait('Do you want to add another User Pool Group') + .sendConfirmNo() + .wait('Sort the user pool groups in order of preference') + .sendCarriageReturn() // As is, Admins, Users + .wait('Do you want to add an admin queries API') + .sendKeyDown() + .sendCarriageReturn() // No + .wait('Multifactor authentication (MFA) user login options') + .sendCarriageReturn() // OFF + .wait('Email based user registration/forgot password') + .sendCarriageReturn() // Enabled + .wait('Please specify an email verification subject') + .sendCarriageReturn() // Your verification code + .wait('Please specify an email verification message') + .sendCarriageReturn() // Your verification code is {####} + .wait('Do you want to override the default password policy') + .sendConfirmNo() + .wait('What attributes are required for signing up') + .sendCarriageReturn() // Email + .wait("Specify the app's refresh token expiration period") + .sendCarriageReturn() // 30 + .wait('Do you want to specify the user attributes this app can read and write') + .sendConfirmNo() + .wait('Do you want to enable any of the following capabilities') + .sendCarriageReturn() // None + .wait('Do you want to use an OAuth flow') + .sendKeyDown() + .sendCarriageReturn() // No + .wait('Do you want to configure Lambda Triggers for Cognito') + .sendConfirmNo() + .sendEof() + .run((err: Error) => (err ? reject(err) : resolve())); + }); +} + +// creates 2 groups: Admins, Users export function addAuthWithGroupsAndAdminAPI(cwd: string, settings: any): Promise { return new Promise((resolve, reject) => { spawn(getCLIPath(), ['add', 'auth'], { cwd, stripColors: true }) .wait('Do you want to use the default authentication and security configuration') - .send(KEY_DOWN_ARROW) - .send(KEY_DOWN_ARROW) + .sendKeyDown(2) .sendCarriageReturn() // Manual configuration .wait('Select the authentication/authorization services that you want to use') .sendCarriageReturn() // for sign-up/-in and IAM controls @@ -888,7 +964,7 @@ export function addAuthWithGroupsAndAdminAPI(cwd: string, settings: any): Promis .wait('Allow unauthenticated logins') .sendCarriageReturn() // No .wait('Do you want to enable 3rd party authentication providers') - .send(KEY_DOWN_ARROW) + .sendKeyDown() .sendCarriageReturn() // No .wait('Provide a name for your user pool') .sendCarriageReturn() // Default @@ -932,7 +1008,7 @@ export function addAuthWithGroupsAndAdminAPI(cwd: string, settings: any): Promis .wait('Do you want to enable any of the following capabilities') .sendCarriageReturn() // None .wait('Do you want to use an OAuth flow') - .send(KEY_DOWN_ARROW) + .sendKeyDown() .sendCarriageReturn() // No .wait('Do you want to configure Lambda Triggers for Cognito') .sendConfirmNo() diff --git a/packages/amplify-e2e-core/src/categories/geo.ts b/packages/amplify-e2e-core/src/categories/geo.ts index cae3c27619a..46fa7f4b74f 100644 --- a/packages/amplify-e2e-core/src/categories/geo.ts +++ b/packages/amplify-e2e-core/src/categories/geo.ts @@ -1,18 +1,18 @@ import { getCLIPath, nspawn as spawn, KEY_DOWN_ARROW, generateRandomShortId } from '..'; export type GeoConfig = { - isFirstGeoResource?: boolean - isAdditional?: boolean - isDefault?: boolean - resourceName?: string -} + isFirstGeoResource?: boolean; + isAdditional?: boolean; + isDefault?: boolean; + resourceName?: string; +}; const defaultGeoConfig: GeoConfig = { isFirstGeoResource: false, isAdditional: false, isDefault: true, - resourceName: '\r' -} + resourceName: '\r', +}; const defaultSearchIndexQuestion = `Set this search index as the default? It will be used in Amplify search index API calls if no explicit reference is provided.`; const defaultMapQuestion = `Set this Map as the default? It will be used in Amplify Map API calls if no explicit reference is provided.`; @@ -33,15 +33,14 @@ export function addMapWithDefault(cwd: string, settings: GeoConfig = {}): Promis .sendCarriageReturn(); if (config.isFirstGeoResource === true) { - chain.wait('Are you tracking commercial assets for your business in your app?') - .sendCarriageReturn(); + chain.wait('Are you tracking commercial assets for your business in your app?').sendCarriageReturn(); chain.wait('Successfully set RequestBasedUsage pricing plan for your Geo resources.'); } chain.wait('Do you want to configure advanced settings?').sendConfirmNo(); if (config.isAdditional === true) { - chain.wait(defaultMapQuestion) + chain.wait(defaultMapQuestion); if (config.isDefault === true) { chain.sendConfirmYes(); } else { @@ -54,7 +53,7 @@ export function addMapWithDefault(cwd: string, settings: GeoConfig = {}): Promis } else { reject(); } - }) + }); }); } @@ -75,28 +74,26 @@ export function addPlaceIndexWithDefault(cwd: string, settings: GeoConfig = {}): .sendCarriageReturn(); if (config.isFirstGeoResource === true) { - chain.wait('Are you tracking commercial assets for your business in your app?') - .sendConfirmNo(); + chain.wait('Are you tracking commercial assets for your business in your app?').sendConfirmNo(); chain.wait('Successfully set RequestBasedUsage pricing plan for your Geo resources.'); } - chain.wait('Do you want to configure advanced settings?') - .sendConfirmNo(); - if (config.isAdditional === true) { - chain.wait(defaultSearchIndexQuestion); - if (config.isDefault === true) { - chain.sendConfirmYes(); - } else { - chain.sendConfirmNo(); - } + chain.wait('Do you want to configure advanced settings?').sendConfirmNo(); + if (config.isAdditional === true) { + chain.wait(defaultSearchIndexQuestion); + if (config.isDefault === true) { + chain.sendConfirmYes(); + } else { + chain.sendConfirmNo(); } - chain.run((err: Error) => { - if (!err) { - resolve(); - } else { - reject(); - } - }) + } + chain.run((err: Error) => { + if (!err) { + resolve(); + } else { + reject(); + } + }); }); } @@ -122,7 +119,7 @@ export function updateMapWithDefault(cwd: string): Promise { } else { reject(); } - }) + }); }); } @@ -148,7 +145,7 @@ export function updateSecondMapAsDefault(cwd: string): Promise { } else { reject(); } - }) + }); }); } @@ -175,7 +172,7 @@ export function updatePlaceIndexWithDefault(cwd: string): Promise { } else { reject(); } - }) + }); }); } @@ -202,7 +199,7 @@ export function updateSecondPlaceIndexAsDefault(cwd: string): Promise { } else { reject(); } - }) + }); }); } @@ -225,7 +222,7 @@ export function removeMap(cwd: string): Promise { } else { reject(); } - }) + }); }); } @@ -250,7 +247,7 @@ export function removeFirstDefaultMap(cwd: string): Promise { } else { reject(); } - }) + }); }); } @@ -274,7 +271,7 @@ export function removePlaceIndex(cwd: string): Promise { } else { reject(); } - }) + }); }); } @@ -300,7 +297,7 @@ export function removeFirstDefaultPlaceIndex(cwd: string): Promise { } else { reject(); } - }) + }); }); } diff --git a/packages/amplify-e2e-core/src/categories/storage.ts b/packages/amplify-e2e-core/src/categories/storage.ts index 17e67496dc5..dead585718c 100644 --- a/packages/amplify-e2e-core/src/categories/storage.ts +++ b/packages/amplify-e2e-core/src/categories/storage.ts @@ -595,7 +595,6 @@ export function overrideS3(cwd: string, settings: {}) { }); } - export function addS3StorageWithSettings(projectDir: string, settings: AddStorageSettings): Promise { return new Promise((resolve, reject) => { let chain = spawn(getCLIPath(), ['add', 'storage'], { cwd: projectDir, stripColors: true }); diff --git a/packages/amplify-e2e-core/src/cli-test-runner.js b/packages/amplify-e2e-core/src/cli-test-runner.js index ad6255698ea..2f6dc5d6098 100644 --- a/packages/amplify-e2e-core/src/cli-test-runner.js +++ b/packages/amplify-e2e-core/src/cli-test-runner.js @@ -1,16 +1,38 @@ -const circusRunner = require('jest-circus/runner'); -const throat = require('throat'); -const uuid = require('uuid'); +const circusRunner = require("jest-circus/runner"); +const throat = require("throat"); +const uuid = require("uuid"); const mutex = throat(1); -export const run = async (globalConfig, config, environment, runtime, testPath) => { +export const run = async ( + globalConfig, + config, + environment, + runtime, + testPath +) => { const CLITestRunner = {}; - environment.global.addCLITestRunnerLogs = logs => { + environment.global.addCLITestRunnerLogs = (logs) => { CLITestRunner.logs = logs; }; - environment.global.getRandomId = () => mutex(() => uuid.v4().split('-')[0]); - const result = await circusRunner(globalConfig, config, environment, runtime, testPath); + environment.global.getRandomId = () => mutex(() => uuid.v4().split("-")[0]); + const result = await circusRunner( + globalConfig, + config, + environment, + runtime, + testPath + ); + setTimeout(() => { + if (process.platform === "win32") { + // An issue with node-pty leaves open handles when running within jest + // This prevents the jest process from exiting without being forced. + // Exiting here as a workaround, only on windows. + // A timeout is used to give Jest time to render the list of passed/failed tests. + // See https://github.com/microsoft/node-pty/issues/437 + process.exit(result.numFailingTests !== 0); + } + }, 1000); result.CLITestRunner = CLITestRunner; return result; }; diff --git a/packages/amplify-e2e-core/src/index.ts b/packages/amplify-e2e-core/src/index.ts index 963e5e0e4b9..048d2576415 100644 --- a/packages/amplify-e2e-core/src/index.ts +++ b/packages/amplify-e2e-core/src/index.ts @@ -38,14 +38,12 @@ export function getCLIPath(testingWithLatestCodebase = false) { } export function isTestingWithLatestCodebase(scriptRunnerPath) { - return scriptRunnerPath === process.execPath + return scriptRunnerPath === process.execPath; } export function getScriptRunnerPath(testingWithLatestCodebase = false) { if (!testingWithLatestCodebase) { - return process.platform === 'win32' - ? 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' - : 'exec'; + return process.platform === 'win32' ? 'node.exe' : 'exec'; } // nodejs executable @@ -81,9 +79,10 @@ export async function installAmplifyCLI(version: string = 'latest') { env: process.env, stdio: 'inherit', }); - process.env.AMPLIFY_PATH = process.platform === 'win32' - ? path.join(os.homedir(), '..', '..', 'Program` Files', 'nodejs', 'node_modules', '@aws-amplify', 'cli', 'bin', 'amplify') - : path.join(os.homedir(), '.npm-global', 'bin', 'amplify'); + process.env.AMPLIFY_PATH = + process.platform === 'win32' + ? path.join(os.homedir(), '..', '..', 'Program` Files', 'nodejs', 'node_modules', '@aws-amplify', 'cli', 'bin', 'amplify') + : path.join(os.homedir(), '.npm-global', 'bin', 'amplify'); } export async function createNewProjectDir( diff --git a/packages/amplify-e2e-core/src/init/amplifyPush.ts b/packages/amplify-e2e-core/src/init/amplifyPush.ts index cbf96b13b46..4a79398dd8c 100644 --- a/packages/amplify-e2e-core/src/init/amplifyPush.ts +++ b/packages/amplify-e2e-core/src/init/amplifyPush.ts @@ -83,9 +83,17 @@ export function cancelIterativeAmplifyPush( }); } -export function amplifyPushWithoutCodegen(cwd: string, testingWithLatestCodebase: boolean = false): Promise { +export function amplifyPushWithoutCodegen( + cwd: string, + testingWithLatestCodebase: boolean = false, + allowDestructiveUpdates: boolean = false, +): Promise { + const args = ['push']; + if (allowDestructiveUpdates) { + args.push('--allow-destructive-graphql-schema-updates'); + } return new Promise((resolve, reject) => { - spawn(getCLIPath(testingWithLatestCodebase), ['push'], { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) + spawn(getCLIPath(testingWithLatestCodebase), args, { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) .wait('Are you sure you want to continue?') .sendCarriageReturn() .run((err: Error) => { @@ -98,9 +106,18 @@ export function amplifyPushWithoutCodegen(cwd: string, testingWithLatestCodebase }); } -export function amplifyPushUpdate(cwd: string, waitForText?: RegExp, testingWithLatestCodebase: boolean = false): Promise { +export function amplifyPushUpdate( + cwd: string, + waitForText?: RegExp, + testingWithLatestCodebase: boolean = false, + allowDestructiveUpdates: boolean = false, +): Promise { + const args = ['push']; + if (allowDestructiveUpdates) { + args.push('--allow-destructive-graphql-schema-updates'); + } return new Promise((resolve, reject) => { - spawn(getCLIPath(testingWithLatestCodebase), ['push'], { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) + spawn(getCLIPath(testingWithLatestCodebase), args, { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) .wait('Are you sure you want to continue?') .sendConfirmYes() .wait(waitForText || /.*/) @@ -130,9 +147,17 @@ export function amplifyPushAuth(cwd: string, testingWithLatestCodebase: boolean }); } -export function amplifyPushUpdateForDependentModel(cwd: string, testingWithLatestCodebase: boolean = false): Promise { +export function amplifyPushUpdateForDependentModel( + cwd: string, + testingWithLatestCodebase: boolean = false, + allowDestructiveUpdates: boolean = false, +): Promise { + const args = ['push']; + if (allowDestructiveUpdates) { + args.push('--allow-destructive-graphql-schema-updates'); + } return new Promise((resolve, reject) => { - spawn(getCLIPath(testingWithLatestCodebase), ['push'], { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) + spawn(getCLIPath(testingWithLatestCodebase), args, { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) .wait('Are you sure you want to continue?') .sendConfirmYes() .wait(/.*/) @@ -255,6 +280,21 @@ export function amplifyPushWithNoChanges(cwd: string, testingWithLatestCodebase: }); } +export function amplifyPushDestructiveApiUpdate(cwd: string, includeForce: boolean) { + return new Promise((resolve, reject) => { + const args = ['push', '--yes']; + if (includeForce) { + args.push('--force'); + } + const chain = spawn(getCLIPath(), args, { cwd, stripColors: true }); + if (includeForce) { + chain.wait('All resources are updated in the cloud').run(err => (err ? reject(err) : resolve())); + } else { + chain.wait('If this is intended, rerun the command with').run(err => (err ? resolve(err) : reject())); // in this case, we expect the CLI to error out + } + }); +} + export function amplifyPushOverride(cwd: string, testingWithLatestCodebase: boolean = false): Promise { return new Promise((resolve, reject) => { //Test detailed status diff --git a/packages/amplify-e2e-core/src/init/initProjectHelper.ts b/packages/amplify-e2e-core/src/init/initProjectHelper.ts index 0a942b3e89e..fac3d5b5908 100644 --- a/packages/amplify-e2e-core/src/init/initProjectHelper.ts +++ b/packages/amplify-e2e-core/src/init/initProjectHelper.ts @@ -2,6 +2,7 @@ import { nspawn as spawn, getCLIPath, singleSelect, addCircleCITags } from '..'; import { KEY_DOWN_ARROW } from '../utils'; import { amplifyRegions } from '../configure'; import { EOL } from 'os'; +import { v4 as uuid } from 'uuid'; const defaultSettings = { name: EOL, @@ -45,6 +46,8 @@ export function initJSProjectWithProfile(cwd: string, settings?: Partial 20) console.warn('Project names should not be longer than 20 characters. This may cause tests to break.'); + return new Promise((resolve, reject) => { const chain = spawn(getCLIPath(), cliArgs, { cwd, stripColors: true, env, disableCIDetection: s.disableCIDetection }) .wait('Enter a name for the project') @@ -130,6 +133,12 @@ export function initAndroidProjectWithProfile(cwd: string, settings: Object): Pr }); } +export function createRandomName() { + const length = 20; + const regExp = new RegExp('-', 'g'); + return uuid().replace(regExp, '').substring(0, length); +} + export function initIosProjectWithProfile(cwd: string, settings: Object): Promise { const s = { ...defaultSettings, ...settings }; diff --git a/packages/amplify-e2e-core/src/utils/api.ts b/packages/amplify-e2e-core/src/utils/api.ts index f1588fb348e..ca5e2e7c66f 100644 --- a/packages/amplify-e2e-core/src/utils/api.ts +++ b/packages/amplify-e2e-core/src/utils/api.ts @@ -11,3 +11,15 @@ export function updateConfig(projectDir: string, projectName: string, config: an const configPath = path.join(projectDir, 'amplify', 'backend', 'api', projectName, TRANSFORM_CONFIG_FILE_NAME); fs.writeFileSync(configPath, JSON.stringify(config, null, 4)); } + +export function addCustomResolver(projectDir: string, apiName: string, resolverName: string, resolver: string) { + const resolverPath = path.join(projectDir, 'amplify', 'backend', 'api', apiName, 'resolvers', resolverName); + fs.writeFileSync(resolverPath, resolver); +} + +export function writeToCustomResourcesJson(projectDir: string, apiName: string, json?: Object) { + const jsonPath = path.join(projectDir, 'amplify', 'backend', 'api', apiName, 'stacks', 'CustomResources.json'); + const customResourceJson = JSON.parse(fs.readFileSync(jsonPath).toString()); + const mergedJson = { ...customResourceJson, ...json }; + fs.writeFileSync(jsonPath, JSON.stringify(mergedJson)); +} diff --git a/packages/amplify-e2e-core/src/utils/headless.ts b/packages/amplify-e2e-core/src/utils/headless.ts index 2fbce197d92..3d367ec97b8 100644 --- a/packages/amplify-e2e-core/src/utils/headless.ts +++ b/packages/amplify-e2e-core/src/utils/headless.ts @@ -1,40 +1,95 @@ -import { AddApiRequest, AddAuthRequest, ImportAuthRequest, UpdateApiRequest, UpdateAuthRequest } from 'amplify-headless-interface'; -import execa from 'execa'; +import { + AddApiRequest, + AddAuthRequest, + AddStorageRequest, + ImportAuthRequest, + ImportStorageRequest, + RemoveStorageRequest, + UpdateApiRequest, + UpdateAuthRequest, + UpdateStorageRequest, +} from 'amplify-headless-interface'; +import execa, { ExecaChildProcess } from 'execa'; import { getCLIPath } from '..'; -export const addHeadlessApi = async (cwd: string, request: AddApiRequest) => { - await executeHeadlessCommand(cwd, 'api', 'add', request); +export const addHeadlessApi = async (cwd: string, request: AddApiRequest): Promise> => { + return await executeHeadlessCommand(cwd, 'api', 'add', request); }; -export const updateHeadlessApi = async (cwd: string, request: UpdateApiRequest) => { - await executeHeadlessCommand(cwd, 'api', 'update', request); +export const updateHeadlessApi = async ( + cwd: string, + request: UpdateApiRequest, + allowDestructiveUpdates?: boolean, +): Promise> => { + return await executeHeadlessCommand(cwd, 'api', 'update', request, undefined, allowDestructiveUpdates); }; -export const removeHeadlessApi = async (cwd: string, apiName: string) => { - await headlessRemoveResource(cwd, 'api', apiName); +export const removeHeadlessApi = async (cwd: string, apiName: string): Promise> => { + return await headlessRemoveResource(cwd, 'api', apiName); }; -export const addHeadlessAuth = async (cwd: string, request: AddAuthRequest) => { - await executeHeadlessCommand(cwd, 'auth', 'add', request); +export const addHeadlessAuth = async (cwd: string, request: AddAuthRequest): Promise> => { + return await executeHeadlessCommand(cwd, 'auth', 'add', request); }; -export const updateHeadlessAuth = async (cwd: string, request: UpdateAuthRequest) => { - await executeHeadlessCommand(cwd, 'auth', 'update', request); +export const updateHeadlessAuth = async (cwd: string, request: UpdateAuthRequest): Promise> => { + return await executeHeadlessCommand(cwd, 'auth', 'update', request); }; -export const removeHeadlessAuth = async (cwd: string, authName: string) => { - await headlessRemoveResource(cwd, 'auth', authName); +export const removeHeadlessAuth = async (cwd: string, authName: string): Promise> => { + return await headlessRemoveResource(cwd, 'auth', authName); }; -export const headlessAuthImport = async (cwd: string, request: ImportAuthRequest) => { - await executeHeadlessCommand(cwd, 'auth', 'import', request); +export const headlessAuthImport = async (cwd: string, request: ImportAuthRequest): Promise> => { + return await executeHeadlessCommand(cwd, 'auth', 'import', request); }; -const headlessRemoveResource = async (cwd: string, category: string, resourceName: string) => { - await execa(getCLIPath(), ['remove', category, resourceName, '--yes'], { cwd }); +export const addHeadlessStorage = async (cwd: string, request: AddStorageRequest): Promise> => { + return await executeHeadlessCommand(cwd, 'storage', 'add', request); }; -const executeHeadlessCommand = async (cwd: string, category: string, operation: string, request: AnyHeadlessRequest) => { - await execa(getCLIPath(), [operation, category, '--headless'], { input: JSON.stringify(request), cwd }); + +export const importHeadlessStorage = async ( + cwd: string, + request: ImportStorageRequest, + reject: boolean = true, +): Promise> => { + return await executeHeadlessCommand(cwd, 'storage', 'import', request, reject); +}; + +export const removeHeadlessStorage = async (cwd: string, request: RemoveStorageRequest): Promise> => { + return await executeHeadlessCommand(cwd, 'storage', 'remove', request); +}; + +export const updateHeadlessStorage = async (cwd: string, request: UpdateStorageRequest): Promise> => { + return await executeHeadlessCommand(cwd, 'storage', 'update', request); +}; + +const headlessRemoveResource = async (cwd: string, category: string, resourceName: string): Promise> => { + return await execa(getCLIPath(), ['remove', category, resourceName, '--yes'], { cwd }); +}; + +const executeHeadlessCommand = async ( + cwd: string, + category: string, + operation: string, + request: AnyHeadlessRequest, + reject: boolean = true, + allowDestructiveUpdates: boolean = false, +): Promise> => { + const args = [operation, category, '--headless']; + if (allowDestructiveUpdates) { + args.push('--allow-destructive-graphql-schema-updates'); + } + return await execa(getCLIPath(), args, { input: JSON.stringify(request), cwd, reject }); }; -type AnyHeadlessRequest = AddApiRequest | UpdateApiRequest | AddAuthRequest | UpdateAuthRequest | ImportAuthRequest; +type AnyHeadlessRequest = + | AddApiRequest + | UpdateApiRequest + | AddAuthRequest + | UpdateAuthRequest + | ImportAuthRequest + | AddStorageRequest + | ImportStorageRequest + | RemoveStorageRequest + | UpdateStorageRequest; diff --git a/packages/amplify-e2e-core/src/utils/nexpect.ts b/packages/amplify-e2e-core/src/utils/nexpect.ts index e268b1b05e6..264f0094711 100644 --- a/packages/amplify-e2e-core/src/utils/nexpect.ts +++ b/packages/amplify-e2e-core/src/utils/nexpect.ts @@ -23,7 +23,7 @@ import { join, parse } from 'path'; import * as fs from 'fs-extra'; import * as os from 'os'; import { getScriptRunnerPath, isTestingWithLatestCodebase } from '..'; - +const RETURN = process.platform === 'win32' ? '\r' : EOL; const DEFAULT_NO_OUTPUT_TIMEOUT = process.env.AMPLIFY_TEST_TIMEOUT_SEC ? Number.parseInt(process.env.AMPLIFY_TEST_TIMEOUT_SEC, 10) * 1000 : 5 * 60 * 1000; // 5 Minutes @@ -158,7 +158,7 @@ function chain(context: Context): ExecutionContext { sendLine: function (line: string): ExecutionContext { let _sendline: ExecutionStep = { fn: () => { - context.process.write(`${line}${EOL}`); + context.process.write(`${line}${RETURN}`); return true; }, name: '_sendline', @@ -172,7 +172,7 @@ function chain(context: Context): ExecutionContext { sendCarriageReturn: function (): ExecutionContext { let _sendline: ExecutionStep = { fn: () => { - context.process.write(EOL); + context.process.write(RETURN); return true; }, name: '_sendline', @@ -234,7 +234,7 @@ function chain(context: Context): ExecutionContext { sendConfirmYes: function (): ExecutionContext { var _send: ExecutionStep = { fn: () => { - context.process.write(`Y${EOL}`); + context.process.write(`Y${RETURN}`); return true; }, name: '_send', @@ -248,7 +248,7 @@ function chain(context: Context): ExecutionContext { sendConfirmNo: function (): ExecutionContext { var _send: ExecutionStep = { fn: () => { - context.process.write(`N${EOL}`); + context.process.write(`N${RETURN}`); return true; }, name: '_send', @@ -262,7 +262,7 @@ function chain(context: Context): ExecutionContext { sendCtrlC: function (): ExecutionContext { var _send: ExecutionStep = { fn: () => { - context.process.write(`${CONTROL_C}${EOL}`); + context.process.write(`${CONTROL_C}${RETURN}`); return true; }, name: '_send', @@ -640,11 +640,16 @@ export function nspawn(command: string | string[], params: string[] = [], option } const testingWithLatestCodebase = isTestingWithLatestCodebase(command); - if (testingWithLatestCodebase) { + if (testingWithLatestCodebase || (process.platform === 'win32' && !command.endsWith('.exe'))) { params.unshift(command); command = getScriptRunnerPath(testingWithLatestCodebase); } + if (process.platform === 'win32' && !command.endsWith('powershell.exe')) { + params.unshift(command); + command = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; + } + let childEnv = undefined; let pushEnv = undefined; @@ -665,6 +670,7 @@ export function nspawn(command: string | string[], params: string[] = [], option ...process.env, ...pushEnv, ...options.env, + NODE_OPTIONS: '--max_old_space_size=4096', }; // Undo ci-info detection, required for some tests diff --git a/packages/amplify-e2e-core/src/utils/sdk-calls.ts b/packages/amplify-e2e-core/src/utils/sdk-calls.ts index 129561f4e1d..3b4da4e0f8f 100644 --- a/packages/amplify-e2e-core/src/utils/sdk-calls.ts +++ b/packages/amplify-e2e-core/src/utils/sdk-calls.ts @@ -201,6 +201,16 @@ export const deleteTable = async (tableName: string, region: string) => { return await service.deleteTable({ TableName: tableName }).promise(); }; +export const putItemInTable = async (tableName: string, region: string, item: unknown) => { + const ddb = new DynamoDB.DocumentClient({ region }); + return await ddb.put({ TableName: tableName, Item: item }).promise(); +}; + +export const scanTable = async (tableName: string, region: string) => { + const ddb = new DynamoDB.DocumentClient({ region }); + return await ddb.scan({ TableName: tableName }).promise(); +}; + export const getAppSyncApi = async (appSyncApiId: string, region: string) => { const service = new AppSync({ region }); return await service.getGraphqlApi({ apiId: appSyncApiId }).promise(); diff --git a/packages/amplify-e2e-tests/schemas/custom_query.graphql b/packages/amplify-e2e-tests/schemas/custom_query.graphql new file mode 100644 index 00000000000..f86f19f9ad6 --- /dev/null +++ b/packages/amplify-e2e-tests/schemas/custom_query.graphql @@ -0,0 +1,21 @@ +type Todo @model { + id: ID! + name: String! + description: String + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + content: String + todo: Todo @hasOne +} + +type CommentConnection { + items: [Comment] + nextToken: String +} + +type Query { + commentsForTodo(todoId: ID!, limit: Int, nextToken: String): CommentConnection +} diff --git a/packages/amplify-e2e-tests/schemas/iterative-push/change-model-name/initial-schema.graphql b/packages/amplify-e2e-tests/schemas/iterative-push/change-model-name/initial-schema.graphql index 01480f28130..d3190805b39 100644 --- a/packages/amplify-e2e-tests/schemas/iterative-push/change-model-name/initial-schema.graphql +++ b/packages/amplify-e2e-tests/schemas/iterative-push/change-model-name/initial-schema.graphql @@ -3,7 +3,7 @@ type Comment @model @key(name: "byTodo", fields: ["todoID"]) { todoID: ID! } -type Todo @model { +type Task @model { id: ID! name: String! description: String diff --git a/packages/amplify-e2e-tests/schemas/model_with_sandbox_mode.graphql b/packages/amplify-e2e-tests/schemas/model_with_sandbox_mode.graphql new file mode 100644 index 00000000000..22c833e9c67 --- /dev/null +++ b/packages/amplify-e2e-tests/schemas/model_with_sandbox_mode.graphql @@ -0,0 +1,6 @@ +type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: "dev") + +type Todo @model { + id: ID! + content: String +} diff --git a/packages/amplify-e2e-tests/schemas/simple_model_new_primary_key.graphql b/packages/amplify-e2e-tests/schemas/simple_model_new_primary_key.graphql new file mode 100644 index 00000000000..cdbe2826bfe --- /dev/null +++ b/packages/amplify-e2e-tests/schemas/simple_model_new_primary_key.graphql @@ -0,0 +1,4 @@ +type Todo @model @key(fields: ["content"]) { + id: ID! + content: String! +} diff --git a/packages/amplify-e2e-tests/src/__tests__/api_1.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_1.test.ts index e20b226cab2..cd1f15374db 100644 --- a/packages/amplify-e2e-tests/src/__tests__/api_1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/api_1.test.ts @@ -4,7 +4,7 @@ import { deleteProject, initFlutterProjectWithProfile, initJSProjectWithProfile, - addApiWithSchema, + addApiWithoutSchema, updateApiSchema, updateApiWithMultiAuth, createNewProjectDir, @@ -22,6 +22,7 @@ import { getBackendAmplifyMeta, amplifyPushUpdateForDependentModel, amplifyPushForce, + createRandomName, } from 'amplify-e2e-core'; import path from 'path'; import { existsSync } from 'fs'; @@ -30,8 +31,10 @@ import _ from 'lodash'; describe('amplify add api (GraphQL)', () => { let projRoot: string; + let projFolderName: string; beforeEach(async () => { - projRoot = await createNewProjectDir('graphql-api'); + projFolderName = 'graphqlapi'; + projRoot = await createNewProjectDir(projFolderName); }); afterEach(async () => { @@ -44,8 +47,10 @@ describe('amplify add api (GraphQL)', () => { it('init a project and add the simple_model api', async () => { const envName = 'devtest'; - await initJSProjectWithProfile(projRoot, { name: 'simplemodel', envName }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + const projName = 'simplemodel'; + await initJSProjectWithProfile(projRoot, { name: projName, envName }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projName, 'simple_model.graphql'); await amplifyPush(projRoot); const meta = getProjectMeta(projRoot); @@ -74,8 +79,10 @@ describe('amplify add api (GraphQL)', () => { it('init a project then add and remove api', async () => { const envName = 'devtest'; - await initIosProjectWithProfile(projRoot, { name: 'simplemodel', envName }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + const projName = 'simplemodel'; + await initIosProjectWithProfile(projRoot, { name: projName, envName }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projName, 'simple_model.graphql'); await amplifyPush(projRoot); let meta = getProjectMeta(projRoot); @@ -98,8 +105,10 @@ describe('amplify add api (GraphQL)', () => { it('init a Flutter project and add the simple_model api', async () => { const envName = 'devtest'; - await initFlutterProjectWithProfile(projRoot, { name: 'simplemodel', envName }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + const projName = 'simplemodel'; + await initFlutterProjectWithProfile(projRoot, { name: projName, envName }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projName, 'simple_model.graphql'); await amplifyPushWithoutCodegen(projRoot); const meta = getProjectMeta(projRoot); @@ -132,7 +141,8 @@ describe('amplify add api (GraphQL)', () => { const initialSchema = 'initial_key_blog.graphql'; const nextSchema = 'next_key_blog.graphql'; await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema); await amplifyPushUpdate(projRoot); @@ -145,13 +155,15 @@ describe('amplify add api (GraphQL)', () => { }); it('init a project and add the simple_model api with multiple authorization providers', async () => { - await initJSProjectWithProfile(projRoot, { name: 'simplemodelmultiauth' }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + const appName = createRandomName(); + await initJSProjectWithProfile(projRoot, { name: appName }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, appName, 'simple_model.graphql'); await updateApiWithMultiAuth(projRoot, {}); await amplifyPush(projRoot); const meta = getProjectMeta(projRoot); - const { output } = meta.api.simplemodelmultiauth; + const { output } = meta.api[appName]; const { GraphQLAPIIdOutput, GraphQLAPIEndpointOutput, GraphQLAPIKeyOutput } = output; const { graphqlApi } = await getAppSyncApi(GraphQLAPIIdOutput, meta.providers.awscloudformation.Region); @@ -189,7 +201,8 @@ describe('amplify add api (GraphQL)', () => { it('init a project and add the simple_model api, match transformer version to current version', async () => { const name = `simplemodelv${TRANSFORM_CURRENT_VERSION}`; await initJSProjectWithProfile(projRoot, { name }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, name, 'simple_model.graphql'); await amplifyPush(projRoot); const meta = getProjectMeta(projRoot); @@ -217,7 +230,8 @@ describe('amplify add api (GraphQL)', () => { const initialSchema = 'two-model-schema.graphql'; const fnName = `integtestfn${random}`; await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await addFunction( projRoot, { @@ -235,7 +249,7 @@ describe('amplify add api (GraphQL)', () => { ); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema); - await amplifyPushUpdateForDependentModel(projRoot); + await amplifyPushUpdateForDependentModel(projRoot, undefined, true); const meta = getProjectMeta(projRoot); const region = meta.providers.awscloudformation.Region; const { output } = meta.api.blogapp; @@ -264,7 +278,8 @@ describe('amplify add api (GraphQL)', () => { it('api force push with no changes', async () => { const projectName = `apinochange`; await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, 'two-model-schema.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, 'two-model-schema.graphql'); await amplifyPush(projRoot); let meta = getBackendAmplifyMeta(projRoot); const { lastPushDirHash: beforeDirHash } = meta.api[projectName]; diff --git a/packages/amplify-e2e-tests/src/__tests__/api_2.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_2.test.ts index 968bff75d5e..2b1e2fe0b66 100644 --- a/packages/amplify-e2e-tests/src/__tests__/api_2.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/api_2.test.ts @@ -1,37 +1,36 @@ import { + addApiWithBlankSchemaAndConflictDetection, + addApiWithoutSchema, + addFunction, + addRestApi, + addSimpleDDB, amplifyPush, amplifyPushUpdate, + apiDisableDataStore, + apiEnableDataStore, + checkIfBucketExists, + createNewProjectDir, deleteProject, + deleteProjectDir, + enableAdminUI, + getAppSyncApi, + getLocalEnvInfo, + getProjectMeta, + getTransformConfig, initJSProjectWithProfile, listAttachedRolePolicies, listRolePolicies, + updateApiSchema, + updateAPIWithResolutionStrategyWithModels, updateAuthAddAdminQueries, } from 'amplify-e2e-core'; -import * as path from 'path'; -import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs'; import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; +import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs'; import gql from 'graphql-tag'; -const providerName = 'awscloudformation'; - -import { - addApiWithSchema, - addApiWithSchemaAndConflictDetection, - addRestApi, - updateAPIWithResolutionStrategy, - apiUpdateToggleDataStore, - addFunction, - addSimpleDDB, - checkIfBucketExists, - createNewProjectDir, - deleteProjectDir, - getAppSyncApi, - getProjectMeta, - getLocalEnvInfo, - getTransformConfig, - enableAdminUI, -} from 'amplify-e2e-core'; import { TRANSFORM_CURRENT_VERSION } from 'graphql-transformer-core'; import _ from 'lodash'; +import * as path from 'path'; +const providerName = 'awscloudformation'; // to deal with bug in cognito-identity-js (global as any).fetch = require('node-fetch'); @@ -55,7 +54,8 @@ describe('amplify add api (GraphQL)', () => { it('init a project with conflict detection enabled and a schema with @key, test update mutation', async () => { const name = `keyconflictdetection`; await initJSProjectWithProfile(projRoot, { name }); - await addApiWithSchemaAndConflictDetection(projRoot, 'key-conflict-detection.graphql'); + await addApiWithBlankSchemaAndConflictDetection(projRoot); + await updateApiSchema(projRoot, name, 'key-conflict-detection.graphql'); await amplifyPush(projRoot); const meta = getProjectMeta(projRoot); @@ -139,7 +139,8 @@ describe('amplify add api (GraphQL)', () => { it('init a project with conflict detection enabled and toggle disable', async () => { const name = `conflictdetection`; await initJSProjectWithProfile(projRoot, { name }); - await addApiWithSchemaAndConflictDetection(projRoot, 'simple_model.graphql'); + await addApiWithBlankSchemaAndConflictDetection(projRoot); + await updateApiSchema(projRoot, name, 'simple_model.graphql'); await amplifyPush(projRoot); @@ -165,7 +166,7 @@ describe('amplify add api (GraphQL)', () => { expect(transformConfig.ResolverConfig.project.ConflictHandler).toEqual('AUTOMERGE'); // remove datastore feature - await apiUpdateToggleDataStore(projRoot, {}); + await apiDisableDataStore(projRoot, {}); await amplifyPushUpdate(projRoot); const disableDSConfig = getTransformConfig(projRoot, name); expect(disableDSConfig).toBeDefined(); @@ -188,7 +189,8 @@ describe('amplify add api (GraphQL)', () => { // setupAdminUI await enableAdminUI(appId, envName, region); - await addApiWithSchemaAndConflictDetection(projRoot, 'simple_model.graphql'); + await addApiWithBlankSchemaAndConflictDetection(projRoot); + await updateApiSchema(projRoot, name, 'simple_model.graphql'); await amplifyPush(projRoot); meta = getProjectMeta(projRoot); @@ -209,7 +211,8 @@ describe('amplify add api (GraphQL)', () => { it('init a sync enabled project and update conflict resolution strategy', async () => { const name = `syncenabled`; await initJSProjectWithProfile(projRoot, { name }); - await addApiWithSchemaAndConflictDetection(projRoot, 'simple_model.graphql'); + await addApiWithBlankSchemaAndConflictDetection(projRoot); + await updateApiSchema(projRoot, name, 'simple_model.graphql'); let transformConfig = getTransformConfig(projRoot, name); expect(transformConfig).toBeDefined(); @@ -218,7 +221,7 @@ describe('amplify add api (GraphQL)', () => { expect(transformConfig.ResolverConfig.project.ConflictDetection).toEqual('VERSION'); expect(transformConfig.ResolverConfig.project.ConflictHandler).toEqual('AUTOMERGE'); - await updateAPIWithResolutionStrategy(projRoot, {}); + await updateAPIWithResolutionStrategyWithModels(projRoot, {}); transformConfig = getTransformConfig(projRoot, name); expect(transformConfig).toBeDefined(); @@ -246,7 +249,8 @@ describe('amplify add api (GraphQL)', () => { it('init a datastore enabled project and then remove datastore config in update', async () => { const name = 'withoutdatastore'; await initJSProjectWithProfile(projRoot, { name }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, name, 'simple_model.graphql'); await amplifyPush(projRoot); const meta = getProjectMeta(projRoot); @@ -267,7 +271,7 @@ describe('amplify add api (GraphQL)', () => { expect(_.isEmpty(withoutDSConfig.ResolverConfig)).toBe(true); // amplify update api to enable datastore - await apiUpdateToggleDataStore(projRoot, {}); + await apiEnableDataStore(projRoot, {}); let transformConfigWithDS = getTransformConfig(projRoot, name); expect(transformConfigWithDS).toBeDefined(); expect(transformConfigWithDS.ResolverConfig).toBeDefined(); diff --git a/packages/amplify-e2e-tests/src/__tests__/api_3.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_3.test.ts index 266fafd3423..4fc01468d68 100644 --- a/packages/amplify-e2e-tests/src/__tests__/api_3.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/api_3.test.ts @@ -8,7 +8,8 @@ import { updateHeadlessApi, getProjectSchema, removeHeadlessApi, - addApiWithSchema, + addApiWithoutSchema, + updateApiSchema, createNewProjectDir, deleteProjectDir, getAppSyncApi, @@ -39,8 +40,8 @@ describe('amplify add api (GraphQL)', () => { it('init a project and add the simple_model api, change transformer version to base version and push', async () => { const name = `simplemodelv${TRANSFORM_BASE_VERSION}`; await initJSProjectWithProfile(projRoot, { name }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); - + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, name, 'simple_model.graphql'); const transformConfig = getTransformConfig(projRoot, name); expect(transformConfig).toBeDefined(); expect(transformConfig.Version).toBeDefined(); @@ -123,8 +124,8 @@ describe('amplify add api (GraphQL)', () => { await initJSProjectWithProfile(projRoot, {}); await addHeadlessApi(projRoot, addApiRequest); await amplifyPush(projRoot); - await updateHeadlessApi(projRoot, updateApiRequest); - await amplifyPushUpdate(projRoot); + await updateHeadlessApi(projRoot, updateApiRequest, true); + await amplifyPushUpdate(projRoot, undefined, undefined, true); // verify const meta = getProjectMeta(projRoot); diff --git a/packages/amplify-e2e-tests/src/__tests__/api_4.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_4.test.ts index 177f658d14e..338fac8d53a 100644 --- a/packages/amplify-e2e-tests/src/__tests__/api_4.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/api_4.test.ts @@ -1,5 +1,6 @@ import { - addApiWithSchema, + addApiWithoutSchema, + updateApiSchema, amplifyPush, createNewProjectDir, deleteProject, @@ -31,7 +32,8 @@ describe('multi-key GSI behavior', () => { beforeEach(async () => { projRoot = await createNewProjectDir(projName); await initJSProjectWithProfile(projRoot, { name: projName }); - await addApiWithSchema(projRoot, 'multi-gsi.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projName, 'multi-gsi.graphql'); await amplifyPush(projRoot); appSyncClient = getAppSyncClientFromProj(projRoot); diff --git a/packages/amplify-e2e-tests/src/__tests__/api_5.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_5.test.ts index e17e45de418..0411b8760b4 100644 --- a/packages/amplify-e2e-tests/src/__tests__/api_5.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/api_5.test.ts @@ -14,11 +14,7 @@ import gql from 'graphql-tag'; const providerName = 'awscloudformation'; import { - addApiWithSchema, - addApiWithSchemaAndConflictDetection, addRestApi, - updateAPIWithResolutionStrategy, - apiUpdateToggleDataStore, addFunction, addSimpleDDB, checkIfBucketExists, @@ -64,15 +60,8 @@ describe('amplify add api (REST)', () => { expect(meta.function).toBeDefined(); let seenAtLeastOneFunc = false; for (let key of Object.keys(meta.function)) { - const { - service, - build, - lastBuildTimeStamp, - lastPackageTimeStamp, - distZipFilename, - lastPushTimeStamp, - lastPushDirHash, - } = meta.function[key]; + const { service, build, lastBuildTimeStamp, lastPackageTimeStamp, distZipFilename, lastPushTimeStamp, lastPushDirHash } = + meta.function[key]; expect(service).toBe('Lambda'); expect(build).toBeTruthy(); expect(lastBuildTimeStamp).toBeDefined(); diff --git a/packages/amplify-e2e-tests/src/__tests__/api_6.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_6.test.ts new file mode 100644 index 00000000000..79bbebf1ec9 --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/api_6.test.ts @@ -0,0 +1,84 @@ +import { + createNewProjectDir, + initJSProjectWithProfile, + addApiWithoutSchema, + amplifyPush, + deleteProject, + deleteProjectDir, + putItemInTable, + scanTable, + rebuildApi, + getProjectMeta, + updateApiSchema, + amplifyPushDestructiveApiUpdate, + addFunction, + amplifyPushAuth, +} from 'amplify-e2e-core'; + +const projName = 'apitest'; +let projRoot; +beforeEach(async () => { + projRoot = await createNewProjectDir(projName); + await initJSProjectWithProfile(projRoot, { name: projName }); + await addApiWithoutSchema(projRoot); + await amplifyPush(projRoot); +}); +afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); +}); + +describe('amplify reset api', () => { + it('recreates all model tables', async () => { + const projMeta = getProjectMeta(projRoot); + const apiId = projMeta?.api?.[projName]?.output?.GraphQLAPIIdOutput; + const region = projMeta?.providers?.awscloudformation?.Region; + expect(apiId).toBeDefined(); + expect(region).toBeDefined(); + const tableName = `Todo-${apiId}-integtest`; + await putItemInTable(tableName, region, { id: 'this is a test value' }); + const scanResultBefore = await scanTable(tableName, region); + expect(scanResultBefore.Items.length).toBe(1); + + await rebuildApi(projRoot, projName); + + const scanResultAfter = await scanTable(tableName, region); + expect(scanResultAfter.Items.length).toBe(0); + }); +}); + +describe('destructive updates flag', () => { + it('blocks destructive updates when flag not present', async () => { + updateApiSchema(projRoot, projName, 'simple_model_new_primary_key.graphql'); + await amplifyPushDestructiveApiUpdate(projRoot, false); + // success indicates that the command errored out + }); + + it('allows destructive updates when flag present', async () => { + updateApiSchema(projRoot, projName, 'simple_model_new_primary_key.graphql'); + await amplifyPushDestructiveApiUpdate(projRoot, true); + // success indicates that the push completed + }); + + it('disconnects and reconnects functions dependent on replaced table', async () => { + const functionName = 'funcTableDep'; + await addFunction( + projRoot, + { + name: functionName, + functionTemplate: 'Hello World', + additionalPermissions: { + permissions: ['storage'], + choices: ['api', 'storage'], + resources: ['Todo:@model(appsync)'], + operations: ['create', 'read', 'update', 'delete'], + }, + }, + 'nodejs', + ); + await amplifyPushAuth(projRoot); + updateApiSchema(projRoot, projName, 'simple_model_new_primary_key.graphql'); + await amplifyPushDestructiveApiUpdate(projRoot, false); + // success indicates that the push completed + }); +}); diff --git a/packages/amplify-e2e-tests/src/__tests__/feature-flags.test.ts b/packages/amplify-e2e-tests/src/__tests__/feature-flags.test.ts index 14cf60fab36..c66c2ee51db 100644 --- a/packages/amplify-e2e-tests/src/__tests__/feature-flags.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/feature-flags.test.ts @@ -1,5 +1,13 @@ import * as fs from 'fs-extra'; -import { initJSProjectWithProfile, deleteProject, addApiWithSchema, amplifyPush, amplifyPull, getAppId } from 'amplify-e2e-core'; +import { + initJSProjectWithProfile, + deleteProject, + addApiWithoutSchema, + updateApiSchema, + amplifyPush, + amplifyPull, + getAppId, +} from 'amplify-e2e-core'; import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; import { pathManager } from 'amplify-cli-core'; import { addEnvironment } from '../environment/env'; @@ -24,9 +32,11 @@ describe('feature flags', () => { it('push and pull with multiple config files for environments', async () => { await initJSProjectWithProfile(projRoot, { + name: 'apifeatureflag', disableAmplifyAppCreation: false, }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, 'apifeatureflag', 'simple_model.graphql'); const envName = 'test'; const cliJSONPath = pathManager.getCLIJSONFilePath(projRoot); diff --git a/packages/amplify-e2e-tests/src/__tests__/function_1.test.ts b/packages/amplify-e2e-tests/src/__tests__/function_1.test.ts index 793eebb1f93..7745d3d1bdb 100644 --- a/packages/amplify-e2e-tests/src/__tests__/function_1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/function_1.test.ts @@ -3,7 +3,7 @@ import { addFunction, functionBuild, addLambdaTrigger } from 'amplify-e2e-core'; import { addSimpleDDB } from 'amplify-e2e-core'; import { addKinesis } from 'amplify-e2e-core'; import { createNewProjectDir, deleteProjectDir, getProjectMeta, getFunction } from 'amplify-e2e-core'; -import { addApiWithSchema } from 'amplify-e2e-core'; +import { addApiWithoutSchema, updateApiSchema } from 'amplify-e2e-core'; import { appsyncGraphQLRequest } from 'amplify-e2e-core'; import { getCloudWatchLogs, putKinesisRecords, invokeFunction, getEventSourceMappings } from 'amplify-e2e-core'; @@ -60,8 +60,11 @@ describe('nodejs', () => { }); it('graphql mutation should result in trigger called in minimal AppSync + trigger infra', async () => { - await initJSProjectWithProfile(projRoot, {}); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await initJSProjectWithProfile(projRoot, { + name: 'graphqltriggerinfra', + }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, 'graphqltriggerinfra', 'simple_model.graphql'); await addFunction(projRoot, { functionTemplate: 'Lambda trigger', triggerType: 'DynamoDB' }, 'nodejs', addLambdaTrigger); await functionBuild(projRoot, {}); @@ -220,9 +223,12 @@ describe('nodejs', () => { await amplifyPushAuth(projRoot); const meta = getProjectMeta(projRoot); - const { Name: table1Name, Arn: table1Arn, Region: table1Region, StreamArn: table1StreamArn } = Object.keys(meta.storage).map( - key => meta.storage[key], - )[0].output; + const { + Name: table1Name, + Arn: table1Arn, + Region: table1Region, + StreamArn: table1StreamArn, + } = Object.keys(meta.storage).map(key => meta.storage[key])[0].output; expect(table1Name).toBeDefined(); expect(table1Arn).toBeDefined(); diff --git a/packages/amplify-e2e-tests/src/__tests__/function_2.test.ts b/packages/amplify-e2e-tests/src/__tests__/function_2.test.ts index 1eb2c43e7a6..d92fb6842f7 100644 --- a/packages/amplify-e2e-tests/src/__tests__/function_2.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/function_2.test.ts @@ -1,5 +1,6 @@ import { - addApiWithSchema, + addApiWithoutSchema, + updateApiSchema, addApi, addAuthWithDefault, addDDBWithTrigger, @@ -28,9 +29,8 @@ import { addAuthWithGroupsAndAdminAPI, getFunction, loadFunctionTestFile, + createRandomName, } from 'amplify-e2e-core'; -import fs from 'fs-extra'; -import path from 'path'; import _ from 'lodash'; describe('nodejs', () => { @@ -120,14 +120,17 @@ describe('nodejs', () => { }); it('lambda with dynamoDB permissions should be able to scan ddb', async () => { - await initJSProjectWithProfile(projRoot, {}); + await initJSProjectWithProfile(projRoot, { + name: 'dynamodbscan', + }); const random = Math.floor(Math.random() * 10000); const fnName = `integtestfn${random}`; const ddbName = `integtestddb${random}`; // test ability to scan both appsync @model-backed and regular ddb tables - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, 'dynamodbscan', 'simple_model.graphql'); await addSimpleDDB(projRoot, { name: ddbName }); await addFunction( @@ -185,7 +188,10 @@ describe('nodejs', () => { }); it('existing lambda updated with additional permissions should be able to scan ddb', async () => { - await initJSProjectWithProfile(projRoot, {}); + const appName = createRandomName(); + await initJSProjectWithProfile(projRoot, { + name: appName, + }); const random = Math.floor(Math.random() * 10000); const fnName = `integtestfn${random}`; @@ -209,7 +215,8 @@ describe('nodejs', () => { expect(functionName).toBeDefined(); expect(region).toBeDefined(); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, appName, 'simple_model.graphql'); await updateFunction( projRoot, { @@ -241,8 +248,11 @@ describe('nodejs', () => { }); it('@model-backed lambda function should generate envvars TODOTABLE_NAME, TODOTABLE_ARN, GRAPHQLAPIIDOUTPUT', async () => { - await initJSProjectWithProfile(projRoot, {}); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await initJSProjectWithProfile(projRoot, { + name: 'modelbackedlambda', + }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, 'modelbackedlambda', 'simple_model.graphql'); const random = Math.floor(Math.random() * 10000); const fnName = `integtestfn${random}`; diff --git a/packages/amplify-e2e-tests/src/__tests__/function_5.test.ts b/packages/amplify-e2e-tests/src/__tests__/function_5.test.ts index a12eeb99fa0..986210b2e08 100644 --- a/packages/amplify-e2e-tests/src/__tests__/function_5.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/function_5.test.ts @@ -1,6 +1,6 @@ import { addFunction, - addApiWithSchema, + addApiWithoutSchema, amplifyPull, amplifyPushAuth, amplifyPush, @@ -83,9 +83,13 @@ describe('test dependency in root stack', () => { }); it('init a project with api and function and update the @model and add function access to @model ', async () => { - await initJSProjectWithProfile(projRoot, {}); const projectName = 'mytestapi'; - await addApiWithSchema(projRoot, 'simple_model.graphql', { apiName: projectName }); + await initJSProjectWithProfile(projRoot, { + name: projectName, + }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, 'simple_model.graphql'); + const random = Math.floor(Math.random() * 10000); const fnName = `integtestfn${random}`; await addFunction( @@ -113,6 +117,6 @@ describe('test dependency in root stack', () => { }, 'nodejs', ); - await amplifyPushWithoutCodegen(projRoot); + await amplifyPushWithoutCodegen(projRoot, undefined, true); }); }); diff --git a/packages/amplify-e2e-tests/src/__tests__/function_9.test.ts b/packages/amplify-e2e-tests/src/__tests__/function_9.test.ts index 30e89832eac..cd7df53717e 100644 --- a/packages/amplify-e2e-tests/src/__tests__/function_9.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/function_9.test.ts @@ -1,5 +1,5 @@ import { - addApiWithSchema, + addApiWithoutSchema, addApi, addAuthWithDefault, addDDBWithTrigger, @@ -28,6 +28,8 @@ import { addAuthWithGroupsAndAdminAPI, getFunction, loadFunctionTestFile, + updateApiSchema, + createRandomName, } from 'amplify-e2e-core'; import fs from 'fs-extra'; import path from 'path'; @@ -80,8 +82,12 @@ describe('nodejs', () => { }); it('adding api and storage permissions should not add duplicates to CFN', async () => { - await initJSProjectWithProfile(projRoot, {}); - await addApiWithSchema(projRoot, 'two-model-schema.graphql'); + const appName = createRandomName(); + await initJSProjectWithProfile(projRoot, { + name: appName, + }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, appName, 'two-model-schema.graphql'); const random = Math.floor(Math.random() * 10000); const fnName = `integtestfn${random}`; @@ -143,8 +149,12 @@ describe('nodejs', () => { }); it('function dependencies should be preserved when not editing permissions during `amplify update function`', async () => { - await initJSProjectWithProfile(projRoot, {}); - await addApiWithSchema(projRoot, 'two-model-schema.graphql'); + const appName = createRandomName(); + await initJSProjectWithProfile(projRoot, { + name: appName, + }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, appName, 'two-model-schema.graphql'); const random = Math.floor(Math.random() * 10000); const fnName = `integtestfn${random}`; diff --git a/packages/amplify-e2e-tests/src/__tests__/geo-add.test.ts b/packages/amplify-e2e-tests/src/__tests__/geo-add.test.ts index 27750c6d512..7571895b5a3 100644 --- a/packages/amplify-e2e-tests/src/__tests__/geo-add.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/geo-add.test.ts @@ -1,4 +1,4 @@ -import { +import { createNewProjectDir, deleteProject, deleteProjectDir, @@ -11,7 +11,7 @@ import { getMap, getPlaceIndex, generateRandomShortId, - getGeoJSConfiguration + getGeoJSConfiguration, } from 'amplify-e2e-core'; import { existsSync } from 'fs'; import path from 'path'; diff --git a/packages/amplify-e2e-tests/src/__tests__/geo-remove.test.ts b/packages/amplify-e2e-tests/src/__tests__/geo-remove.test.ts index ac2903614c5..0368556fe65 100644 --- a/packages/amplify-e2e-tests/src/__tests__/geo-remove.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/geo-remove.test.ts @@ -1,4 +1,4 @@ -import { +import { createNewProjectDir, deleteProject, deleteProjectDir, @@ -14,7 +14,7 @@ import { removeFirstDefaultMap, removeFirstDefaultPlaceIndex, generateResourceIdsInOrder, - getGeoJSConfiguration + getGeoJSConfiguration, } from 'amplify-e2e-core'; import { existsSync } from 'fs'; import path from 'path'; diff --git a/packages/amplify-e2e-tests/src/__tests__/geo-update.test.ts b/packages/amplify-e2e-tests/src/__tests__/geo-update.test.ts index a3d4c115eb0..5858c8db4e4 100644 --- a/packages/amplify-e2e-tests/src/__tests__/geo-update.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/geo-update.test.ts @@ -1,4 +1,4 @@ -import { +import { createNewProjectDir, deleteProject, deleteProjectDir, @@ -15,7 +15,7 @@ import { updateSecondMapAsDefault, updateSecondPlaceIndexAsDefault, generateResourceIdsInOrder, - getGeoJSConfiguration + getGeoJSConfiguration, } from 'amplify-e2e-core'; import { existsSync } from 'fs'; import path from 'path'; diff --git a/packages/amplify-e2e-tests/src/__tests__/import_s3_1.test.ts b/packages/amplify-e2e-tests/src/__tests__/import_s3_1.test.ts index fab0230efaf..5bc845d0752 100644 --- a/packages/amplify-e2e-tests/src/__tests__/import_s3_1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/import_s3_1.test.ts @@ -13,11 +13,9 @@ import { deleteProject, deleteProjectDir, getAppId, - getTeamProviderInfo, initJSProjectWithProfile, } from 'amplify-e2e-core'; import { randomizedFunctionName } from '../schema-api-directives/functionTester'; -import { addEnvironmentWithImportedAuth, checkoutEnvironment, removeEnvironment } from '../environment/env'; import { expectLocalAndCloudMetaFilesMatching, expectLocalAndPulledBackendConfigMatching, @@ -34,8 +32,6 @@ import { expectLocalTeamInfoHasOnlyAuthCategoryAndNoStorage, getS3ResourceName, expectS3LocalAndOGMetaFilesOutputMatching, - headlessPullExpectError, - headlessPull, } from '../import-helpers'; const profileName = 'amplify-integ-test-user'; diff --git a/packages/amplify-e2e-tests/src/__tests__/import_s3_2.test.ts b/packages/amplify-e2e-tests/src/__tests__/import_s3_2.test.ts index 8805e3e3fc8..a18b829ddc8 100644 --- a/packages/amplify-e2e-tests/src/__tests__/import_s3_2.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/import_s3_2.test.ts @@ -1,12 +1,7 @@ -import * as path from 'path'; -import * as fs from 'fs-extra'; -import { $TSObject, JSONUtilities } from 'amplify-cli-core'; import { addAuthWithDefault, - addFunction, addS3StorageWithSettings, AddStorageSettings, - amplifyPull, amplifyPushAuth, amplifyStatus, createNewProjectDir, @@ -16,26 +11,19 @@ import { getTeamProviderInfo, initJSProjectWithProfile, } from 'amplify-e2e-core'; -import { randomizedFunctionName } from '../schema-api-directives/functionTester'; import { addEnvironmentWithImportedAuth, checkoutEnvironment, removeEnvironment } from '../environment/env'; import { + createStorageSettings, expectLocalAndCloudMetaFilesMatching, expectLocalAndPulledBackendConfigMatching, - getShortId, - readRootStack, - createStorageSettings, - StorageProjectDetails, + expectS3LocalAndOGMetaFilesOutputMatching, getOGStorageProjectDetails, - importS3, + getShortId, getStorageProjectDetails, - expectStorageProjectDetailsMatch, - removeImportedS3WithDefault, - expectNoStorageInMeta, - expectLocalTeamInfoHasOnlyAuthCategoryAndNoStorage, - getS3ResourceName, - expectS3LocalAndOGMetaFilesOutputMatching, - headlessPullExpectError, headlessPull, + headlessPullExpectError, + importS3, + StorageProjectDetails, } from '../import-helpers'; const profileName = 'amplify-integ-test-user'; diff --git a/packages/amplify-e2e-tests/src/__tests__/import_s3_3.test.ts b/packages/amplify-e2e-tests/src/__tests__/import_s3_3.test.ts new file mode 100644 index 00000000000..a4c1fddd1eb --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/import_s3_3.test.ts @@ -0,0 +1,228 @@ +import * as aws from 'aws-sdk'; +import { + addAuthWithDefault, + amplifyPushAuth, + amplifyStatus, + createNewProjectDir, + deleteProject, + deleteProjectDir, + initJSProjectWithProfile, + importHeadlessStorage, + removeHeadlessStorage, +} from 'amplify-e2e-core'; +import { + expectLocalAndCloudMetaFilesMatching, + getShortId, + getStorageProjectDetails, + expectNoStorageInMeta, + expectLocalTeamInfoHasOnlyAuthCategoryAndNoStorage, + getS3ResourceName, +} from '../import-helpers'; + +const profileName = 'amplify-integ-test-user'; + +describe('headless s3 import', () => { + const projectPrefix = 'sssheadimp'; + const bucketPrefix = 'sss-headless-import-'; + + const projectSettings = { + name: projectPrefix, + }; + + let projectRoot: string; + let ignoreProjectDeleteErrors: boolean = false; + let bucketNameToImport: string; + let bucketLocation: string; + + beforeAll(async () => { + const shortId = getShortId(); + bucketNameToImport = `${bucketPrefix}${shortId}`; + + const credentials = new aws.SharedIniFileCredentials({ profile: profileName }); + aws.config.credentials = credentials; + + const s3 = new aws.S3(); + + await s3 + .createBucket({ + Bucket: bucketNameToImport, + }) + .promise(); + + const locationResponse = await s3 + .getBucketLocation({ + Bucket: bucketNameToImport, + }) + .promise(); + + // For us-east-1 buckets the LocationConstraint is always emtpy, we have to return a + // region in every case. + // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html + if ( + locationResponse.LocationConstraint === undefined || + locationResponse.LocationConstraint === '' || + locationResponse.LocationConstraint === null + ) { + bucketLocation = 'us-east-1'; + } else { + bucketLocation = locationResponse.LocationConstraint; + } + }); + + afterAll(async () => { + // Delete bucket + const s3 = new aws.S3(); + + await s3 + .deleteBucket({ + Bucket: bucketNameToImport, + }) + .promise(); + }); + + beforeEach(async () => { + projectRoot = await createNewProjectDir(projectPrefix); + ignoreProjectDeleteErrors = false; + }); + + afterEach(async () => { + try { + await deleteProject(projectRoot); + } catch (error) { + // In some tests where project initialization fails it can lead to errors on cleanup which we + // can ignore if set by the test + if (!ignoreProjectDeleteErrors) { + throw error; + } + } + + deleteProjectDir(projectRoot); + }); + + it('import storage when no auth resource is in the project', async () => { + await initJSProjectWithProfile(projectRoot, projectSettings); + + const processResult = await importHeadlessStorage( + projectRoot, + { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + bucketName: bucketNameToImport, + }, + }, + false, + ); + + expect(processResult.exitCode).toBe(1); + expect(processResult.stdout).toContain( + 'Cannot headlessly import storage resource without an existing auth resource. It can be added with "amplify add auth"', + ); + }); + + it('import storage when there is already a storage resource in the project', async () => { + await initJSProjectWithProfile(projectRoot, projectSettings); + await addAuthWithDefault(projectRoot, {}); + + const processResult = await importHeadlessStorage( + projectRoot, + { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + bucketName: bucketNameToImport, + }, + }, + false, + ); + + expect(processResult.exitCode).toBe(0); + expect(processResult.stdout).toEqual(''); + + const processResultFail = await importHeadlessStorage( + projectRoot, + { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + bucketName: bucketNameToImport, + }, + }, + false, + ); + + expect(processResultFail.exitCode).toBe(1); + expect(processResultFail.stdout).toContain('Amazon S3 storage was already added to your project'); + }); + + it('import storage with non-existent bucket`', async () => { + await initJSProjectWithProfile(projectRoot, projectSettings); + await addAuthWithDefault(projectRoot, {}); + + const fakeBucketName = `fake-bucket-name-${getShortId()}`; + + const processResult = await importHeadlessStorage( + projectRoot, + { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + bucketName: fakeBucketName, + }, + }, + false, + ); + + expect(processResult.exitCode).toBe(1); + expect(processResult.stdout).toContain(`The specified bucket: "${fakeBucketName}" does not exist.`); + }); + + it('import storage successfully and push, remove storage and push`', async () => { + await initJSProjectWithProfile(projectRoot, projectSettings); + await addAuthWithDefault(projectRoot, {}); + + const processResult = await importHeadlessStorage(projectRoot, { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + bucketName: bucketNameToImport, + }, + }); + + expect(processResult.exitCode).toBe(0); + expect(processResult.stdout).toEqual(''); + + await amplifyStatus(projectRoot, 'Import'); + await amplifyPushAuth(projectRoot); + await amplifyStatus(projectRoot, 'No Change'); + + const storageResourceName = getS3ResourceName(projectRoot); + + expectLocalAndCloudMetaFilesMatching(projectRoot); + + const projectDetails = getStorageProjectDetails(projectRoot); + + expect(projectDetails.meta.BucketName).toEqual(bucketNameToImport); + expect(projectDetails.meta.Region).toEqual(bucketLocation); + + expect(projectDetails.team.bucketName).toEqual(bucketNameToImport); + expect(projectDetails.team.region).toEqual(bucketLocation); + + await removeHeadlessStorage(projectRoot, { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + resourceName: storageResourceName, + // deleteBucketAndContents: true, + }, + }); + + await amplifyStatus(projectRoot, 'Unlink'); + + await amplifyPushAuth(projectRoot); + + expectNoStorageInMeta(projectRoot); + + expectLocalTeamInfoHasOnlyAuthCategoryAndNoStorage(projectRoot); + }); +}); diff --git a/packages/amplify-e2e-tests/src/__tests__/migration/api.connection.migration.test.ts b/packages/amplify-e2e-tests/src/__tests__/migration/api.connection.migration.test.ts index bcbf7ded7de..809aea44e16 100644 --- a/packages/amplify-e2e-tests/src/__tests__/migration/api.connection.migration.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/migration/api.connection.migration.test.ts @@ -1,5 +1,5 @@ import { initJSProjectWithProfile, deleteProject, amplifyPush, amplifyPushUpdate, addFeatureFlag } from 'amplify-e2e-core'; -import { addApiWithSchema, updateApiSchema } from 'amplify-e2e-core'; +import { addApiWithoutSchema, updateApiSchema } from 'amplify-e2e-core'; import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; describe('amplify add api', () => { @@ -21,7 +21,8 @@ describe('amplify add api', () => { await initJSProjectWithProfile(projRoot, { name: projectName }); addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema1); await expect( @@ -40,7 +41,8 @@ describe('amplify add api', () => { await initJSProjectWithProfile(projRoot, { name: projectName }); addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema1); await expect( diff --git a/packages/amplify-e2e-tests/src/__tests__/migration/api.connection.migration2.test.ts b/packages/amplify-e2e-tests/src/__tests__/migration/api.connection.migration2.test.ts index e23854dc03d..4573978141b 100644 --- a/packages/amplify-e2e-tests/src/__tests__/migration/api.connection.migration2.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/migration/api.connection.migration2.test.ts @@ -1,5 +1,5 @@ import { initJSProjectWithProfile, deleteProject, amplifyPush, amplifyPushUpdate, addFeatureFlag } from 'amplify-e2e-core'; -import { addApiWithSchema, updateApiSchema } from 'amplify-e2e-core'; +import { addApiWithoutSchema, updateApiSchema } from 'amplify-e2e-core'; import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; describe('amplify add api', () => { @@ -21,7 +21,8 @@ describe('amplify add api', () => { await initJSProjectWithProfile(projRoot, { name: projectName }); addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema1); await expect( @@ -41,7 +42,8 @@ describe('amplify add api', () => { await initJSProjectWithProfile(projRoot, { name: projectName }); addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema1); await amplifyPushUpdate(projRoot, /GraphQL endpoint:.*/); diff --git a/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration1.test.ts b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration1.test.ts index d323a06dac6..2664f430aa7 100644 --- a/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration1.test.ts @@ -1,5 +1,5 @@ import { initJSProjectWithProfile, deleteProject, amplifyPush, amplifyPushUpdate, addFeatureFlag } from 'amplify-e2e-core'; -import { addApiWithSchema, updateApiSchema } from 'amplify-e2e-core'; +import { addApiWithoutSchema, updateApiSchema } from 'amplify-e2e-core'; import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; describe('amplify add api', () => { @@ -23,7 +23,8 @@ describe('amplify add api', () => { // testing this with old behavior with named lsi key addFeatureFlag(projRoot, 'graphqltransformer', 'secondarykeyasgsi', false); - await addApiWithSchema(projRoot, initialSchema, { apiKeyExpirationDays: 2 }); + await addApiWithoutSchema(projRoot, { apiKeyExpirationDays: 2 }); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema1); @@ -43,7 +44,8 @@ describe('amplify add api', () => { await initJSProjectWithProfile(projRoot, { name: projectName }); addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema1); @@ -60,7 +62,8 @@ describe('amplify add api', () => { await initJSProjectWithProfile(projRoot, { name: projectName }); addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema1); @@ -77,7 +80,8 @@ describe('amplify add api', () => { await initJSProjectWithProfile(projRoot, { name: projectName }); addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema1); diff --git a/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration2.test.ts b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration2.test.ts index eb7f82e437b..14a8345ec1c 100644 --- a/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration2.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration2.test.ts @@ -1,12 +1,12 @@ import { initJSProjectWithProfile, deleteProject, amplifyPush, amplifyPushUpdate, addFeatureFlag } from 'amplify-e2e-core'; -import { addApiWithSchema, updateApiSchema, getProjectMeta } from 'amplify-e2e-core'; +import { addApiWithoutSchema, updateApiSchema, getProjectMeta } from 'amplify-e2e-core'; import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; import { addEnvironment } from '../../environment/env'; describe('amplify add api', () => { let projRoot: string; beforeEach(async () => { - projRoot = await createNewProjectDir('api-key-migration'); + projRoot = await createNewProjectDir('api-key-migration-2'); }); afterEach(async () => { @@ -19,7 +19,8 @@ describe('amplify add api', () => { const initial_schema = 'migrations_key/three_gsi_model_schema.graphql'; const nextSchema = 'migrations_key/four_gsi_model_schema.graphql'; await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, initial_schema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initial_schema); await amplifyPush(projRoot); updateApiSchema(projRoot, projectName, nextSchema); @@ -32,7 +33,8 @@ describe('amplify add api', () => { const nextSchema1 = 'migrations_key/cant_change_key_schema.graphql'; await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); await addEnvironment(projRoot, { envName: 'test' }); updateApiSchema(projRoot, projectName, nextSchema1); @@ -44,114 +46,4 @@ describe('amplify add api', () => { expect(GraphQLAPIEndpointOutput).toBeDefined(); expect(GraphQLAPIKeyOutput).toBeDefined(); }); - - it('init project, run invalid migration trying to add more than one gsi, and check for error', async () => { - const projectName = 'migratingkey'; - const initialSchema = 'migrations_key/initial_schema.graphql'; - const nextSchema1 = 'migrations_key/cant_add_more_gsi.graphql'; - - await initJSProjectWithProfile(projRoot, { name: projectName }); - addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - - await addApiWithSchema(projRoot, initialSchema); - await amplifyPush(projRoot); - - updateApiSchema(projRoot, projectName, nextSchema1); - await expect( - amplifyPushUpdate( - projRoot, - /Attempting to add more than 1 global secondary index SomeGSI1 and someGSI2 on the TodoTable table in the Todo stack.*/, - ), - ).rejects.toThrowError('Process exited with non zero exit code 1'); - }); - - it('init project, run invalid migration trying to delete more than one gsi, and check for error', async () => { - const projectName = 'migratingkey1'; - const initialSchema = 'migrations_key/initial_schema1.graphql'; - const nextSchema1 = 'migrations_key/cant_remove_more_gsi.graphql'; - - await initJSProjectWithProfile(projRoot, { name: projectName }); - addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - - await addApiWithSchema(projRoot, initialSchema); - await amplifyPush(projRoot); - - updateApiSchema(projRoot, projectName, nextSchema1); - await expect( - amplifyPushUpdate( - projRoot, - /Attempting to delete more than 1 global secondary index SomeGSI1 and someGSI2 on the TodoTable table in the Todo stack.*/, - ), - ).rejects.toThrowError('Process exited with non zero exit code 1'); - }); - - it('init project, run invalid migration trying to add and delete gsi, and check for error', async () => { - const projectName = 'migratingkey2'; - const initialSchema = 'migrations_key/initial_schema.graphql'; - const nextSchema1 = 'migrations_key/cant_update_delete_gsi.graphql'; - - await initJSProjectWithProfile(projRoot, { name: projectName }); - addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - - await addApiWithSchema(projRoot, initialSchema); - await amplifyPush(projRoot); - updateApiSchema(projRoot, projectName, nextSchema1); - await expect( - amplifyPushUpdate( - projRoot, - /Attempting to add and delete a global secondary index SomeGSI1 and someGSI2 on the TodoTable table in the Todo stack.*/, - ), - ).rejects.toThrowError('Process exited with non zero exit code 1'); - }); - - it('init project, run invalid migration when adding more than one gsi on the same table', async () => { - const projectName = 'invalidgsiupdate'; - - const initialSchema = 'migrations_key/simple_key.graphql'; - const nextSchema = 'migrations_key/cant_add_multiple_gsi.graphql'; - - await initJSProjectWithProfile(projRoot, { name: projectName }); - addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - - await addApiWithSchema(projRoot, initialSchema); - await amplifyPush(projRoot); - - updateApiSchema(projRoot, projectName, nextSchema); - await expect( - amplifyPushUpdate( - projRoot, - /Attempting to mutate more than 1 global secondary index at the same time on the TodoTable table in the Todo stack.*/, - ), - ).rejects.toThrowError('Process exited with non zero exit code 1'); - }); - - it('init project, allow updated two types with new GSIs', async () => { - const projectName = 'twotableupdategsi'; - const initialSchema = 'migrations_key/two_key_model_schema.graphql'; - const nextSchema = 'migrations_key/four_key_model_schema.graphql'; - - await initJSProjectWithProfile(projRoot, { name: projectName }); - addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - - await addApiWithSchema(projRoot, initialSchema); - await amplifyPush(projRoot); - - updateApiSchema(projRoot, projectName, nextSchema); - await amplifyPushUpdate(projRoot, /GraphQL endpoint:.*/); - }); - - it('init project, run valid migration adding a GSI', async () => { - const projectName = 'validaddinggsi'; - const initialSchema = 'migrations_key/initial_schema.graphql'; - const nextSchema1 = 'migrations_key/add_gsi.graphql'; - - await initJSProjectWithProfile(projRoot, { name: projectName }); - addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); - - await addApiWithSchema(projRoot, initialSchema); - await amplifyPush(projRoot); - - updateApiSchema(projRoot, projectName, nextSchema1); - await amplifyPushUpdate(projRoot, /GraphQL endpoint:.*/); - }); }); diff --git a/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration3.test.ts b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration3.test.ts new file mode 100644 index 00000000000..f3a59e0fcee --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration3.test.ts @@ -0,0 +1,57 @@ +import { initJSProjectWithProfile, deleteProject, amplifyPush, amplifyPushUpdate, addFeatureFlag } from 'amplify-e2e-core'; +import { addApiWithoutSchema, updateApiSchema, getProjectMeta } from 'amplify-e2e-core'; +import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; +import { addEnvironment } from '../../environment/env'; + +describe('amplify add api', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('api-key-migration-3'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('init project, run invalid migration trying to delete more than one gsi, and check for error', async () => { + const projectName = 'migratingkey1'; + const initialSchema = 'migrations_key/initial_schema1.graphql'; + const nextSchema1 = 'migrations_key/cant_remove_more_gsi.graphql'; + + await initJSProjectWithProfile(projRoot, { name: projectName }); + addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); + + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); + await amplifyPush(projRoot); + + updateApiSchema(projRoot, projectName, nextSchema1); + await expect( + amplifyPushUpdate( + projRoot, + /Attempting to delete more than 1 global secondary index SomeGSI1 and someGSI2 on the TodoTable table in the Todo stack.*/, + ), + ).rejects.toThrowError('Process exited with non zero exit code 1'); + }); + + it('init project, run invalid migration trying to add and delete gsi, and check for error', async () => { + const projectName = 'migratingkey2'; + const initialSchema = 'migrations_key/initial_schema.graphql'; + const nextSchema1 = 'migrations_key/cant_update_delete_gsi.graphql'; + + await initJSProjectWithProfile(projRoot, { name: projectName }); + addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); + + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); + await amplifyPush(projRoot); + updateApiSchema(projRoot, projectName, nextSchema1); + await expect( + amplifyPushUpdate( + projRoot, + /Attempting to add and delete a global secondary index SomeGSI1 and someGSI2 on the TodoTable table in the Todo stack.*/, + ), + ).rejects.toThrowError('Process exited with non zero exit code 1'); + }); +}); diff --git a/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration4.test.ts b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration4.test.ts new file mode 100644 index 00000000000..8af292182db --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration4.test.ts @@ -0,0 +1,48 @@ +import { initJSProjectWithProfile, deleteProject, amplifyPush, amplifyPushUpdate, addFeatureFlag } from 'amplify-e2e-core'; +import { addApiWithoutSchema, updateApiSchema, getProjectMeta } from 'amplify-e2e-core'; +import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; +import { addEnvironment } from '../../environment/env'; + +describe('amplify add api', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('api-key-migration-4'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('init project, allow updated two types with new GSIs', async () => { + const projectName = 'twotableupdategsi'; + const initialSchema = 'migrations_key/two_key_model_schema.graphql'; + const nextSchema = 'migrations_key/four_key_model_schema.graphql'; + + await initJSProjectWithProfile(projRoot, { name: projectName }); + addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); + + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); + await amplifyPush(projRoot); + + updateApiSchema(projRoot, projectName, nextSchema); + await amplifyPushUpdate(projRoot, /GraphQL endpoint:.*/); + }); + + it('init project, run valid migration adding a GSI', async () => { + const projectName = 'validaddinggsi'; + const initialSchema = 'migrations_key/initial_schema.graphql'; + const nextSchema1 = 'migrations_key/add_gsi.graphql'; + + await initJSProjectWithProfile(projRoot, { name: projectName }); + addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); + + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); + await amplifyPush(projRoot); + + updateApiSchema(projRoot, projectName, nextSchema1); + await amplifyPushUpdate(projRoot, /GraphQL endpoint:.*/); + }); +}); diff --git a/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration5.test.ts b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration5.test.ts new file mode 100644 index 00000000000..0f4a4fdccde --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/migration/api.key.migration5.test.ts @@ -0,0 +1,59 @@ +import { initJSProjectWithProfile, deleteProject, amplifyPush, amplifyPushUpdate, addFeatureFlag } from 'amplify-e2e-core'; +import { addApiWithoutSchema, updateApiSchema, getProjectMeta } from 'amplify-e2e-core'; +import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; +import { addEnvironment } from '../../environment/env'; + +describe('amplify add api', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('api-key-migration-5'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('init project, run invalid migration trying to add more than one gsi, and check for error', async () => { + const projectName = 'migratingkey3'; + const initialSchema = 'migrations_key/initial_schema.graphql'; + const nextSchema1 = 'migrations_key/cant_add_more_gsi.graphql'; + + await initJSProjectWithProfile(projRoot, { name: projectName }); + addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); + + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); + await amplifyPush(projRoot); + + updateApiSchema(projRoot, projectName, nextSchema1); + await expect( + amplifyPushUpdate( + projRoot, + /Attempting to add more than 1 global secondary index SomeGSI1 and someGSI2 on the TodoTable table in the Todo stack.*/, + ), + ).rejects.toThrowError('Process exited with non zero exit code 1'); + }); + + it('init project, run invalid migration when adding more than one gsi on the same table', async () => { + const projectName = 'invalidgsiupdate'; + + const initialSchema = 'migrations_key/simple_key.graphql'; + const nextSchema = 'migrations_key/cant_add_multiple_gsi.graphql'; + + await initJSProjectWithProfile(projRoot, { name: projectName }); + addFeatureFlag(projRoot, 'graphqltransformer', 'enableiterativegsiupdates', false); + + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); + await amplifyPush(projRoot); + + updateApiSchema(projRoot, projectName, nextSchema); + await expect( + amplifyPushUpdate( + projRoot, + /Attempting to mutate more than 1 global secondary index at the same time on the TodoTable table in the Todo stack.*/, + ), + ).rejects.toThrowError('Process exited with non zero exit code 1'); + }); +}); diff --git a/packages/amplify-e2e-tests/src/__tests__/pull.test.ts b/packages/amplify-e2e-tests/src/__tests__/pull.test.ts index eaba219a335..ec3e35dddae 100644 --- a/packages/amplify-e2e-tests/src/__tests__/pull.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/pull.test.ts @@ -1,6 +1,7 @@ import { getAppId, - addApiWithSchema, + addApiWithoutSchema, + updateApiSchema, amplifyPull, amplifyPush, createNewProjectDir, @@ -24,8 +25,12 @@ describe('amplify pull', () => { }); it('pulling twice with noUpdateBackend does not re-prompt', async () => { - await initJSProjectWithProfile(projRoot, { disableAmplifyAppCreation: false }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await initJSProjectWithProfile(projRoot, { + disableAmplifyAppCreation: false, + name: 'testapi', + }); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, 'testapi', 'simple_model.graphql'); await amplifyPush(projRoot); const appId = getAppId(projRoot); await amplifyPull(projRoot2, { appId, emptyDir: true, noUpdateBackend: true }); diff --git a/packages/amplify-e2e-tests/src/__tests__/resolvers.test.ts b/packages/amplify-e2e-tests/src/__tests__/resolvers.test.ts new file mode 100644 index 00000000000..6ae626daa60 --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/resolvers.test.ts @@ -0,0 +1,137 @@ +import { + initJSProjectWithProfile, + deleteProject, + createNewProjectDir, + deleteProjectDir, + addFeatureFlag, + addApiWithoutSchema, + addCustomResolver, + apiGqlCompile, + updateApiSchema, + writeToCustomResourcesJson, +} from 'amplify-e2e-core'; +import { join } from 'path'; +import * as fs from 'fs-extra'; + +describe('user created resolvers', () => { + let projectDir: string; + let apiName = 'simpleapi'; + + beforeEach(async () => { + projectDir = await createNewProjectDir('overrideresolvers'); + await initJSProjectWithProfile(projectDir, {}); + addFeatureFlag(projectDir, 'graphqltransformer', 'useexperimentalpipelinedtransformer', true); + }); + + afterEach(async () => { + await deleteProject(projectDir); + deleteProjectDir(projectDir); + }); + + describe('overriding generated resolvers', () => { + it('adds the overwritten resolver to the build', async () => { + const resolverName = 'Query.listTodos.req.vtl'; + const resolver = '$util.unauthorized()'; + const generatedResolverPath = join(projectDir, 'amplify', 'backend', 'api', apiName, 'build', 'pipelineFunctions', resolverName); + + await addApiWithoutSchema(projectDir, { apiName }); + await updateApiSchema(projectDir, apiName, 'simple_model.graphql'); + await apiGqlCompile(projectDir, true); + + expect(fs.readFileSync(generatedResolverPath).toString()).not.toEqual(resolver); + + addCustomResolver(projectDir, apiName, resolverName, resolver); + await apiGqlCompile(projectDir, true); + + expect(fs.readFileSync(generatedResolverPath).toString()).toEqual(resolver); + }); + }); + + describe('custom resolvers', () => { + it('adds the overwritten resolver to the build', async () => { + const resolverReqName = 'Query.commentsForTodo.req.vtl'; + const resolverResName = 'Query.commentsForTodo.res.vtl'; + + const resolverReq = '$util.unauthorized()'; + const resolverRes = '$util.toJson({})'; + + const generatedReqResolverPath = join( + projectDir, + 'amplify', + 'backend', + 'api', + apiName, + 'build', + 'pipelineFunctions', + resolverReqName, + ); + const generatedResResolverPath = join( + projectDir, + 'amplify', + 'backend', + 'api', + apiName, + 'build', + 'pipelineFunctions', + resolverResName, + ); + const stackPath = join(projectDir, 'amplify', 'backend', 'api', apiName, 'build', 'stacks', 'CustomResources.json'); + + const Resources = { + Resources: { + QueryCommentsForTodoResolver: { + Type: 'AWS::AppSync::Resolver', + Properties: { + ApiId: { + Ref: 'AppSyncApiId', + }, + DataSourceName: 'CommentTable', + TypeName: 'Query', + FieldName: 'commentsForTodo', + RequestMappingTemplateS3Location: { + 'Fn::Sub': [ + 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.req.vtl', + { + S3DeploymentBucket: { + Ref: 'S3DeploymentBucket', + }, + S3DeploymentRootKey: { + Ref: 'S3DeploymentRootKey', + }, + }, + ], + }, + ResponseMappingTemplateS3Location: { + 'Fn::Sub': [ + 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.res.vtl', + { + S3DeploymentBucket: { + Ref: 'S3DeploymentBucket', + }, + S3DeploymentRootKey: { + Ref: 'S3DeploymentRootKey', + }, + }, + ], + }, + }, + }, + }, + }; + + await addApiWithoutSchema(projectDir, { apiName }); + await updateApiSchema(projectDir, apiName, 'custom_query.graphql'); + await apiGqlCompile(projectDir, true); + + addCustomResolver(projectDir, apiName, resolverReqName, resolverReq); + addCustomResolver(projectDir, apiName, resolverResName, resolverRes); + writeToCustomResourcesJson(projectDir, apiName, Resources); + + await apiGqlCompile(projectDir, true); + + expect(fs.readFileSync(generatedReqResolverPath).toString()).toEqual(resolverReq); + expect(fs.readFileSync(generatedResResolverPath).toString()).toEqual(resolverRes); + expect(JSON.parse(fs.readFileSync(stackPath).toString()).Resources).toEqual(Resources.Resources); + }); + }); +}); diff --git a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-rollback-1.test.ts b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-rollback-1.test.ts index dd64161cb29..ecaec785943 100644 --- a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-rollback-1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-rollback-1.test.ts @@ -7,22 +7,27 @@ import { amplifyPushIterativeRollback, getDDBTable, getBackendAmplifyMeta, - addApiWithSchema, + addApiWithoutSchema, addFeatureFlag, amplifyPush, updateApiSchema, getTableResourceId, getNestedStackID, cancelIterativeAmplifyPush, + createRandomName, } from 'amplify-e2e-core'; // 30-45min describe('Iterative Rollback - add 2 @keys ', () => { let projectDir: string; + let appName: string; beforeAll(async () => { + appName = createRandomName(); projectDir = await createNewProjectDir('iterativeRollback'); - await initJSProjectWithProfile(projectDir, {}); + await initJSProjectWithProfile(projectDir, { + name: appName, + }); addFeatureFlag(projectDir, 'graphqltransformer', 'enableiterativegsiupdates', true); }); afterAll(async () => { @@ -30,15 +35,15 @@ describe('Iterative Rollback - add 2 @keys ', () => { deleteProjectDir(projectDir); }); it('should support rolling back from the 2nd deployment on adding gsis', async () => { - const apiName = 'renamekey'; const initialSchema = path.join('iterative-push', 'two-key-add', 'initial-schema.graphql'); - await addApiWithSchema(projectDir, initialSchema, { apiName, apiKeyExpirationDays: 7 }); + await addApiWithoutSchema(projectDir, { apiKeyExpirationDays: 7 }); + await updateApiSchema(projectDir, appName, initialSchema); await amplifyPush(projectDir); // get info on table const meta = getBackendAmplifyMeta(projectDir); const { StackId: stackId, Region: region } = meta.providers.awscloudformation; - const { logicalId } = meta.api[apiName].providerMetadata; + const { logicalId } = meta.api[appName].providerMetadata; const apiID = await getNestedStackID(stackId, region, logicalId); const tableName = await getTableResourceId(region, 'Record', apiID); let table = await getDDBTable(tableName, region); @@ -46,7 +51,7 @@ describe('Iterative Rollback - add 2 @keys ', () => { expect(table.Table.GlobalSecondaryIndexes).toBeUndefined(); const finalSchema = path.join('iterative-push', 'two-key-add', 'final-schema.graphql'); - updateApiSchema(projectDir, apiName, finalSchema); + updateApiSchema(projectDir, appName, finalSchema); // cancel iterative push on 2nd deployment await cancelIterativeAmplifyPush(projectDir, { current: 2, max: 3 }); diff --git a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-rollback-2.test.ts b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-rollback-2.test.ts index fda5124dc18..3175875a28f 100644 --- a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-rollback-2.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-rollback-2.test.ts @@ -7,21 +7,26 @@ import { amplifyPushIterativeRollback, getDDBTable, getBackendAmplifyMeta, - addApiWithSchema, + addApiWithoutSchema, addFeatureFlag, amplifyPush, updateApiSchema, getTableResourceId, getNestedStackID, cancelIterativeAmplifyPush, + createRandomName, } from 'amplify-e2e-core'; describe('Iterative Rollback - removing two @keys', () => { let projectDir: string; + let appName: string; beforeAll(async () => { + appName = createRandomName(); projectDir = await createNewProjectDir('iterativeRollback'); - await initJSProjectWithProfile(projectDir, {}); + await initJSProjectWithProfile(projectDir, { + name: appName, + }); addFeatureFlag(projectDir, 'graphqltransformer', 'enableiterativegsiupdates', true); }); afterAll(async () => { @@ -29,15 +34,15 @@ describe('Iterative Rollback - removing two @keys', () => { deleteProjectDir(projectDir); }); it('should support rolling back from the 2nd deployment on adding gsis', async () => { - const apiName = 'renamekey'; const initialSchema = path.join('iterative-push', 'multiple-key-delete', 'initial-schema.graphql'); - await addApiWithSchema(projectDir, initialSchema, { apiName, apiKeyExpirationDays: 7 }); + await addApiWithoutSchema(projectDir, { apiKeyExpirationDays: 7 }); + await updateApiSchema(projectDir, appName, initialSchema); await amplifyPush(projectDir); // get info on table const meta = getBackendAmplifyMeta(projectDir); const { StackId: stackId, Region: region } = meta.providers.awscloudformation; - const { logicalId } = meta.api[apiName].providerMetadata; + const { logicalId } = meta.api[appName].providerMetadata; const apiID = await getNestedStackID(stackId, region, logicalId); const tableName = await getTableResourceId(region, 'Something', apiID); let table = await getDDBTable(tableName, region); @@ -46,7 +51,7 @@ describe('Iterative Rollback - removing two @keys', () => { expect(table.Table.GlobalSecondaryIndexes.length).toEqual(2); const finalSchema = path.join('iterative-push', 'multiple-key-delete', 'final-schema.graphql'); - updateApiSchema(projectDir, apiName, finalSchema); + updateApiSchema(projectDir, appName, finalSchema); // cancel iterative push on 2nd deployment await cancelIterativeAmplifyPush(projectDir, { current: 2, max: 3 }); diff --git a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-1.test.ts b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-1.test.ts index 2c4109c2b8b..0afb994c9c6 100644 --- a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-1.test.ts @@ -4,19 +4,24 @@ import { initJSProjectWithProfile, deleteProject, deleteProjectDir, - addApiWithSchema, + addApiWithoutSchema, addFeatureFlag, amplifyPush, updateApiSchema, amplifyPushUpdate, + createRandomName, } from 'amplify-e2e-core'; describe('Schema iterative update - rename @key', () => { let projectDir: string; + let appName: string; beforeAll(async () => { + appName = createRandomName(); projectDir = await createNewProjectDir('schemaIterative'); - await initJSProjectWithProfile(projectDir, {}); + await initJSProjectWithProfile(projectDir, { + name: appName, + }); addFeatureFlag(projectDir, 'graphqltransformer', 'enableiterativegsiupdates', true); }); @@ -25,14 +30,13 @@ describe('Schema iterative update - rename @key', () => { deleteProjectDir(projectDir); }); it('should support changing gsi name', async () => { - const apiName = 'renamekey'; - const initialSchema = path.join('iterative-push', 'change-model-name', 'initial-schema.graphql'); - await addApiWithSchema(projectDir, initialSchema, { apiName, apiKeyExpirationDays: 7 }); + await addApiWithoutSchema(projectDir, { apiKeyExpirationDays: 7 }); + await updateApiSchema(projectDir, appName, initialSchema); await amplifyPush(projectDir); const finalSchema = path.join('iterative-push', 'change-model-name', 'final-schema.graphql'); - await updateApiSchema(projectDir, apiName, finalSchema); - await amplifyPushUpdate(projectDir); + await updateApiSchema(projectDir, appName, finalSchema); + await amplifyPushUpdate(projectDir, undefined, undefined, true); }); }); diff --git a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-3.test.ts b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-3.test.ts index 31fd211f241..1878135d8ef 100644 --- a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-3.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-3.test.ts @@ -4,7 +4,7 @@ import { initJSProjectWithProfile, deleteProject, deleteProjectDir, - addApiWithSchema, + addApiWithoutSchema, addFeatureFlag, amplifyPush, updateApiSchema, @@ -16,7 +16,9 @@ describe('Schema iterative update - delete', () => { beforeAll(async () => { projectDir = await createNewProjectDir('schemaIterative'); - await initJSProjectWithProfile(projectDir, {}); + await initJSProjectWithProfile(projectDir, { + name: 'deletekeys', + }); addFeatureFlag(projectDir, 'graphqltransformer', 'enableiterativegsiupdates', true); }); @@ -28,7 +30,8 @@ describe('Schema iterative update - delete', () => { const apiName = 'deletekeys'; const initialSchema = path.join('iterative-push', 'multiple-key-delete', 'initial-schema.graphql'); - await addApiWithSchema(projectDir, initialSchema, { apiName, apiKeyExpirationDays: 7 }); + await addApiWithoutSchema(projectDir, { apiKeyExpirationDays: 7 }); + await updateApiSchema(projectDir, apiName, initialSchema); await amplifyPush(projectDir); const finalSchema = path.join('iterative-push', 'multiple-key-delete', 'final-schema.graphql'); diff --git a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-4.test.ts b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-4.test.ts index a83805997a0..6f1a0bb1180 100644 --- a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-4.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-4.test.ts @@ -4,7 +4,7 @@ import { initJSProjectWithProfile, deleteProject, deleteProjectDir, - addApiWithSchema, + addApiWithoutSchema, addFeatureFlag, amplifyPush, updateApiSchema, @@ -16,7 +16,9 @@ describe('Schema iterative update - create update and delete', () => { beforeAll(async () => { projectDir = await createNewProjectDir('schemaIterative'); - await initJSProjectWithProfile(projectDir, {}); + await initJSProjectWithProfile(projectDir, { + name: 'iterativetest1', + }); addFeatureFlag(projectDir, 'graphqltransformer', 'enableiterativegsiupdates', true); }); @@ -28,7 +30,8 @@ describe('Schema iterative update - create update and delete', () => { const apiName = 'iterativetest1'; const initialSchema = path.join('iterative-push', 'add-remove-and-update-key', 'initial-schema.graphql'); - await addApiWithSchema(projectDir, initialSchema, { apiName, apiKeyExpirationDays: 7 }); + await addApiWithoutSchema(projectDir, { apiKeyExpirationDays: 7 }); + await updateApiSchema(projectDir, apiName, initialSchema); await amplifyPush(projectDir); const finalSchema = path.join('iterative-push', 'add-remove-and-update-key', 'final-schema.graphql'); diff --git a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-locking.test.ts b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-locking.test.ts index 1fe5aefa131..d0ea8a4818b 100644 --- a/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-locking.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/schema-iterative-update-locking.test.ts @@ -4,7 +4,7 @@ import { initJSProjectWithProfile, deleteProject, deleteProjectDir, - addApiWithSchema, + addApiWithoutSchema, addFeatureFlag, amplifyPush, updateApiSchema, @@ -24,6 +24,7 @@ describe('Schema iterative update - locking', () => { projectRoot = await createNewProjectDir('schemaIterativeLock'); await initJSProjectWithProfile(projectRoot, { + name: 'iterlock', disableAmplifyAppCreation: false, }); @@ -40,7 +41,8 @@ describe('Schema iterative update - locking', () => { // Create and push project with API const initialSchema = path.join('iterative-push', 'change-model-name', 'initial-schema.graphql'); - await addApiWithSchema(projectRoot, initialSchema, { apiName, apiKeyExpirationDays: 7 }); + await addApiWithoutSchema(projectRoot, { apiKeyExpirationDays: 7 }); + await updateApiSchema(projectRoot, apiName, initialSchema); await amplifyPush(projectRoot); // Apply updates to first project diff --git a/packages/amplify-e2e-tests/src/__tests__/schema-key.test.ts b/packages/amplify-e2e-tests/src/__tests__/schema-key.test.ts index f58c885e7e0..4b95ddd3114 100644 --- a/packages/amplify-e2e-tests/src/__tests__/schema-key.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/schema-key.test.ts @@ -1,12 +1,14 @@ -import { initJSProjectWithProfile, deleteProject, createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; +import { initJSProjectWithProfile, deleteProject, createNewProjectDir, deleteProjectDir, createRandomName } from 'amplify-e2e-core'; import { testSchema } from '../schema-api-directives'; describe('api directives @key', () => { let projectDir: string; + let appName: string; beforeEach(async () => { + appName = createRandomName(); projectDir = await createNewProjectDir('key'); - await initJSProjectWithProfile(projectDir, {}); + await initJSProjectWithProfile(projectDir, { name: appName }); }); afterEach(async () => { @@ -30,7 +32,7 @@ describe('api directives @key', () => { }); it('key SelectiveSync with key directive', async () => { - const testresult = await testSchema(projectDir, 'key', 'howTo4'); + const testresult = await testSchema(projectDir, 'key', 'howTo4', appName); expect(testresult).toBeTruthy(); }); }); diff --git a/packages/amplify-e2e-tests/src/__tests__/storage-1.test.ts b/packages/amplify-e2e-tests/src/__tests__/storage-1.test.ts index c5a30b73118..3ce91002324 100644 --- a/packages/amplify-e2e-tests/src/__tests__/storage-1.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/storage-1.test.ts @@ -1,25 +1,7 @@ -import { $TSAny, JSONUtilities } from 'amplify-cli-core'; -import { initJSProjectWithProfile, initFlutterProjectWithProfile, deleteProject, amplifyPushAuth } from 'amplify-e2e-core'; -import { addAuthWithDefault, addAuthWithGroupsAndAdminAPI } from 'amplify-e2e-core'; -import { - addSimpleDDB, - overrideDDB, - buildOverrideStorage, - addDDBWithTrigger, - updateDDBWithTrigger, - addSimpleDDBwithGSI, - updateSimpleDDBwithGSI, - overrideS3, - addS3AndAuthWithAuthOnlyAccess, - addS3WithGuestAccess, - addS3WithGroupAccess, - addS3WithTrigger, - updateS3AddTrigger, -} from 'amplify-e2e-core'; -import { createNewProjectDir, deleteProjectDir, getProjectMeta, getDDBTable, checkIfBucketExists } from 'amplify-e2e-core'; +import { $TSAny } from 'amplify-cli-core'; +import { addAuthWithDefault, addAuthWithGroupsAndAdminAPI, addS3AndAuthWithAuthOnlyAccess, addS3WithGroupAccess, addS3WithGuestAccess, addS3WithTrigger, amplifyPushAuth, checkIfBucketExists, createNewProjectDir, deleteProject, deleteProjectDir, getProjectMeta, initFlutterProjectWithProfile, initJSProjectWithProfile, updateS3AddTrigger } from 'amplify-e2e-core'; import * as fs from 'fs-extra'; import * as path from 'path'; -import uuid from 'uuid'; function getServiceMeta( projectRoot : string, category: string, service : string ): $TSAny { @@ -102,175 +84,3 @@ describe('amplify add/update storage(S3)', () => { await validate(projRoot); }); }); -describe('s3 override tests', () => { - let projRoot: string; - beforeEach(async () => { - projRoot = await createNewProjectDir('s3-overrides'); - }); - - afterEach(async () => { - await deleteProject(projRoot); - deleteProjectDir(projRoot); - }); - - it('override S3 Removal property', async () => { - await initJSProjectWithProfile(projRoot, {}); - await addAuthWithDefault(projRoot, {}); - await addS3WithGuestAccess(projRoot, {}); - await overrideS3(projRoot, {}); - - const resourcePath = path.join(projRoot, 'amplify', 'backend', 'storage'); - const resourceName = (fs.readdirSync(resourcePath))[0]; - const srcOverrideFilePath = path.join(__dirname, '..', '..', 'overrides', 'override-storage-s3.ts'); - const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'storage', resourceName, 'override.ts'); - - const cfnFilePath = path.join(projRoot, 'amplify', 'backend', 'storage', resourceName, 'build', 'cloudformation-template.json'); - fs.copyFileSync(srcOverrideFilePath, destOverrideFilePath); - await buildOverrideStorage(projRoot, {}); - let s3CFNFileJSON: any = JSONUtilities.readJson(cfnFilePath); - // check if overrides are applied to the cfn file - expect(s3CFNFileJSON?.Resources?.S3Bucket?.Properties?.VersioningConfiguration?.Status).toEqual('Enabled'); - - // check if override persists after an update - s3CFNFileJSON = JSONUtilities.readJson(cfnFilePath); - expect(s3CFNFileJSON?.Resources?.S3Bucket?.Properties?.VersioningConfiguration?.Status).toEqual('Enabled'); - - await amplifyPushAuth(projRoot); - const s3Meta = getServiceMeta( projRoot, "storage", "S3" ); - const { - BucketName: bucketName, - Region: region - } = s3Meta.output; - - expect(region).toBeDefined(); - expect(bucketName).toBeDefined(); - const bucketExists = await checkIfBucketExists(bucketName, region); - expect(bucketExists).toMatchObject({}); - }); -}); - -describe('amplify add/update storage(DDB) with GSI', () => { - let projRoot: string; - beforeEach(async () => { - projRoot = await createNewProjectDir('ddb-gsi'); - }); - - afterEach(async () => { - await deleteProject(projRoot); - deleteProjectDir(projRoot); - }); - - it('init a project add a GSI and then update with another GSI', async () => { - await initJSProjectWithProfile(projRoot, {}); - await addAuthWithDefault(projRoot, {}); - await addSimpleDDBwithGSI(projRoot, {}); - await updateSimpleDDBwithGSI(projRoot, {}); - await amplifyPushAuth(projRoot); - }); -}); - -describe('amplify add/update storage(DDB)', () => { - let projRoot: string; - beforeEach(async () => { - projRoot = await createNewProjectDir('ddb-add-update'); - }); - - afterEach(async () => { - await deleteProject(projRoot); - deleteProjectDir(projRoot); - }); - - it('init a project and add/update ddb table with & without trigger', async () => { - await initJSProjectWithProfile(projRoot, {}); - await addSimpleDDB(projRoot, {}); - await addDDBWithTrigger(projRoot, {}); - await amplifyPushAuth(projRoot); - await updateDDBWithTrigger(projRoot, {}); - await amplifyPushAuth(projRoot); - - const meta = getProjectMeta(projRoot); - const { - Name: table1Name, - Arn: table1Arn, - Region: table1Region, - StreamArn: table1StreamArn, - } = Object.keys(meta.storage).map(key => meta.storage[key])[0].output; - - expect(table1Name).toBeDefined(); - expect(table1Arn).toBeDefined(); - expect(table1Region).toBeDefined(); - expect(table1StreamArn).toBeDefined(); - const table1Configs = await getDDBTable(table1Name, table1Region); - - expect(table1Configs.Table.TableArn).toEqual(table1Arn); - - const { - Name: table2Name, - Arn: table2Arn, - Region: table2Region, - StreamArn: table2StreamArn, - } = Object.keys(meta.storage).map(key => meta.storage[key])[1].output; - - expect(table2Name).toBeDefined(); - expect(table2Arn).toBeDefined(); - expect(table2Region).toBeDefined(); - expect(table2StreamArn).toBeDefined(); - const table2Configs = await getDDBTable(table2Name, table2Region); - expect(table2Configs.Table.TableArn).toEqual(table2Arn); - }); -}); - -describe('ddb override tests', () => { - let projRoot: string; - beforeEach(async () => { - projRoot = await createNewProjectDir('ddb-overrides'); - }); - - afterEach(async () => { - await deleteProject(projRoot); - deleteProjectDir(projRoot); - }); - - it('override DDB StreamSpecification property', async () => { - const resourceName = `dynamo${uuid.v4().split('-')[0]}`; - await initJSProjectWithProfile(projRoot, {}); - await addSimpleDDB(projRoot, { name: resourceName }); - await overrideDDB(projRoot, {}); - - const srcOverrideFilePath = path.join(__dirname, '..', '..', 'overrides', 'override-storage-ddb.ts'); - const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'storage', resourceName, 'override.ts'); - const cfnFilePath = path.join(projRoot, 'amplify', 'backend', 'storage', resourceName, 'build', 'cloudformation-template.json'); - - fs.copyFileSync(srcOverrideFilePath, destOverrideFilePath); - - await buildOverrideStorage(projRoot, {}); - - let ddbCFNFileJSON: any = JSONUtilities.readJson(cfnFilePath); - - // check if overrides are applied to the cfn file - expect(ddbCFNFileJSON?.Resources?.DynamoDBTable?.Properties?.StreamSpecification?.StreamViewType).toEqual('NEW_AND_OLD_IMAGES'); - - await updateDDBWithTrigger(projRoot, {}); - - // check if override persists after an update - ddbCFNFileJSON = JSONUtilities.readJson(cfnFilePath); - expect(ddbCFNFileJSON?.Resources?.DynamoDBTable?.Properties?.StreamSpecification?.StreamViewType).toEqual('NEW_AND_OLD_IMAGES'); - - await amplifyPushAuth(projRoot); - - const meta = getProjectMeta(projRoot); - const { - Name: table1Name, - Arn: table1Arn, - Region: table1Region, - StreamArn: table1StreamArn, - } = Object.keys(meta.storage).map(key => meta.storage[key])[0].output; - - expect(table1Name).toBeDefined(); - expect(table1Arn).toBeDefined(); - expect(table1Region).toBeDefined(); - expect(table1StreamArn).toBeDefined(); - const table1Configs = await getDDBTable(table1Name, table1Region); - expect(table1Configs.Table.TableArn).toEqual(table1Arn); - }); -}); diff --git a/packages/amplify-e2e-tests/src/__tests__/storage-2.test.ts b/packages/amplify-e2e-tests/src/__tests__/storage-2.test.ts index 66178ee886a..b68521e0b09 100644 --- a/packages/amplify-e2e-tests/src/__tests__/storage-2.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/storage-2.test.ts @@ -1,20 +1,13 @@ -import { initJSProjectWithProfile, initFlutterProjectWithProfile, deleteProject, amplifyPushAuth } from 'amplify-e2e-core'; -import { addAuthWithDefault, addAuthWithGroupsAndAdminAPI } from 'amplify-e2e-core'; import { - addSimpleDDB, - addDDBWithTrigger, - updateDDBWithTrigger, + addAuthWithDefault, addSimpleDDBwithGSI, + amplifyPushAuth, + createNewProjectDir, + deleteProject, + deleteProjectDir, + initJSProjectWithProfile, updateSimpleDDBwithGSI, - addS3AndAuthWithAuthOnlyAccess, - addS3WithGuestAccess, - addS3WithGroupAccess, - addS3WithTrigger, - updateS3AddTrigger, } from 'amplify-e2e-core'; -import { createNewProjectDir, deleteProjectDir, getProjectMeta, getDDBTable, checkIfBucketExists } from 'amplify-e2e-core'; -import * as fs from 'fs-extra'; -import * as path from 'path'; describe('amplify add/update storage(DDB) with GSI', () => { let projRoot: string; diff --git a/packages/amplify-e2e-tests/src/__tests__/storage-3.test.ts b/packages/amplify-e2e-tests/src/__tests__/storage-3.test.ts index 2fc5f49c284..81f068efd3b 100644 --- a/packages/amplify-e2e-tests/src/__tests__/storage-3.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/storage-3.test.ts @@ -1,20 +1,15 @@ -import { initJSProjectWithProfile, initFlutterProjectWithProfile, deleteProject, amplifyPushAuth } from 'amplify-e2e-core'; -import { addAuthWithDefault, addAuthWithGroupsAndAdminAPI } from 'amplify-e2e-core'; import { - addSimpleDDB, addDDBWithTrigger, + addSimpleDDB, + amplifyPushAuth, + createNewProjectDir, + deleteProject, + deleteProjectDir, + getDDBTable, + getProjectMeta, + initJSProjectWithProfile, updateDDBWithTrigger, - addSimpleDDBwithGSI, - updateSimpleDDBwithGSI, - addS3AndAuthWithAuthOnlyAccess, - addS3WithGuestAccess, - addS3WithGroupAccess, - addS3WithTrigger, - updateS3AddTrigger, } from 'amplify-e2e-core'; -import { createNewProjectDir, deleteProjectDir, getProjectMeta, getDDBTable, checkIfBucketExists } from 'amplify-e2e-core'; -import * as fs from 'fs-extra'; -import * as path from 'path'; describe('amplify add/update storage(DDB)', () => { let projRoot: string; @@ -36,9 +31,12 @@ describe('amplify add/update storage(DDB)', () => { await amplifyPushAuth(projRoot); const meta = getProjectMeta(projRoot); - const { Name: table1Name, Arn: table1Arn, Region: table1Region, StreamArn: table1StreamArn } = Object.keys(meta.storage).map( - key => meta.storage[key], - )[0].output; + const { + Name: table1Name, + Arn: table1Arn, + Region: table1Region, + StreamArn: table1StreamArn, + } = Object.keys(meta.storage).map(key => meta.storage[key])[0].output; expect(table1Name).toBeDefined(); expect(table1Arn).toBeDefined(); @@ -48,9 +46,12 @@ describe('amplify add/update storage(DDB)', () => { expect(table1Configs.Table.TableArn).toEqual(table1Arn); - const { Name: table2Name, Arn: table2Arn, Region: table2Region, StreamArn: table2StreamArn } = Object.keys(meta.storage).map( - key => meta.storage[key], - )[1].output; + const { + Name: table2Name, + Arn: table2Arn, + Region: table2Region, + StreamArn: table2StreamArn, + } = Object.keys(meta.storage).map(key => meta.storage[key])[1].output; expect(table2Name).toBeDefined(); expect(table2Arn).toBeDefined(); diff --git a/packages/amplify-e2e-tests/src/__tests__/storage-4.test.ts b/packages/amplify-e2e-tests/src/__tests__/storage-4.test.ts new file mode 100644 index 00000000000..709c148e0b5 --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/storage-4.test.ts @@ -0,0 +1,143 @@ +import { + addAuthWithDefault, + addAuthWithGroups, + addHeadlessStorage, + amplifyPushAuth, + checkIfBucketExists, + createNewProjectDir, + deleteProject, + deleteProjectDir, + getProjectMeta, + initJSProjectWithProfile, + removeHeadlessStorage, + updateHeadlessStorage, +} from 'amplify-e2e-core'; +import { AddStorageRequest, CrudOperation, RemoveStorageRequest, UpdateStorageRequest } from 'amplify-headless-interface'; +import { v4 as uuid } from 'uuid'; + +async function validateS3Bucket(projRoot: string) { + const meta = getProjectMeta(projRoot); + const { BucketName: bucketName, Region: region } = Object.keys(meta.storage).map(key => meta.storage[key])[0].output; + + expect(bucketName).toBeDefined(); + expect(region).toBeDefined(); + + const bucketExists = await checkIfBucketExists(bucketName, region); + expect(bucketExists).toMatchObject({}); +} + +describe('amplify add/update storage(S3) headlessly', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('s3-test'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('init a project and add/update/remove S3 headlessly', async () => { + const [shortId] = uuid().split('-'); + const resourceName = 'headlessTest1'; + const bucketName = `storageintegtest${shortId}`; + + const addStorageRequest = { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + permissions: { + auth: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.DELETE, CrudOperation.READ], + }, + bucketName, + resourceName, + }, + }; + + const updateStorageRequest = { + version: 1, + serviceModification: { + serviceName: 'S3', + permissions: { + auth: [CrudOperation.READ], + }, + resourceName, + }, + }; + + await initJSProjectWithProfile(projRoot, {}); + await addAuthWithDefault(projRoot, {}); + await addHeadlessStorage(projRoot, addStorageRequest as AddStorageRequest); + await amplifyPushAuth(projRoot); + await validateS3Bucket(projRoot); + await updateHeadlessStorage(projRoot, updateStorageRequest as UpdateStorageRequest); + await amplifyPushAuth(projRoot); + await validateS3Bucket(projRoot); + }); + + it('init a project and headlessly add/update/remove S3 with Lambda trigger', async () => { + const [shortId] = uuid().split('-'); + const resourceName = 'headlessTest2'; + const bucketName = `storageintegtest${shortId}`; + const lambdaTriggerName = `lambdaTrigger${shortId}`; + + const addStorageRequest = { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + permissions: { + auth: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.READ], + guest: [CrudOperation.READ], + groups: { + Admins: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.DELETE, CrudOperation.READ], + Users: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.READ], + }, + }, + bucketName, + resourceName, + lambdaTrigger: { + mode: 'new', + name: lambdaTriggerName, + }, + }, + }; + + const updateStorageRequest = { + version: 1, + serviceModification: { + serviceName: 'S3', + permissions: { + auth: [CrudOperation.READ], + groups: { + Admins: [CrudOperation.CREATE_AND_UPDATE, CrudOperation.DELETE, CrudOperation.READ], + Users: [CrudOperation.READ], + }, + }, + resourceName, + lambdaTrigger: { + mode: 'existing', + name: lambdaTriggerName, + }, + }, + }; + + const removeStorageRequest = { + version: 1, + serviceConfiguration: { + serviceName: 'S3', + resourceName, + }, + }; + + await initJSProjectWithProfile(projRoot, {}); + await addAuthWithGroups(projRoot); + await addHeadlessStorage(projRoot, addStorageRequest as AddStorageRequest); + await amplifyPushAuth(projRoot); + await validateS3Bucket(projRoot); + await updateHeadlessStorage(projRoot, updateStorageRequest as UpdateStorageRequest); + await amplifyPushAuth(projRoot); + await validateS3Bucket(projRoot); + await removeHeadlessStorage(projRoot, removeStorageRequest as RemoveStorageRequest); + await amplifyPushAuth(projRoot); + }); +}); diff --git a/packages/amplify-e2e-tests/src/__tests__/storage-5.test.ts b/packages/amplify-e2e-tests/src/__tests__/storage-5.test.ts new file mode 100644 index 00000000000..e368ff0f3b1 --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/storage-5.test.ts @@ -0,0 +1,203 @@ +import { $TSAny, JSONUtilities } from 'amplify-cli-core'; +import { + createNewProjectDir, + deleteProject, + deleteProjectDir, + initJSProjectWithProfile, + addAuthWithDefault, + addS3WithGuestAccess, + overrideS3, + buildOverrideStorage, + amplifyPushAuth, + checkIfBucketExists, + addSimpleDDBwithGSI, + updateSimpleDDBwithGSI, + addSimpleDDB, + addDDBWithTrigger, + updateDDBWithTrigger, + getProjectMeta, + getDDBTable, + overrideDDB, +} from 'amplify-e2e-core'; +import uuid from 'uuid'; +import * as path from 'path'; +import * as fs from 'fs-extra'; + +function getServiceMeta(projectRoot: string, category: string, service: string): $TSAny { + const meta = getProjectMeta(projectRoot); + for (const storageResourceName of Object.keys(meta[category])) { + if (meta.storage[storageResourceName].service.toUpperCase() === service.toUpperCase()) { + return meta.storage[storageResourceName]; + } + } +} + +describe('s3 override tests', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('s3-overrides'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('override S3 Removal property', async () => { + await initJSProjectWithProfile(projRoot, {}); + await addAuthWithDefault(projRoot, {}); + await addS3WithGuestAccess(projRoot, {}); + await overrideS3(projRoot, {}); + + const resourcePath = path.join(projRoot, 'amplify', 'backend', 'storage'); + const resourceName = fs.readdirSync(resourcePath)[0]; + const srcOverrideFilePath = path.join(__dirname, '..', '..', 'overrides', 'override-storage-s3.ts'); + const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'storage', resourceName, 'override.ts'); + + const cfnFilePath = path.join(projRoot, 'amplify', 'backend', 'storage', resourceName, 'build', 'cloudformation-template.json'); + fs.copyFileSync(srcOverrideFilePath, destOverrideFilePath); + await buildOverrideStorage(projRoot, {}); + let s3CFNFileJSON: any = JSONUtilities.readJson(cfnFilePath); + // check if overrides are applied to the cfn file + expect(s3CFNFileJSON?.Resources?.S3Bucket?.Properties?.VersioningConfiguration?.Status).toEqual('Enabled'); + + // check if override persists after an update + s3CFNFileJSON = JSONUtilities.readJson(cfnFilePath); + expect(s3CFNFileJSON?.Resources?.S3Bucket?.Properties?.VersioningConfiguration?.Status).toEqual('Enabled'); + + await amplifyPushAuth(projRoot); + const s3Meta = getServiceMeta(projRoot, 'storage', 'S3'); + const { BucketName: bucketName, Region: region } = s3Meta.output; + + expect(region).toBeDefined(); + expect(bucketName).toBeDefined(); + const bucketExists = await checkIfBucketExists(bucketName, region); + expect(bucketExists).toMatchObject({}); + }); +}); + +describe('amplify add/update storage(DDB) with GSI', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('ddb-gsi'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('init a project add a GSI and then update with another GSI', async () => { + await initJSProjectWithProfile(projRoot, {}); + await addAuthWithDefault(projRoot, {}); + await addSimpleDDBwithGSI(projRoot, {}); + await updateSimpleDDBwithGSI(projRoot, {}); + await amplifyPushAuth(projRoot); + }); +}); + +describe('amplify add/update storage(DDB)', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('ddb-add-update'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('init a project and add/update ddb table with & without trigger', async () => { + await initJSProjectWithProfile(projRoot, {}); + await addSimpleDDB(projRoot, {}); + await addDDBWithTrigger(projRoot, {}); + await amplifyPushAuth(projRoot); + await updateDDBWithTrigger(projRoot, {}); + await amplifyPushAuth(projRoot); + + const meta = getProjectMeta(projRoot); + const { + Name: table1Name, + Arn: table1Arn, + Region: table1Region, + StreamArn: table1StreamArn, + } = Object.keys(meta.storage).map(key => meta.storage[key])[0].output; + + expect(table1Name).toBeDefined(); + expect(table1Arn).toBeDefined(); + expect(table1Region).toBeDefined(); + expect(table1StreamArn).toBeDefined(); + const table1Configs = await getDDBTable(table1Name, table1Region); + + expect(table1Configs.Table.TableArn).toEqual(table1Arn); + + const { + Name: table2Name, + Arn: table2Arn, + Region: table2Region, + StreamArn: table2StreamArn, + } = Object.keys(meta.storage).map(key => meta.storage[key])[1].output; + + expect(table2Name).toBeDefined(); + expect(table2Arn).toBeDefined(); + expect(table2Region).toBeDefined(); + expect(table2StreamArn).toBeDefined(); + const table2Configs = await getDDBTable(table2Name, table2Region); + expect(table2Configs.Table.TableArn).toEqual(table2Arn); + }); +}); + +describe('ddb override tests', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('ddb-overrides'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('override DDB StreamSpecification property', async () => { + const resourceName = `dynamo${uuid.v4().split('-')[0]}`; + await initJSProjectWithProfile(projRoot, {}); + await addSimpleDDB(projRoot, { name: resourceName }); + await overrideDDB(projRoot, {}); + + const srcOverrideFilePath = path.join(__dirname, '..', '..', 'overrides', 'override-storage-ddb.ts'); + const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'storage', resourceName, 'override.ts'); + const cfnFilePath = path.join(projRoot, 'amplify', 'backend', 'storage', resourceName, 'build', 'cloudformation-template.json'); + + fs.copyFileSync(srcOverrideFilePath, destOverrideFilePath); + + await buildOverrideStorage(projRoot, {}); + + let ddbCFNFileJSON: any = JSONUtilities.readJson(cfnFilePath); + + // check if overrides are applied to the cfn file + expect(ddbCFNFileJSON?.Resources?.DynamoDBTable?.Properties?.StreamSpecification?.StreamViewType).toEqual('NEW_AND_OLD_IMAGES'); + + await updateDDBWithTrigger(projRoot, {}); + + // check if override persists after an update + ddbCFNFileJSON = JSONUtilities.readJson(cfnFilePath); + expect(ddbCFNFileJSON?.Resources?.DynamoDBTable?.Properties?.StreamSpecification?.StreamViewType).toEqual('NEW_AND_OLD_IMAGES'); + + await amplifyPushAuth(projRoot); + + const meta = getProjectMeta(projRoot); + const { + Name: table1Name, + Arn: table1Arn, + Region: table1Region, + StreamArn: table1StreamArn, + } = Object.keys(meta.storage).map(key => meta.storage[key])[0].output; + + expect(table1Name).toBeDefined(); + expect(table1Arn).toBeDefined(); + expect(table1Region).toBeDefined(); + expect(table1StreamArn).toBeDefined(); + const table1Configs = await getDDBTable(table1Name, table1Region); + expect(table1Configs.Table.TableArn).toEqual(table1Arn); + }); +}); diff --git a/packages/amplify-e2e-tests/src/cleanup-e2e-resources.ts b/packages/amplify-e2e-tests/src/cleanup-e2e-resources.ts index f3e8434087e..58c745fc957 100644 --- a/packages/amplify-e2e-tests/src/cleanup-e2e-resources.ts +++ b/packages/amplify-e2e-tests/src/cleanup-e2e-resources.ts @@ -452,7 +452,13 @@ export const cleanup = async () => { } const amplifyApps: AmplifyAppInfo[] = []; const stacks: StackInfo[] = []; - + const stsRes = new aws.STS({ + apiVersion: '2011-06-15', + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + sessionToken: process.env.AWS_SESSION_TOKEN, + }); + const parentAccountIdentity = await stsRes.getCallerIdentity().promise(); const orgApi = new aws.Organizations({ apiVersion: '2016-11-28', // the region where the organization exists @@ -461,14 +467,17 @@ export const cleanup = async () => { let accs; try { accs = await orgApi.listAccounts().promise(); - accs = accs.map(async account => { + accs = accs.Accounts.map(async account => { + if (account.Id === parentAccountIdentity.Account) { + return { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + sessionToken: process.env.AWS_SESSION_TOKEN, + }; + } + const randomNumber = Math.floor(Math.random() * 100000); - const assumeRoleRes = await new aws.STS({ - apiVersion: '2011-06-15', - accessKeyId: process.env.AWS_ACCESS_KEY_ID, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, - sessionToken: process.env.AWS_SESSION_TOKEN, - }) + const assumeRoleRes = await stsRes .assumeRole({ RoleArn: `arn:aws:iam::${account.Id}:role/OrganizationAccountAccessRole`, RoleSessionName: `testSession${randomNumber}`, @@ -485,7 +494,7 @@ export const cleanup = async () => { accs = await Promise.all(accs); } catch (e) { console.error(e); - console.log('Error assuming role in child account. Using parent AWS account.'); + console.log('Error assuming child account role. This could be because the script is already running from within a child account. Running on current AWS account only.'); accs = [ { accessKeyId: process.env.AWS_ACCESS_KEY_ID, diff --git a/packages/amplify-e2e-tests/src/configure_tests.ts b/packages/amplify-e2e-tests/src/configure_tests.ts index fb3f9bab934..65232a82e81 100644 --- a/packages/amplify-e2e-tests/src/configure_tests.ts +++ b/packages/amplify-e2e-tests/src/configure_tests.ts @@ -29,4 +29,5 @@ process.nextTick(async () => { console.log(e.stack); process.exit(1); } + process.exit(); }); diff --git a/packages/amplify-e2e-tests/src/schema-api-directives/index.ts b/packages/amplify-e2e-tests/src/schema-api-directives/index.ts index db52c93a9b4..d54d6b03b70 100644 --- a/packages/amplify-e2e-tests/src/schema-api-directives/index.ts +++ b/packages/amplify-e2e-tests/src/schema-api-directives/index.ts @@ -13,7 +13,7 @@ import { runFunctionTest } from './functionTester'; // to deal with subscriptions in node env (global as any).WebSocket = require('ws'); -export async function testSchema(projectDir: string, directive: string, section: string): Promise { +export async function testSchema(projectDir: string, directive: string, section: string, appName?: string): Promise { let testModule; const testFilePath = path.join(__dirname, 'tests', `${directive}-${section}.ts`); @@ -29,7 +29,7 @@ export async function testSchema(projectDir: string, directive: string, section: try { if (testModule.runTest) { - await testModule.runTest(projectDir, testModule); + await testModule.runTest(projectDir, testModule, appName); } else { switch (directive) { case 'auth': diff --git a/packages/amplify-e2e-tests/src/schema-api-directives/tests/key-howTo4.ts b/packages/amplify-e2e-tests/src/schema-api-directives/tests/key-howTo4.ts index 9c5398c0c14..266432014a1 100644 --- a/packages/amplify-e2e-tests/src/schema-api-directives/tests/key-howTo4.ts +++ b/packages/amplify-e2e-tests/src/schema-api-directives/tests/key-howTo4.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { addApiWithSchemaAndConflictDetection, amplifyPush } from 'amplify-e2e-core'; +import { addApiWithBlankSchemaAndConflictDetection, amplifyPush, updateApiSchema } from 'amplify-e2e-core'; import { getApiKey, configureAmplify, getConfiguredAppsyncClientAPIKeyAuth } from '../authHelper'; import { testQueries, testMutations } from '../common'; @@ -269,8 +269,9 @@ export const expected_result_query5 = { }, }; -export async function runTest(projectDir: string, testModule: any) { - await addApiWithSchemaAndConflictDetection(projectDir, testModule.schemaName); +export async function runTest(projectDir: string, testModule: any, appName: string) { + await addApiWithBlankSchemaAndConflictDetection(projectDir); + await updateApiSchema(projectDir, appName, testModule.schemaName); await amplifyPush(projectDir); const awsconfig = configureAmplify(projectDir); diff --git a/packages/amplify-frontend-javascript/lib/frontend-config-creator.js b/packages/amplify-frontend-javascript/lib/frontend-config-creator.js index dec64769136..ef8f52425f6 100644 --- a/packages/amplify-frontend-javascript/lib/frontend-config-creator.js +++ b/packages/amplify-frontend-javascript/lib/frontend-config-creator.js @@ -188,14 +188,11 @@ function getAWSExportsObject(resources) { // add geo config if geo resources exist if (Object.entries(geoConfig).length > 0) { - Object.assign( - configOutput, - { - geo: { - amazon_location_service: geoConfig - } - } - ); + Object.assign(configOutput, { + geo: { + amazon_location_service: geoConfig, + }, + }); } return configOutput; @@ -562,16 +559,16 @@ function getSumerianConfig(sumerianResources) { } function getMapConfig(mapResources) { - let defaultMap = ""; + let defaultMap = ''; const mapConfig = { - items: {} + items: {}, }; mapResources.forEach(mapResource => { const mapName = mapResource.output.Name; mapConfig.items[mapName] = { - style: mapResource.output.Style - } - if(mapResource.isDefault) { + style: mapResource.output.Style, + }; + if (mapResource.isDefault) { defaultMap = mapName; } }); @@ -580,14 +577,14 @@ function getMapConfig(mapResources) { } function getPlaceIndexConfig(placeIndexResources) { - let defaultPlaceIndex = ""; + let defaultPlaceIndex = ''; const placeIndexConfig = { - items: [] + items: [], }; placeIndexResources.forEach(placeIndexResource => { const placeIndexName = placeIndexResource.output.Name; placeIndexConfig.items.push(placeIndexName); - if(placeIndexResource.isDefault) { + if (placeIndexResource.isDefault) { defaultPlaceIndex = placeIndexName; } }); diff --git a/packages/amplify-graphql-auth-transformer/.npmignore b/packages/amplify-graphql-auth-transformer/.npmignore new file mode 100644 index 00000000000..3ee5d55b0b8 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/.npmignore @@ -0,0 +1,5 @@ +**/__mocks__/** +**/__tests__/** +src +tsconfig.json +tsconfig.tsbuildinfo diff --git a/packages/amplify-graphql-auth-transformer/CHANGELOG.md b/packages/amplify-graphql-auth-transformer/CHANGELOG.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/amplify-graphql-auth-transformer/package.json b/packages/amplify-graphql-auth-transformer/package.json new file mode 100644 index 00000000000..687fb28fdfe --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/package.json @@ -0,0 +1,66 @@ +{ + "name": "@aws-amplify/graphql-auth-transformer", + "version": "0.1.0", + "description": "Amplify GraphQL @auth Transformer", + "repository": { + "type": "git", + "url": "https://github.com/aws-amplify/amplify-cli.git", + "directory": "packages/amplify-graphql-auth-transformer" + }, + "author": "Amazon Web Services", + "license": "Apache-2.0", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "keywords": [ + "graphql", + "cloudformation", + "aws", + "amplify" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest", + "build": "tsc", + "clean": "rimraf ./lib", + "watch": "tsc -w" + }, + "dependencies": { + "@aws-amplify/graphql-transformer-core": "0.9.2", + "@aws-amplify/graphql-transformer-interfaces": "1.10.0", + "@aws-amplify/graphql-model-transformer": "0.6.4", + "@aws-cdk/aws-appsync": "~1.124.0", + "@aws-cdk/aws-dynamodb": "~1.124.0", + "@aws-cdk/core": "~1.124.0", + "@aws-cdk/aws-iam": "~1.124.0", + "constructs": "^3.3.125", + "graphql": "^14.5.8", + "graphql-mapping-template": "4.18.3", + "graphql-transformer-common": "4.19.10", + "lodash": "^4.17.21" + }, + "devDependencies": { + "@aws-amplify/graphql-index-transformer": "0.4.0", + "@aws-amplify/graphql-searchable-transformer": "0.6.3", + "@types/fs-extra": "^8.0.1", + "@aws-cdk/assert": "~1.124.0", + "@types/node": "^12.12.6" + }, + "jest": { + "testURL": "http://localhost", + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(src/__tests__/.*.test.ts)$", + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true + } +} diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/__snapshots__/conflict-resolution.test.ts.snap b/packages/amplify-graphql-auth-transformer/src/__tests__/__snapshots__/conflict-resolution.test.ts.snap new file mode 100644 index 00000000000..a736cdea5da --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/__snapshots__/conflict-resolution.test.ts.snap @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`test multi auth model with conflict resolution 1`] = ` +"## [Start] Authorization Steps. ** +$util.qr($ctx.stash.put(\\"hasAuth\\", true)) +#set( $isAuthorized = false ) +#set( $primaryFieldMap = {} ) +#if( $util.authType() == \\"IAM Authorization\\" ) + #if( !$isAuthorized ) + #if( $ctx.identity.userArn == $ctx.stash.authRole ) + #set( $isAuthorized = true ) + #end + #end +#end +#if( $util.authType() == \\"User Pool Authorization\\" ) + #if( !$isAuthorized ) + #set( $authFilter = [{ + \\"owner\\": { + \\"eq\\": $util.defaultIfNull($ctx.identity.claims.get(\\"username\\"), $util.defaultIfNull($ctx.identity.claims.get(\\"cognito:username\\"), \\"___xamznone____\\")) + } +}] ) + $util.qr($ctx.stash.put(\\"authFilter\\", { \\"or\\": $authFilter })) + #end +#end +#if( !$isAuthorized && $util.isNull($ctx.stash.authFilter) && $primaryFieldMap.isEmpty() ) +$util.unauthorized() +#end +$util.toJson({\\"version\\":\\"2018-05-29\\",\\"payload\\":{}}) +## [End] Authorization Steps. **" +`; + +exports[`test single auth model is enabled with conflict resolution 1`] = ` +"## [Start] Authorization Steps. ** +$util.qr($ctx.stash.put(\\"hasAuth\\", true)) +#set( $isAuthorized = false ) +#set( $primaryFieldMap = {} ) +#if( $util.authType() == \\"User Pool Authorization\\" ) + #if( !$isAuthorized ) + #set( $authFilter = [{ + \\"owner\\": { + \\"eq\\": $util.defaultIfNull($ctx.identity.claims.get(\\"username\\"), $util.defaultIfNull($ctx.identity.claims.get(\\"cognito:username\\"), \\"___xamznone____\\")) + } +}] ) + $util.qr($ctx.stash.put(\\"authFilter\\", { \\"or\\": $authFilter })) + #end +#end +#if( !$isAuthorized && $util.isNull($ctx.stash.authFilter) && $primaryFieldMap.isEmpty() ) +$util.unauthorized() +#end +$util.toJson({\\"version\\":\\"2018-05-29\\",\\"payload\\":{}}) +## [End] Authorization Steps. **" +`; diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/__snapshots__/field-auth-argument.test.ts.snap b/packages/amplify-graphql-auth-transformer/src/__tests__/__snapshots__/field-auth-argument.test.ts.snap new file mode 100644 index 00000000000..0a0706bd49e --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/__snapshots__/field-auth-argument.test.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`per-field @auth without @model 1`] = ` +Object { + "Properties": Object { + "Description": "", + "Path": "/", + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "appsync:GraphQL", + "Effect": "Allow", + "Resource": Object { + "Fn::Sub": Array [ + "arn:aws:appsync:\${AWS::Region}:\${AWS::AccountId}:apis/\${apiId}/types/\${typeName}/fields/\${fieldName}", + Object { + "apiId": Object { + "Fn::GetAtt": Array [ + "GraphQLAPI", + "ApiId", + ], + }, + "fieldName": "listContext", + "typeName": "Query", + }, + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Roles": Array [ + Object { + "Ref": "authRoleName", + }, + ], + }, + "Type": "AWS::IAM::ManagedPolicy", +} +`; diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/accesscontrol.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/accesscontrol.test.ts new file mode 100644 index 00000000000..6aa2d0e9ecf --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/accesscontrol.test.ts @@ -0,0 +1,112 @@ +import { AccessControlMatrix } from '../accesscontrol'; +import { MODEL_OPERATIONS } from '../utils'; + +test('test access control on object and field', () => { + /* + given the following schema + type Student + @model + @auth(rules: [ + { allow: groups, groups: ["admin"] } + { allow: groups, groups: ["student"], operations: [read] } + ]) { + studentID: ID + name: String + #acm protect email only studentID can update their own email + email: AWSEmail @auth(rules: [ + { allow: owner, ownerField: "studentID", operations: [update] } + { allow: groups, groups: ["admin"] } + ]) + # only allowed to student and admin + ssn: String @auth(rules: [ + { allow: owner, ownerField: "studentID", operations: [read] } + { allow: groups, groups: ["admin"] } + ]) + } + */ + // create an acm for the student type + const adminRole = 'userPools:staticGroup:admin'; + const studentGroupRole = 'userPools:staticGroup:student'; + const studentOwnerRole = 'userPools:owner:studentID'; + const studentTypeFields = ['studentID', 'name', 'email', 'ssn']; + const acm = new AccessControlMatrix({ + resources: studentTypeFields, + operations: MODEL_OPERATIONS, + }); + // add OBJECT rules first + // add admin role which has full access on all CRUD operations for all fields + acm.setRole({ + role: adminRole, + operations: MODEL_OPERATIONS, + }); + // add the student static group rule which only has read access + acm.setRole({ + role: studentGroupRole, + operations: ['read'], + }); + + studentTypeFields.forEach(field => { + // check that admin has CRUD access on all fields + expect(acm.isAllowed(adminRole, field, 'create')).toBe(true); + expect(acm.isAllowed(adminRole, field, 'read')).toBe(true); + expect(acm.isAllowed(adminRole, field, 'update')).toBe(true); + expect(acm.isAllowed(adminRole, field, 'delete')).toBe(true); + // check that studentGroupRole has access to read only + expect(acm.isAllowed(studentGroupRole, field, 'read')).toBe(true); + expect(acm.isAllowed(studentGroupRole, field, 'create')).toBe(false); + expect(acm.isAllowed(studentGroupRole, field, 'update')).toBe(false); + expect(acm.isAllowed(studentGroupRole, field, 'delete')).toBe(false); + }); + // when adding a field rule on email we need to overwrite it + acm.resetAccessForResource('email'); + + expect(acm.isAllowed(studentGroupRole, 'email', 'read')).toBe(false); + acm.setRole({ + role: studentOwnerRole, + operations: ['update'], + resource: 'email', + }); + expect(acm.isAllowed(adminRole, 'email', 'update')).toBe(false); + expect(acm.isAllowed(studentOwnerRole, 'email', 'update')).toBe(true); +}); + +test('test access control only on field', () => { + /* + given the following schema + type Student + @model { + studentID: ID + name: String + # only allows read access on email and ssn for studentID ownerfield can also only update email + email: AWSEmail @auth(rules: [ + { allow: owner, ownerField: "studentID", operations: [read, update] } + ]) + ssn: String @auth(rules: [ + { allow: owner, ownerField: "studentID", operations: [read] } + ]) + } + */ + // create an acm for the student type + const studentOwnerRole = 'userPools:owner:studentID'; + const studentTypeFields = ['studentID', 'name', 'email', 'ssn']; + const acm = new AccessControlMatrix({ + resources: studentTypeFields, + operations: MODEL_OPERATIONS, + }); + // set role for email field + acm.setRole({ role: studentOwnerRole, operations: ['read', 'update'], resource: 'email' }); + // set role for ssn field + acm.setRole({ role: studentOwnerRole, operations: ['read'], resource: 'ssn' }); + + // expect the correct permissions are assigned for email field + expect(acm.isAllowed(studentOwnerRole, 'email', 'update')).toBe(true); + expect(acm.isAllowed(studentOwnerRole, 'email', 'read')).toBe(true); + expect(acm.isAllowed(studentOwnerRole, 'email', 'delete')).toBe(false); + expect(acm.isAllowed(studentOwnerRole, 'email', 'create')).toBe(false); + + // expect the correct permissions are assigned for ssn field + expect(acm.isAllowed(studentOwnerRole, 'ssn', 'create')).toBe(false); + expect(acm.isAllowed(studentOwnerRole, 'ssn', 'read')).toBe(true); + expect(acm.isAllowed(studentOwnerRole, 'ssn', 'update')).toBe(false); + expect(acm.isAllowed(studentOwnerRole, 'ssn', 'delete')).toBe(false); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/amplify-admin-auth.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/amplify-admin-auth.test.ts new file mode 100644 index 00000000000..2ca7f2e4098 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/amplify-admin-auth.test.ts @@ -0,0 +1,337 @@ +import { AuthTransformer } from '../graphql-auth-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import _ from 'lodash'; + +test('test simple model with public auth rule and amplify admin app is present', () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: public}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + }`; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'API_KEY', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: true, + adminUserPoolID: 'us-fake-1_uuid', + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).toContain('Post @aws_api_key @aws_iam'); +}); + +test('Test simple model with public auth rule and amplify admin app is not enabled', () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: public}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + } + `; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'API_KEY', + }, + additionalAuthenticationProviders: [], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).not.toContain('Post @aws_api_key @aws_iam'); +}); + +test('Test model with public auth rule without all operations and amplify admin app is present', () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: public, operations: [read, update]}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + } + `; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'API_KEY', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: true, + adminUserPoolID: 'us-fake-1_uuid', + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + + expect(out.schema).toContain('type Post @aws_iam @aws_api_key'); + expect(out.schema).toContain('createPost(input: CreatePostInput!, condition: ModelPostConditionInput): Post @aws_api_key @aws_iam'); + expect(out.schema).toContain('updatePost(input: UpdatePostInput!, condition: ModelPostConditionInput): Post @aws_api_key @aws_iam'); + expect(out.schema).toContain('deletePost(input: DeletePostInput!, condition: ModelPostConditionInput): Post @aws_api_key @aws_iam'); + + // No Resource extending Auth and UnAuth role + const policyResources = Object.values(out.rootStack.Resources!).filter(r => r.Type === 'AWS::IAM::ManagedPolicy'); + expect(policyResources).toHaveLength(0); +}); + +test('Test simple model with private auth rule and amplify admin app is present', () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: groups, groups: ["Admin", "Dev"]}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + } + `; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: true, + adminUserPoolID: 'us-fake-1_uuid', + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).toContain('type Post @aws_iam @aws_cognito_user_pools'); +}); + +test('Test simple model with private auth rule and amplify admin app not enabled', () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: groups, groups: ["Admin", "Dev"]}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + } + `; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).not.toContain('type Post @aws_iam @aws_cognito_user_pools'); +}); + +test('Test simple model with private auth rule, few operations, and amplify admin app enabled', () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: groups, groups: ["Admin", "Dev"], operations: [read]}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + } + `; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + ], + }, + addAwsIamAuthInOutputSchema: true, + adminUserPoolID: 'us-fake-1_uuid', + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).toContain('type Post @aws_iam @aws_cognito_user_pools'); + expect(out.schema).toContain( + 'createPost(input: CreatePostInput!, condition: ModelPostConditionInput): Post @aws_iam @aws_cognito_user_pools', + ); + expect(out.schema).toContain( + 'updatePost(input: UpdatePostInput!, condition: ModelPostConditionInput): Post @aws_iam @aws_cognito_user_pools', + ); + expect(out.schema).toContain( + 'deletePost(input: DeletePostInput!, condition: ModelPostConditionInput): Post @aws_iam @aws_cognito_user_pools', + ); + + // No Resource extending Auth and UnAuth role + const policyResources = Object.values(out.rootStack.Resources!).filter(r => r.Type === 'AWS::IAM::ManagedPolicy'); + expect(policyResources).toHaveLength(0); +}); + +test('Test simple model with private IAM auth rule, few operations, and amplify admin app is not enabled', () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: private, provider: iam, operations: [read]}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + } + `; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).toContain('Post @aws_iam'); + expect(out.schema).not.toContain( + 'createPost(input: CreatePostInput!, condition: ModelPostConditionInput): Post @aws_iam @aws_cognito_user_pools', + ); + expect(out.schema).not.toContain('deletePost(input: DeletePostInput!): Post @aws_iam'); + expect(out.schema).not.toContain('updatePost(input: UpdatePostInput!): Post @aws_iam'); + + expect(out.schema).toContain('getPost(id: ID!): Post @aws_iam'); + expect(out.schema).toContain('listPosts(filter: ModelPostFilterInput, limit: Int, nextToken: String): ModelPostConnection @aws_iam'); +}); + +test('Test simple model with AdminUI enabled should add IAM policy only for fields that have explicit IAM auth', () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: private, provider: iam, operations: [read]}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + } + `; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: true, + adminUserPoolID: 'us-fake-1_uuid', + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).toContain('Post @aws_iam @aws_cognito_user_pool'); + expect(out.schema).toContain( + 'createPost(input: CreatePostInput!, condition: ModelPostConditionInput): Post @aws_iam @aws_cognito_user_pools', + ); + expect(out.schema).toContain( + 'updatePost(input: UpdatePostInput!, condition: ModelPostConditionInput): Post @aws_iam @aws_cognito_user_pools', + ); + expect(out.schema).toContain( + 'deletePost(input: DeletePostInput!, condition: ModelPostConditionInput): Post @aws_iam @aws_cognito_user_pools', + ); + + expect(out.schema).toContain('getPost(id: ID!): Post @aws_iam'); + expect(out.schema).toContain('listPosts(filter: ModelPostFilterInput, limit: Int, nextToken: String): ModelPostConnection @aws_iam'); + const policyResources = _.filter(out.rootStack.Resources, r => r.Type === 'AWS::IAM::ManagedPolicy'); + expect(policyResources).toHaveLength(1); + const resources = _.get(policyResources, '[0].Properties.PolicyDocument.Statement[0].Resource'); + const typeFieldList = _.map(resources, r => _.get(r, 'Fn::Sub[1]')).map(r => `${_.get(r, 'typeName')}.${_.get(r, 'fieldName', '*')}`); + expect(typeFieldList).toEqual([ + 'Post.*', + 'Query.getPost', + 'Query.listPosts', + 'Mutation.createPost', + 'Mutation.updatePost', + 'Mutation.deletePost', + 'Subscription.onCreatePost', + 'Subscription.onUpdatePost', + 'Subscription.onDeletePost', + ]); + // should throw unauthorized if it's not signed by the admin ui iam role + ['Mutation.createPost.auth.1.req.vtl', 'Mutation.updatePost.auth.1.res.vtl', 'Mutation.deletePost.auth.1.res.vtl'].forEach(r => { + expect(out.pipelineFunctions[r]).toContain( + '#if( $util.authType() == "IAM Authorization" )\n' + + ' #if( $ctx.identity.userArn.contains("us-fake-1_uuid_Full-access/CognitoIdentityCredentials") || $ctx.identity.userArn.contains("us-fake-1_uuid_Manage-only/CognitoIdentityCredentials") )\n' + + ' #return($util.toJson({})\n' + + ' #end\n' + + '$util.unauthorized()\n' + + '#end', + ); + }); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/conflict-resolution.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/conflict-resolution.test.ts new file mode 100644 index 00000000000..3ce36ff6494 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/conflict-resolution.test.ts @@ -0,0 +1,70 @@ +import { AuthTransformer } from '../graphql-auth-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { GraphQLTransform, ConflictHandlerType } from '@aws-amplify/graphql-transformer-core'; +import _ from 'lodash'; + +test('test single auth model is enabled with conflict resolution', () => { + const validSchema = ` + type Post @model @auth(rules: [{ allow: owner}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + }`; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }, + transformConfig: { + ResolverConfig: { + project: { + ConflictDetection: 'VERSION', + ConflictHandler: ConflictHandlerType.AUTOMERGE, + }, + }, + }, + transformers: [new ModelTransformer(), new AuthTransformer()], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).toContain( + `syncPosts(filter: ModelPostFilterInput, limit: Int, nextToken: String, lastSync: AWSTimestamp): ModelPostConnection`, + ); + expect(out.pipelineFunctions['Query.syncPosts.auth.1.req.vtl']).toMatchSnapshot(); +}); + +test('test multi auth model with conflict resolution', () => { + const validSchema = ` + type Post @model @auth(rules: [{ allow: owner }, { allow: private, provider: iam }]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + }`; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [{ authenticationType: 'AWS_IAM' }], + }, + transformConfig: { + ResolverConfig: { + project: { + ConflictDetection: 'VERSION', + ConflictHandler: ConflictHandlerType.AUTOMERGE, + }, + }, + }, + transformers: [new ModelTransformer(), new AuthTransformer()], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).toContain( + `syncPosts(filter: ModelPostFilterInput, limit: Int, nextToken: String, lastSync: AWSTimestamp): ModelPostConnection @aws_iam @aws_cognito_user_pools`, + ); + expect(out.pipelineFunctions['Query.syncPosts.auth.1.req.vtl']).toMatchSnapshot(); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/field-auth-argument.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/field-auth-argument.test.ts new file mode 100644 index 00000000000..d952d2acc32 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/field-auth-argument.test.ts @@ -0,0 +1,79 @@ +import { AuthTransformer } from '../graphql-auth-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; + +test('subscriptions are only generated if the respective mutation operation exists', () => { + const validSchema = ` + type Salary + @model + @auth(rules: [ + {allow: owner}, + {allow: groups, groups: ["Moderator"]} + ]) { + id: ID! + wage: Int + owner: String + secret: String @auth(rules: [{allow: owner}]) + }`; + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + // expect to generate subscription resolvers for create and update only + expect(out).toBeDefined(); + expect(out.rootStack.Resources[ResourceConstants.RESOURCES.GraphQLAPILogicalID].Properties.AuthenticationType).toEqual( + 'AMAZON_COGNITO_USER_POOLS', + ); + expect(out.pipelineFunctions['Salary.secret.res.vtl']).toContain('#if( $operation == "Mutation" )'); + + expect(out.pipelineFunctions['Mutation.createSalary.res.vtl']).toContain('$util.qr($ctx.result.put("__operation", "Mutation"))'); + expect(out.pipelineFunctions['Mutation.updateSalary.res.vtl']).toContain('$util.qr($ctx.result.put("__operation", "Mutation"))'); + expect(out.pipelineFunctions['Mutation.deleteSalary.res.vtl']).toContain('$util.qr($ctx.result.put("__operation", "Mutation"))'); +}); + +test('per-field @auth without @model', () => { + const validSchema = ` + type Query { + listContext: String @auth(rules: [{ allow: groups, groups: ["Allowed"] }, { allow: private, provider: iam }]) + }`; + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [{ authenticationType: 'AWS_IAM' }], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + + const resources = out.rootStack.Resources; + const authPolicyIdx = Object.keys(out.rootStack.Resources).find(r => r.includes('AuthRolePolicy')); + expect(resources[authPolicyIdx]).toMatchSnapshot(); + expect(out.pipelineFunctions['Query.listContext.req.vtl']).toContain( + '#set( $staticGroupRoles = [{"claim":"cognito:groups","entity":"Allowed"}] )', + ); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/group-auth.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/group-auth.test.ts new file mode 100644 index 00000000000..350d843a75a --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/group-auth.test.ts @@ -0,0 +1,97 @@ +import { AuthTransformer } from '../graphql-auth-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; + +test('happy case with static groups', () => { + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const validSchema = ` + type Post @model @auth(rules: [{allow: groups, groups: ["Admin", "Dev"]}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + }`; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.rootStack!.Resources![ResourceConstants.RESOURCES.GraphQLAPILogicalID].Properties.AuthenticationType).toEqual( + 'AMAZON_COGNITO_USER_POOLS', + ); +}); + +test('happy case with dynamic groups', () => { + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const validSchema = ` + type Post @model @auth(rules: [{allow: groups, groupsField: "groups"}]) { + id: ID! + title: String! + groups: [String] + createdAt: String + updatedAt: String + } + `; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.rootStack!.Resources![ResourceConstants.RESOURCES.GraphQLAPILogicalID].Properties.AuthenticationType).toEqual( + 'AMAZON_COGNITO_USER_POOLS', + ); +}); + +test('validation on @auth on a non-@model type', () => { + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const invalidSchema = ` + type Post @auth(rules: [{allow: groups, groupsField: "groups"}]) { + id: ID! + title: String! + group: String + createdAt: String + updatedAt: String + }`; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + expect(() => transformer.transform(invalidSchema)).toThrowError('Types annotated with @auth must also be annotated with @model.'); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/multi-auth.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/multi-auth.test.ts new file mode 100644 index 00000000000..3584c818a07 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/multi-auth.test.ts @@ -0,0 +1,601 @@ +import { AuthTransformer } from '../graphql-auth-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { AppSyncAuthConfiguration, AppSyncAuthConfigurationOIDCEntry, AppSyncAuthMode } from '@aws-amplify/graphql-transformer-interfaces'; +import { DocumentNode, ObjectTypeDefinitionNode, Kind, FieldDefinitionNode, parse, InputValueDefinitionNode } from 'graphql'; + +const userPoolsDefaultConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], +}; + +const apiKeyDefaultConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'API_KEY', + }, + additionalAuthenticationProviders: [], +}; + +const iamDefaultConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AWS_IAM', + }, + additionalAuthenticationProviders: [], +}; + +const withAuthModes = (authConfig: AppSyncAuthConfiguration, authModes: AppSyncAuthMode[]): AppSyncAuthConfiguration => { + const newAuthConfig = { + defaultAuthentication: { + authenticationType: authConfig.defaultAuthentication.authenticationType, + }, + additionalAuthenticationProviders: [], + }; + + for (const authMode of authModes) { + newAuthConfig.additionalAuthenticationProviders.push({ + authenticationType: authMode, + }); + } + + return newAuthConfig; +}; + +const apiKeyDirectiveName = 'aws_api_key'; +const userPoolsDirectiveName = 'aws_cognito_user_pools'; +const iamDirectiveName = 'aws_iam'; +const openIdDirectiveName = 'aws_oidc'; + +const multiAuthDirective = + '@auth(rules: [{allow: private}, {allow: public}, {allow: private, provider: iam }, {allow: owner, provider: oidc }])'; +const ownerAuthDirective = '@auth(rules: [{allow: owner}])'; +const ownerWithIAMAuthDirective = '@auth(rules: [{allow: owner, provider: iam }])'; +const ownerRestrictedPublicAuthDirective = '@auth(rules: [{allow: owner},{allow: public, operations: [read]}])'; +const ownerRestrictedIAMPrivateAuthDirective = '@auth(rules: [{allow: owner},{allow: private, operations: [read], provider: iam }])'; +const groupsAuthDirective = '@auth(rules: [{allow: groups, groups: ["admin"] }])'; +const groupsWithApiKeyAuthDirective = '@auth(rules: [{allow: groups, groups: ["admin"]}, {allow: public, operations: [read]}])'; +const groupsWithProviderAuthDirective = '@auth(rules: [{allow: groups,groups: ["admin"], provider: iam }])'; +const ownerOpenIdAuthDirective = '@auth(rules: [{allow: owner, provider: oidc }])'; +const privateAuthDirective = '@auth(rules: [{allow: private}])'; +const publicIAMAuthDirective = '@auth(rules: [{allow: public, provider: iam }])'; +const privateWithApiKeyAuthDirective = '@auth(rules: [{allow: private, provider: apiKey }])'; +const publicAuthDirective = '@auth(rules: [{allow: public}])'; +const publicUserPoolsAuthDirective = '@auth(rules: [{allow: public, provider: userPools}])'; +const privateAndPublicDirective = '@auth(rules: [{allow: private}, {allow: public}])'; +const privateIAMDirective = '@auth(rules: [{allow: private, provider: iam}])'; +// const privateAndPrivateIAMDirective = '@auth(rules: [{allow: private}, {allow: private, provider: iam}])'; + +const getSchema = (authDirective: string) => { + return ` + type Post @model ${authDirective} { + id: ID! + title: String! + createdAt: String + updatedAt: String + }`; +}; + +const getSchemaWithFieldAuth = (authDirective: string) => { + return ` + type Post @model { + id: ID + title: String + createdAt: String + updatedAt: String + protected: String ${authDirective} + }`; +}; + +const getSchemaWithTypeAndFieldAuth = (typeAuthDirective: string, fieldAuthDirective: string) => { + return ` + type Post @model ${typeAuthDirective} { + id: ID + title: String + createdAt: String + updatedAt: String + protected: String ${fieldAuthDirective} + }`; +}; + +const getSchemaWithNonModelField = (authDirective: string) => { + return ` + type Post @model ${authDirective} { + id: ID! + title: String! + location: Location + status: Status + createdAt: String + updatedAt: String + } + + type Location { + name: String + address: Address + } + + type Address { + street: String + city: String + state: String + zip: String + } + + enum Status { + PUBLISHED, + DRAFT + }`; +}; + +const getSchemaWithRecursiveNonModelField = (authDirective: string) => { + return ` + type Post @model ${authDirective} { + id: ID! + title: String! + tags: [Tag] + } + + type Tag { + id: ID + tags: [Tag] + } + `; +}; + +const getRecursiveSchemaWithDiffModesOnParentType = (authDir1: string, authDir2: string) => { + return ` + type Post @model ${authDir1} { + id: ID! + title: String! + tags: [Tag] + } + + type Comment @model ${authDir2} { + id: ID! + content: String + tags: [Tag] + } + + type Tag { + id: ID + tags: [Tag] + } + `; +}; + +const getTransformer = (authConfig: AppSyncAuthConfiguration) => + new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + +const getObjectType = (doc: DocumentNode, type: string): ObjectTypeDefinitionNode | undefined => { + return doc.definitions.find(def => def.kind === Kind.OBJECT_TYPE_DEFINITION && def.name.value === type) as + | ObjectTypeDefinitionNode + | undefined; +}; + +const expectNone = fieldOrType => { + expect(fieldOrType.directives.length === 0); +}; + +const expectOne = (fieldOrType, directiveName) => { + expect(fieldOrType.directives.length).toBe(1); + expect(fieldOrType.directives.find(d => d.name.value === directiveName)).toBeDefined(); +}; + +const expectTwo = (fieldOrType, directiveNames) => { + expect(directiveNames).toBeDefined(); + expect(directiveNames).toHaveLength(2); + expect(fieldOrType.directives.length === 2); + expect(fieldOrType.directives.find(d => d.name.value === directiveNames[0])).toBeDefined(); + expect(fieldOrType.directives.find(d => d.name.value === directiveNames[1])).toBeDefined(); +}; + +const expectMultiple = (fieldOrType: ObjectTypeDefinitionNode | FieldDefinitionNode, directiveNames: string[]) => { + expect(directiveNames).toBeDefined(); + expect(directiveNames).toHaveLength(directiveNames.length); + expect(fieldOrType.directives.length).toEqual(directiveNames.length); + directiveNames.forEach(directiveName => { + expect(fieldOrType.directives).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: expect.objectContaining({ value: directiveName }), + }), + ]), + ); + }); +}; + +const getField = (type, name) => type.fields.find(f => f.name.value === name); + +describe('validation tests', () => { + const validationTest = (authDirective, authConfig, expectedError) => { + const schema = getSchema(authDirective); + const transformer = getTransformer(authConfig); + + const t = () => { + const out = transformer.transform(schema); + }; + + expect(t).toThrowError(expectedError); + }; + + test('AMAZON_COGNITO_USER_POOLS not configured for project', () => { + validationTest( + privateAuthDirective, + apiKeyDefaultConfig, + `@auth directive with 'userPools' provider found, but the project has no Cognito User \ +Pools authentication provider configured.`, + ); + }); + + test('API_KEY not configured for project', () => { + validationTest( + publicAuthDirective, + userPoolsDefaultConfig, + `@auth directive with 'apiKey' provider found, but the project has no API Key \ +authentication provider configured.`, + ); + }); + + test('AWS_IAM not configured for project', () => { + validationTest( + publicIAMAuthDirective, + userPoolsDefaultConfig, + `@auth directive with 'iam' provider found, but the project has no IAM \ +authentication provider configured.`, + ); + }); + + test('OPENID_CONNECT not configured for project', () => { + validationTest( + ownerOpenIdAuthDirective, + userPoolsDefaultConfig, + `@auth directive with 'oidc' provider found, but the project has no OPENID_CONNECT \ +authentication provider configured.`, + ); + }); + + test(`'group' cannot have provider`, () => { + validationTest( + groupsWithProviderAuthDirective, + userPoolsDefaultConfig, + `@auth directive with 'groups' strategy only supports 'userPools' and 'oidc' providers, but found \ +'iam' assigned`, + ); + }); + + test(`'owner' has invalid IAM provider`, () => { + validationTest( + ownerWithIAMAuthDirective, + userPoolsDefaultConfig, + `@auth directive with 'owner' strategy only supports 'userPools' (default) and \ +'oidc' providers, but found 'iam' assigned.`, + ); + }); + + test(`'public' has invalid 'userPools' provider`, () => { + validationTest( + publicUserPoolsAuthDirective, + userPoolsDefaultConfig, + `@auth directive with 'public' strategy only supports 'apiKey' (default) and 'iam' providers, but \ +found 'userPools' assigned.`, + ); + }); + + test(`'private' has invalid 'apiKey' provider`, () => { + validationTest( + privateWithApiKeyAuthDirective, + userPoolsDefaultConfig, + `@auth directive with 'private' strategy only supports 'userPools' (default) and 'iam' providers, but \ +found 'apiKey' assigned.`, + ); + }); +}); + +describe('schema generation directive tests', () => { + const transformTest = (authDirective, authConfig, expectedDirectiveNames?: string[] | undefined) => { + const schema = getSchema(authDirective); + const transformer = getTransformer(authConfig); + + const out = transformer.transform(schema); + + const schemaDoc = parse(out.schema); + + const postType = getObjectType(schemaDoc, 'Post'); + + if (expectedDirectiveNames && expectedDirectiveNames.length > 0) { + let expectedDireciveNameCount = 0; + + for (const expectedDirectiveName of expectedDirectiveNames) { + expect(postType.directives.find(d => d.name.value === expectedDirectiveName)).toBeDefined(); + expectedDireciveNameCount++; + } + + expect(expectedDireciveNameCount).toEqual(postType.directives.length); + } + }; + + test(`When provider is the same as default, then no directive added`, () => { + transformTest(ownerAuthDirective, userPoolsDefaultConfig); + }); + + test(`When all providers are configured all of them are added`, () => { + const authConfig = withAuthModes(apiKeyDefaultConfig, ['AMAZON_COGNITO_USER_POOLS', 'AWS_IAM', 'OPENID_CONNECT']); + + (authConfig.additionalAuthenticationProviders[2] as AppSyncAuthConfigurationOIDCEntry).openIDConnectConfig = { + name: 'Test Provider', + issuerUrl: 'https://abc.def/', + }; + + transformTest(multiAuthDirective, authConfig, [userPoolsDirectiveName, iamDirectiveName, openIdDirectiveName, apiKeyDirectiveName]); + }); + + test(`Operation fields are getting the directive added, when type has the @auth for all operations`, () => { + const schema = getSchema(ownerAuthDirective); + const transformer = getTransformer(withAuthModes(apiKeyDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + const queryType = getObjectType(schemaDoc, 'Query'); + const mutationType = getObjectType(schemaDoc, 'Mutation'); + const subscriptionType = getObjectType(schemaDoc, 'Subscription'); + + const fields = [...queryType.fields, ...mutationType.fields]; + + for (const field of fields) { + expect(field.directives.length === 1); + expect(field.directives.find(d => d.name.value === userPoolsDirectiveName)).toBeDefined(); + } + + // Check that owner is required when only using owner auth rules + for (const field of subscriptionType.fields) { + expect(field.arguments).toHaveLength(1); + let arg: InputValueDefinitionNode = field.arguments[0]; + expect(arg.name.value).toEqual('owner'); + expect(arg.type.kind).toEqual(Kind.NAMED_TYPE); + } + + // Check that resolvers containing the authMode check block + const authStepSnippet = '## [Start] Authorization Steps. **'; + + expect(out.pipelineFunctions['Query.getPost.auth.1.req.vtl']).toContain(authStepSnippet); + expect(out.pipelineFunctions['Query.listPosts.auth.1.req.vtl']).toContain(authStepSnippet); + expect(out.pipelineFunctions['Mutation.createPost.auth.1.req.vtl']).toContain(authStepSnippet); + expect(out.pipelineFunctions['Mutation.createPost.auth.1.req.vtl']).toContain(authStepSnippet); + expect(out.pipelineFunctions['Mutation.updatePost.auth.1.res.vtl']).toContain(authStepSnippet); + expect(out.pipelineFunctions['Mutation.deletePost.auth.1.res.vtl']).toContain(authStepSnippet); + }); + + test(`Operation fields are getting the directive added, when type has the @auth only for allowed operations`, () => { + const schema = getSchema(ownerRestrictedPublicAuthDirective); + const transformer = getTransformer(withAuthModes(apiKeyDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + const queryType = getObjectType(schemaDoc, 'Query'); + const mutationType = getObjectType(schemaDoc, 'Mutation'); + const subscriptionType = getObjectType(schemaDoc, 'Subscription'); + + expectTwo(getField(queryType, 'getPost'), ['aws_cognito_user_pools', 'aws_api_key']); + expectTwo(getField(queryType, 'listPosts'), ['aws_cognito_user_pools', 'aws_api_key']); + + expectOne(getField(mutationType, 'createPost'), 'aws_cognito_user_pools'); + expectOne(getField(mutationType, 'updatePost'), 'aws_cognito_user_pools'); + expectOne(getField(mutationType, 'deletePost'), 'aws_cognito_user_pools'); + + const onCreate = getField(subscriptionType, 'onCreatePost'); + expectMultiple(onCreate, ['aws_subscribe', 'aws_api_key', 'aws_cognito_user_pools']); + expectMultiple(getField(subscriptionType, 'onUpdatePost'), ['aws_subscribe', 'aws_api_key', 'aws_cognito_user_pools']); + expectMultiple(getField(subscriptionType, 'onDeletePost'), ['aws_subscribe', 'aws_api_key', 'aws_cognito_user_pools']); + expect(onCreate.arguments).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: expect.objectContaining({ value: 'owner' }), + type: expect.objectContaining({ kind: 'NamedType' }), + }), + ]), + ); + }); + + test(`Field level @auth is propagated to type and the type related operations`, () => { + const schema = getSchemaWithFieldAuth(ownerRestrictedPublicAuthDirective); + const transformer = getTransformer(withAuthModes(apiKeyDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + const queryType = getObjectType(schemaDoc, 'Query'); + const mutationType = getObjectType(schemaDoc, 'Mutation'); + + expectTwo(getField(queryType, 'getPost'), ['aws_cognito_user_pools', 'aws_api_key']); + expectTwo(getField(queryType, 'listPosts'), ['aws_cognito_user_pools', 'aws_api_key']); + + expectOne(getField(mutationType, 'createPost'), 'aws_cognito_user_pools'); + expectOne(getField(mutationType, 'updatePost'), 'aws_cognito_user_pools'); + // since there is only one field allowed on delete it does not have access to delete + expectNone(getField(mutationType, 'deletePost')); + + // Check that resolvers containing the authMode check block + const authModeCheckSnippet = '## [Start] Field Authorization Steps. **'; + // resolvers to check is all other resolvers other than protected + expect(out.pipelineFunctions['Post.id.req.vtl']).toContain(authModeCheckSnippet); + expect(out.pipelineFunctions['Post.title.req.vtl']).toContain(authModeCheckSnippet); + expect(out.pipelineFunctions['Post.createdAt.req.vtl']).toContain(authModeCheckSnippet); + expect(out.pipelineFunctions['Post.updatedAt.req.vtl']).toContain(authModeCheckSnippet); + }); + + test(`'groups' @auth at field level is propagated to type and the type related operations`, () => { + const schema = getSchemaWithFieldAuth(groupsAuthDirective); + const transformer = getTransformer(withAuthModes(apiKeyDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + const queryType = getObjectType(schemaDoc, 'Query'); + const mutationType = getObjectType(schemaDoc, 'Mutation'); + + expectOne(getField(queryType, 'getPost'), 'aws_cognito_user_pools'); + expectOne(getField(queryType, 'listPosts'), 'aws_cognito_user_pools'); + + expectOne(getField(mutationType, 'createPost'), 'aws_cognito_user_pools'); + expectOne(getField(mutationType, 'updatePost'), 'aws_cognito_user_pools'); + // since there is only one field allowed on delete it does not have access to delete + expectNone(getField(mutationType, 'deletePost')); + + // Check that resolvers containing the authMode check block + const authModeCheckSnippet = '## [Start] Field Authorization Steps. **'; + + // resolvers to check is all other resolvers other than protected + expect(out.pipelineFunctions['Post.id.req.vtl']).toContain(authModeCheckSnippet); + expect(out.pipelineFunctions['Post.title.req.vtl']).toContain(authModeCheckSnippet); + expect(out.pipelineFunctions['Post.createdAt.req.vtl']).toContain(authModeCheckSnippet); + expect(out.pipelineFunctions['Post.updatedAt.req.vtl']).toContain(authModeCheckSnippet); + }); + + test(`'groups' @auth at field level is propagated to type and the type related operations, also default provider for read`, () => { + const schema = getSchemaWithTypeAndFieldAuth(groupsAuthDirective, groupsWithApiKeyAuthDirective); + const transformer = getTransformer(withAuthModes(apiKeyDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + const queryType = getObjectType(schemaDoc, 'Query'); + const mutationType = getObjectType(schemaDoc, 'Mutation'); + + expectTwo(getField(queryType, 'getPost'), ['aws_cognito_user_pools', 'aws_api_key']); + expectTwo(getField(queryType, 'listPosts'), ['aws_cognito_user_pools', 'aws_api_key']); + + expectOne(getField(mutationType, 'createPost'), 'aws_cognito_user_pools'); + expectOne(getField(mutationType, 'updatePost'), 'aws_cognito_user_pools'); + expectOne(getField(mutationType, 'deletePost'), 'aws_cognito_user_pools'); + + // Check that resolvers containing the authMode group check + const groupCheckSnippet = '#set( $staticGroupRoles = [{"claim":"cognito:groups","entity":"admin"}] )'; + + // resolvers to check is all other resolvers other than protected by the group rule + expect(out.pipelineFunctions['Post.id.req.vtl']).toContain(groupCheckSnippet); + expect(out.pipelineFunctions['Post.title.req.vtl']).toContain(groupCheckSnippet); + expect(out.pipelineFunctions['Post.createdAt.req.vtl']).toContain(groupCheckSnippet); + expect(out.pipelineFunctions['Post.updatedAt.req.vtl']).toContain(groupCheckSnippet); + }); + + test(`Nested types without @model not getting directives applied for iam, and no policy is generated`, () => { + const schema = getSchemaWithNonModelField(''); + const transformer = getTransformer(withAuthModes(iamDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + + const locationType = getObjectType(schemaDoc, 'Location'); + const addressType = getObjectType(schemaDoc, 'Address'); + + expect(locationType.directives.length).toBe(0); + expect(addressType.directives.length).toBe(0); + + const authPolicyIdx = Object.keys(out.rootStack.Resources).find(r => r.includes('AuthRolePolicy')); + + expect(out.rootStack.Resources[authPolicyIdx]).toBeUndefined(); + }); + + test(`Nested types without @model not getting directives applied for iam, but policy is generated`, () => { + const schema = getSchemaWithNonModelField(privateIAMDirective); + const transformer = getTransformer(withAuthModes(iamDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + + const locationType = getObjectType(schemaDoc, 'Location'); + const addressType = getObjectType(schemaDoc, 'Address'); + + expect(locationType.directives.length).toBe(0); + expect(addressType.directives.length).toBe(0); + + // find the key to account for the hash + const authPolicyIdx = Object.keys(out.rootStack.Resources).find(r => r.includes('AuthRolePolicy01')); + expect(out.rootStack.Resources[authPolicyIdx]).toBeDefined; + const authRolePolicy = out.rootStack.Resources[authPolicyIdx]; + + const locationPolicy = authRolePolicy.Properties.PolicyDocument.Statement[0].Resource.filter( + r => + r['Fn::Sub'] && + r['Fn::Sub'].length && + r['Fn::Sub'].length === 2 && + r['Fn::Sub'][1].typeName && + r['Fn::Sub'][1].typeName === 'Location', + ); + expect(locationPolicy).toHaveLength(1); + + const addressPolicy = authRolePolicy.Properties.PolicyDocument.Statement[0].Resource.filter( + r => + r['Fn::Sub'] && + r['Fn::Sub'].length && + r['Fn::Sub'].length === 2 && + r['Fn::Sub'][1].typeName && + r['Fn::Sub'][1].typeName === 'Address', + ); + expect(addressPolicy).toHaveLength(1); + }); + + test(`Recursive types with diff auth modes on parent @model types`, () => { + const schema = getRecursiveSchemaWithDiffModesOnParentType(ownerAuthDirective, privateIAMDirective); + const transformer = getTransformer(withAuthModes(userPoolsDefaultConfig, ['AWS_IAM'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + + const tagType = getObjectType(schemaDoc, 'Tag'); + const expectedDirectiveNames = [userPoolsDirectiveName, iamDirectiveName]; + + expectMultiple(tagType, expectedDirectiveNames); + }); + + test(`Recursive types without @model`, () => { + const schema = getSchemaWithRecursiveNonModelField(ownerRestrictedIAMPrivateAuthDirective); + const transformer = getTransformer(withAuthModes(userPoolsDefaultConfig, ['AWS_IAM'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + + const tagType = getObjectType(schemaDoc, 'Tag'); + const expectedDirectiveNames = [userPoolsDirectiveName, iamDirectiveName]; + + expectMultiple(tagType, expectedDirectiveNames); + }); + + test(`Nested types without @model getting directives applied (cognito default, api key additional)`, () => { + const schema = getSchemaWithNonModelField(privateAndPublicDirective); + const transformer = getTransformer(withAuthModes(userPoolsDefaultConfig, ['API_KEY'])); + + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + + const locationType = getObjectType(schemaDoc, 'Location'); + const addressType = getObjectType(schemaDoc, 'Address'); + const expectedDirectiveNames = [userPoolsDirectiveName, apiKeyDirectiveName]; + + if (expectedDirectiveNames && expectedDirectiveNames.length > 0) { + let expectedDireciveNameCount = 0; + + for (const expectedDirectiveName of expectedDirectiveNames) { + expect(locationType.directives.find(d => d.name.value === expectedDirectiveName)).toBeDefined(); + expectedDireciveNameCount++; + } + + expect(expectedDireciveNameCount).toEqual(locationType.directives.length); + + expectedDireciveNameCount = 0; + + for (const expectedDirectiveName of expectedDirectiveNames) { + expect(addressType.directives.find(d => d.name.value === expectedDirectiveName)).toBeDefined(); + expectedDireciveNameCount++; + } + + expect(expectedDireciveNameCount).toEqual(addressType.directives.length); + } + }); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/owner-auth.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/owner-auth.test.ts new file mode 100644 index 00000000000..3cd649b0647 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/owner-auth.test.ts @@ -0,0 +1,230 @@ +import { parse } from 'graphql'; +import { AuthTransformer } from '../graphql-auth-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { getField, getObjectType } from './test-helpers'; +import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; + +test('auth transformer validation happy case', () => { + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const validSchema = ` + type Post @model @auth(rules: [{allow: owner}]) { + id: ID! + title: String! + createdAt: String + updatedAt: String + }`; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.rootStack.Resources[ResourceConstants.RESOURCES.GraphQLAPILogicalID].Properties.AuthenticationType).toEqual( + 'AMAZON_COGNITO_USER_POOLS', + ); +}); + +test('ownerfield with subscriptions', () => { + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const validSchema = ` + type Post @model @auth(rules: [ + {allow: owner, ownerField: "postOwner"} + ]){ + id: ID! + title: String + postOwner: String + }`; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + + // expect 'postOwner' as an argument for subscription operations + expect(out.schema).toContain('onCreatePost(postOwner: String)'); + expect(out.schema).toContain('onUpdatePost(postOwner: String)'); + expect(out.schema).toContain('onDeletePost(postOwner: String)'); + + // expect logic in the resolvers to check for postOwner args as an allowed owner + expect(out.pipelineFunctions['Subscription.onCreatePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity0 = $util.defaultIfNull($ctx.args.postOwner, null) )', + ); + expect(out.pipelineFunctions['Subscription.onUpdatePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity0 = $util.defaultIfNull($ctx.args.postOwner, null) )', + ); + expect(out.pipelineFunctions['Subscription.onDeletePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity0 = $util.defaultIfNull($ctx.args.postOwner, null) )', + ); +}); + +test('multiple owner rules with subscriptions', () => { + const validSchema = ` + type Post @model + @auth(rules: [ + { allow: owner }, + { allow: owner, ownerField: "editor", operations: [read, update] } + ]) + { + id: ID! + title: String + owner: String + editor: String + }`; + + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + + // expect 'owner' and 'editors' as arguments for subscription operations + expect(out.schema).toContain('onCreatePost(owner: String, editor: String)'); + expect(out.schema).toContain('onUpdatePost(owner: String, editor: String)'); + expect(out.schema).toContain('onDeletePost(owner: String, editor: String)'); + + // expect logic in the resolvers to check for owner args as an allowedOwner + expect(out.pipelineFunctions['Subscription.onCreatePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity0 = $util.defaultIfNull($ctx.args.owner, null) )', + ); + expect(out.pipelineFunctions['Subscription.onUpdatePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity0 = $util.defaultIfNull($ctx.args.owner, null) )', + ); + expect(out.pipelineFunctions['Subscription.onDeletePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity0 = $util.defaultIfNull($ctx.args.owner, null) )', + ); + + // expect logic in the resolvers to check for editor args as an allowedOwner + expect(out.pipelineFunctions['Subscription.onCreatePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity1 = $util.defaultIfNull($ctx.args.editor, null) )', + ); + expect(out.pipelineFunctions['Subscription.onUpdatePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity1 = $util.defaultIfNull($ctx.args.editor, null) )', + ); + expect(out.pipelineFunctions['Subscription.onDeletePost.auth.1.req.vtl']).toContain( + '#set( $ownerEntity1 = $util.defaultIfNull($ctx.args.editor, null) )', + ); +}); + +test('implicit owner fields get added to the type', () => { + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const validSchema = ` + type Post @model + @auth(rules: [ + {allow: owner, ownerField: "postOwner"} + { allow: owner, ownerField: "customOwner", identityClaim: "sub"} + ]) + { + id: ID! + title: String + } + `; + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + + const schema = parse(out.schema); + const postType = getObjectType(schema, 'Post'); + expect(postType).toBeDefined(); + + const postOwnerField = getField(postType, 'postOwner'); + expect(postOwnerField).toBeDefined(); + expect((postOwnerField as any).type.name.value).toEqual('String'); + + const customOwner = getField(postType, 'customOwner'); + expect(customOwner).toBeDefined(); + expect((customOwner as any).type.name.value).toEqual('String'); +}); + +test('implicit owner fields from field level auth get added to the type', () => { + const validSchema = ` + type Post @model + { + id: ID + title: String + protectedField: String @auth(rules: [ + {allow: owner, ownerField: "postOwner"} + { allow: owner, ownerField: "customOwner", identityClaim: "sub"} + ]) + }`; + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + const schema = parse(out.schema); + const postType = getObjectType(schema, 'Post'); + expect(postType).toBeDefined(); + + const postOwnerField = getField(postType, 'postOwner'); + expect(postOwnerField).toBeDefined(); + expect((postOwnerField as any).type.name.value).toEqual('String'); + + const customOwner = getField(postType, 'customOwner'); + expect(customOwner).toBeDefined(); + expect((customOwner as any).type.name.value).toEqual('String'); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/searchable-auth.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/searchable-auth.test.ts new file mode 100644 index 00000000000..4cfd3491014 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/searchable-auth.test.ts @@ -0,0 +1,124 @@ +import { AuthTransformer, SEARCHABLE_AGGREGATE_TYPES } from '../'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { SearchableModelTransformer } from '@aws-amplify/graphql-searchable-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; +import { DocumentNode, ObjectTypeDefinitionNode, Kind, FieldDefinitionNode, parse, InputValueDefinitionNode } from 'graphql'; + +const getObjectType = (doc: DocumentNode, type: string): ObjectTypeDefinitionNode | undefined => { + return doc.definitions.find(def => def.kind === Kind.OBJECT_TYPE_DEFINITION && def.name.value === type) as + | ObjectTypeDefinitionNode + | undefined; +}; +const expectMultiple = (fieldOrType: ObjectTypeDefinitionNode | FieldDefinitionNode, directiveNames: string[]) => { + expect(directiveNames).toBeDefined(); + expect(directiveNames).toHaveLength(directiveNames.length); + expect(fieldOrType.directives.length).toEqual(directiveNames.length); + directiveNames.forEach(directiveName => { + expect(fieldOrType.directives).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: expect.objectContaining({ value: directiveName }), + }), + ]), + ); + }); +}; + +test('auth logic is enabled on owner/static rules in es request', () => { + const validSchema = ` + type Comment @model + @searchable + @auth(rules: [ + { allow: owner } + { allow: groups, groups: ["writer"]} + ]) + { + id: ID! + content: String + } + `; + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new SearchableModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + // expect response resolver to contain auth logic for owner rule + expect(out).toBeDefined(); + expect(out.pipelineFunctions['Query.searchComments.auth.1.req.vtl']).toContain( + '"terms": [$util.defaultIfNull($ctx.identity.claims.get("username"), $util.defaultIfNull($ctx.identity.claims.get("cognito:username"), "___xamznone____"))],', + ); + // expect response resolver to contain auth logic for group rule + expect(out.pipelineFunctions['Query.searchComments.auth.1.req.vtl']).toContain( + '#set( $staticGroupRoles = [{"claim":"cognito:groups","entity":"writer"}] )', + ); +}); + +test('auth logic is enabled for iam/apiKey auth rules', () => { + const expectedDirectives = ['aws_api_key', 'aws_iam']; + const validSchema = ` + type Post @model + @searchable + @auth(rules: [ + { allow: public, provider: apiKey } # api key is allowed + { allow: private, provider: iam } # auth roles are allowed + ]) { + id: ID! + content: String + secret: String @auth(rules: [{ allow: private, provider: iam }]) # only auth role can do crud on this + } + `; + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'API_KEY', + apiKeyConfig: { + description: 'E2E Test API Key', + apiKeyExpirationDays: 300, + }, + }, + { + authenticationType: 'AWS_IAM', + }, + ], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new SearchableModelTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + expect(out.schema).toBeDefined(); + const schemaDoc = parse(out.schema); + for (const aggregateType of SEARCHABLE_AGGREGATE_TYPES) { + expectMultiple(getObjectType(schemaDoc, aggregateType), expectedDirectives); + } + // expect the searchbable types to have the auth directives for total providers + // expect the allowed fields for agg to exclude secret + expect(out.pipelineFunctions['Query.searchPosts.auth.1.req.vtl']).toContain( + `#set( $allowedAggFields = ["createdAt","updatedAt","id","content"] )`, + ); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/test-helpers.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/test-helpers.ts new file mode 100644 index 00000000000..7b92473311a --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/test-helpers.ts @@ -0,0 +1,10 @@ +import { ObjectTypeDefinitionNode, FieldDefinitionNode, DocumentNode, Kind } from 'graphql'; + +export const getObjectType = (doc: DocumentNode, type: string): ObjectTypeDefinitionNode | undefined => { + return doc.definitions.find(def => def.kind === Kind.OBJECT_TYPE_DEFINITION && def.name.value === type) as + | ObjectTypeDefinitionNode + | undefined; +}; +export const getField = (obj: ObjectTypeDefinitionNode, fieldName: string): FieldDefinitionNode | void => { + return obj.fields?.find(f => f.name.value === fieldName); +}; diff --git a/packages/amplify-graphql-auth-transformer/src/accesscontrol/acm.ts b/packages/amplify-graphql-auth-transformer/src/accesscontrol/acm.ts new file mode 100644 index 00000000000..a4712b471cd --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/accesscontrol/acm.ts @@ -0,0 +1,197 @@ +import assert from 'assert'; + +type ACMConfig = { + resources: string[]; + operations: string[]; +}; + +type SetRoleInput = { + role: string; + operations: Array; + resource?: string; +}; + +type ValidateInput = { + role?: string; + resource?: string; + operations?: Array; +}; + +type ResourceOperationInput = { + operations: Array; + role?: string; + resource?: string; +}; + +/** + * Creates an access control matrix + * The following vectors are used + * - Roles + * - Resources + * - Operations + */ +export class AccessControlMatrix { + private roles: Array; + private operations: Array; + private resources: Array; + private matrix: Array>>; + + constructor(config: ACMConfig) { + this.operations = config.operations; + this.resources = config.resources; + this.matrix = new Array(); + this.roles = new Array(); + } + + public setRole(input: SetRoleInput): void { + const { role, resource, operations } = input; + this.validate({ resource, operations }); + let allowedVector: Array>; + if (!this.roles.includes(role)) { + allowedVector = this.getResourceOperationMatrix({ operations, resource }); + this.roles.push(input.role); + this.matrix.push(allowedVector); + assert(this.roles.length === this.matrix.length, 'Roles are not aligned with Roles added in Matrix'); + } else { + allowedVector = this.getResourceOperationMatrix({ operations, resource, role }); + const roleIndex = this.roles.indexOf(role); + this.matrix[roleIndex] = allowedVector; + } + } + + public hasRole(role: string): boolean { + return this.roles.includes(role); + } + + public getRoles(): Array { + return this.roles; + } + + public getResources(): Readonly> { + return this.resources; + } + + public hasResource(resource: string): boolean { + return this.resources.includes(resource); + } + + public isAllowed(role: string, resource: string, operation: string): boolean { + this.validate({ role, resource, operations: [operation] }); + const roleIndex = this.roles.indexOf(role); + const resourceIndex = this.resources.indexOf(resource); + const operationIndex = this.operations.indexOf(operation); + return this.matrix[roleIndex][resourceIndex][operationIndex]; + } + + public resetAccessForResource(resource: string): void { + this.validate({ resource }); + const resourceIndex = this.resources.indexOf(resource); + for (let i = 0; i < this.roles.length; i++) { + this.matrix[i][resourceIndex] = new Array(this.operations.length).fill(false); + } + } + + /** + * Given an operation returns the roles which have access to at least one resource + * If fullAccess is true then it returns roles which have operation access on all resources + * @param operation string + * @param fullAccess boolean + * @returns array of roles + */ + public getRolesPerOperation(operation: string, fullAccess: boolean = false): Array { + this.validate({ operations: [operation] }); + const operationIndex = this.operations.indexOf(operation); + const roles = new Array(); + for (let x = 0; x < this.roles.length; x++) { + let hasOperation: boolean = false; + if (fullAccess) { + hasOperation = this.resources.every((resource, idx) => { + return this.matrix[x][idx][operationIndex]; + }); + } else { + hasOperation = this.resources.some((resource, idx) => { + return this.matrix[x][idx][operationIndex]; + }); + } + if (hasOperation) roles.push(this.roles[x]); + } + return roles; + } + + /** + * @returns a map of role and their access + * this object can then be used in console.table() + */ + public getAcmPerRole(): Map { + const acmPerRole: Map = new Map(); + for (let i = 0; i < this.matrix.length; i++) { + let tableObj: any = {}; + for (let y = 0; y < this.matrix[i].length; y++) { + tableObj[this.resources[y]] = this.matrix[i][y].reduce((prev: any, resource: boolean, index: number) => { + prev[this.operations[index]] = resource; + return prev; + }, {}); + } + acmPerRole.set(this.roles[i], tableObj); + } + return acmPerRole; + } + + /** + * helpers + */ + private validate(input: ValidateInput) { + if (input.resource && !this.resources.includes(input.resource)) { + throw Error(`Resource: ${input.resource} is not configued in the ACM`); + } + if (input.role && !this.roles.includes(input.role)) { + throw Error(`Role: ${input.role} does not exist in ACM.`); + } + if (input.operations) { + input.operations.forEach(operation => { + if (this.operations.indexOf(operation) === -1) throw Error(`Operation: ${operation} does not exist in the ACM.`); + }); + } + } + + /** + * + * if singular resource is specified the operations are only applied on the resource + * a denied array for every other resource is returned in the matrix + * @param operations + * @param resource + * @returns a 2d matrix containg the access for each resource + */ + private getResourceOperationMatrix(input: ResourceOperationInput): Array> { + const { operations, resource, role } = input; + let fieldAllowVector: boolean[][] = []; + let operationList: boolean[] = this.getOperationList(operations); + if (role && resource) { + const roleIndex = this.roles.indexOf(role); + const resourceIndex = this.resources.indexOf(resource); + fieldAllowVector = this.matrix[roleIndex]; + fieldAllowVector[resourceIndex] = operationList; + return fieldAllowVector; + } + for (let i = 0; i < this.resources.length; i++) { + if (resource) { + if (this.resources.indexOf(resource) === i) { + fieldAllowVector.push(operationList); + } else { + fieldAllowVector.push(new Array(this.operations.length).fill(false)); + } + } else { + fieldAllowVector.push(operationList); + } + } + return fieldAllowVector; + } + + private getOperationList(operations: Array): Array { + let operationList: Array = new Array(); + for (let operation of this.operations) { + operationList.push(operations.includes(operation)); + } + return operationList; + } +} diff --git a/packages/amplify-graphql-auth-transformer/src/accesscontrol/index.ts b/packages/amplify-graphql-auth-transformer/src/accesscontrol/index.ts new file mode 100644 index 00000000000..e07fbd95541 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/accesscontrol/index.ts @@ -0,0 +1 @@ +export { AccessControlMatrix } from './acm'; diff --git a/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts b/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts new file mode 100644 index 00000000000..ac275a9ea12 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts @@ -0,0 +1,1083 @@ +import { + DirectiveWrapper, + TransformerContractError, + TransformerAuthBase, + InvalidDirectiveError, + MappingTemplate, + IAM_AUTH_ROLE_PARAMETER, + IAM_UNAUTH_ROLE_PARAMETER, + TransformerResolver, +} from '@aws-amplify/graphql-transformer-core'; +import { + DataSourceProvider, + MutationFieldType, + QueryFieldType, + TransformerTransformSchemaStepContextProvider, + TransformerContextProvider, + TransformerResolverProvider, + TransformerSchemaVisitStepContextProvider, + TransformerAuthProvider, + TransformerBeforeStepContextProvider, +} from '@aws-amplify/graphql-transformer-interfaces'; +import { + AUTH_PROVIDER_DIRECTIVE_MAP, + DEFAULT_GROUP_CLAIM, + DEFAULT_IDENTITY_CLAIM, + DEFAULT_GROUPS_FIELD, + DEFAULT_OWNER_FIELD, + MODEL_OPERATIONS, + SEARCHABLE_AGGREGATE_TYPES, + AuthRule, + authDirectiveDefinition, + ConfiguredAuthProviders, + getConfiguredAuthProviders, + AuthTransformerConfig, + collectFieldNames, + ModelOperation, + ensureAuthRuleDefaults, + getModelConfig, + validateFieldRules, + validateRules, + AuthProvider, + extendTypeWithDirectives, + RoleDefinition, + addDirectivesToOperation, + createPolicyDocumentForManagedPolicy, + getQueryFieldNames, + getMutationFieldNames, + addSubscriptionArguments, + fieldIsList, + getSubscriptionFieldNames, + addDirectivesToField, + getSearchableConfig, + getStackForField, + NONE_DS, + hasRelationalDirective, + getTable, + getRelationalPrimaryMap, +} from './utils'; +import { + DirectiveNode, + FieldDefinitionNode, + ObjectTypeDefinitionNode, + InterfaceTypeDefinitionNode, + Kind, + TypeDefinitionNode, +} from 'graphql'; +import { SubscriptionLevel, ModelDirectiveConfiguration } from '@aws-amplify/graphql-model-transformer'; +import { AccessControlMatrix } from './accesscontrol'; +import { getBaseType, makeDirective, makeField, makeNamedType, ResourceConstants, ModelResourceIDs } from 'graphql-transformer-common'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { + generateAuthExpressionForCreate, + generateAuthExpressionForUpdate, + generateAuthRequestExpression, + geneateAuthExpressionForDelete, + generateAuthExpressionForField, + generateFieldAuthResponse, + generateAuthExpressionForQueries, + generateAuthExpressionForSearchQueries, + generateAuthExpressionForSubscriptions, + setDeniedFieldFlag, + generateAuthExpressionForRelationQuery, +} from './resolvers'; +import { toUpper } from 'graphql-transformer-common'; +import { generateSandboxExpressionForField } from './resolvers/field'; + +// @ auth +// changing the schema +// - transformSchema +// adding resolver +// - generateResolver +// editing IAM policy +// - generateResolver (cdk) +// resolver mapping + +// resolver.ts for auth pipeline slots + +export class AuthTransformer extends TransformerAuthBase implements TransformerAuthProvider { + private config: AuthTransformerConfig; + private configuredAuthProviders: ConfiguredAuthProviders; + // access control + private roleMap: Map; + private authModelConfig: Map; + private authNonModelConfig: Map; + // model config + private modelDirectiveConfig: Map; + // schema generation + private seenNonModelTypes: Map>; + // iam policy generation + private generateIAMPolicyforUnauthRole: boolean; + private generateIAMPolicyforAuthRole: boolean; + private authPolicyResources = new Set(); + private unauthPolicyResources = new Set(); + + constructor(config: AuthTransformerConfig = { addAwsIamAuthInOutputSchema: false }) { + super('amplify-auth-transformer', authDirectiveDefinition); + this.config = config; + this.modelDirectiveConfig = new Map(); + this.seenNonModelTypes = new Map(); + this.authModelConfig = new Map(); + this.roleMap = new Map(); + this.generateIAMPolicyforUnauthRole = false; + this.generateIAMPolicyforAuthRole = false; + this.authNonModelConfig = new Map(); + } + + before = (context: TransformerBeforeStepContextProvider): void => { + // if there was no auth config in the props we add the authConfig from the context + this.config.authConfig = this.config.authConfig || context.authConfig; + this.configuredAuthProviders = getConfiguredAuthProviders(this.config); + }; + + object = (def: ObjectTypeDefinitionNode, directive: DirectiveNode, context: TransformerSchemaVisitStepContextProvider): void => { + const modelDirective = def.directives?.find(dir => dir.name.value === 'model'); + if (!modelDirective) { + throw new TransformerContractError('Types annotated with @auth must also be annotated with @model.'); + } + const typeName = def.name.value; + const authDir = new DirectiveWrapper(directive); + const rules: AuthRule[] = authDir.getArguments<{ rules: Array }>({ rules: [] }).rules; + ensureAuthRuleDefaults(rules); + // validate rules + validateRules(rules, this.configuredAuthProviders); + // create access control for object + const acm = new AccessControlMatrix({ + operations: MODEL_OPERATIONS, + resources: collectFieldNames(def), + }); + // Check the rules to see if we should generate Auth/Unauth Policies + this.setAuthPolicyFlag(rules); + this.setUnauthPolicyFlag(rules); + // add object into policy + this.addTypeToResourceReferences(def.name.value, rules); + // turn rules into roles and add into acm and roleMap + this.convertRulesToRoles(acm, rules); + this.modelDirectiveConfig.set(typeName, getModelConfig(modelDirective, typeName, context.isProjectUsingDataStore())); + this.authModelConfig.set(typeName, acm); + }; + + field = ( + parent: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode, + field: FieldDefinitionNode, + directive: DirectiveNode, + context: TransformerSchemaVisitStepContextProvider, + ): void => { + if (parent.kind === Kind.INTERFACE_TYPE_DEFINITION) { + throw new InvalidDirectiveError( + `The @auth directive cannot be placed on an interface's field. See ${parent.name.value}${field.name.value}`, + ); + } + const isParentTypeBuiltinType = + parent.name.value === context.output.getQueryTypeName() || + parent.name.value === context.output.getMutationTypeName() || + parent.name.value === context.output.getSubscriptionTypeName(); + + if (isParentTypeBuiltinType) { + console.warn( + `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.`, + ); + } + // context.api.host.resolver + // context.resolver -> resolver manager -> dynamodb, relation directives, searchable + // creates field resolver + + const modelDirective = parent.directives?.find(dir => dir.name.value === 'model'); + const typeName = parent.name.value; + const fieldName = field.name.value; + const authDir = new DirectiveWrapper(directive); + const rules: AuthRule[] = authDir.getArguments<{ rules: Array }>({ rules: [] }).rules; + ensureAuthRuleDefaults(rules); + validateFieldRules(rules, isParentTypeBuiltinType, modelDirective !== undefined, this.configuredAuthProviders); + + // regardless if a model directive is used we generate the policy for iam auth + this.setAuthPolicyFlag(rules); + this.setUnauthPolicyFlag(rules); + this.addFieldToResourceReferences(parent.name.value, field.name.value, rules); + + if (modelDirective) { + // auth on models + let acm: AccessControlMatrix; + // check if the parent is already in the model config if not add it + if (!this.modelDirectiveConfig.has(typeName)) { + this.modelDirectiveConfig.set(typeName, getModelConfig(modelDirective, typeName, context.isProjectUsingDataStore())); + acm = new AccessControlMatrix({ + operations: MODEL_OPERATIONS, + resources: collectFieldNames(parent), + }); + } else { + acm = this.authModelConfig.get(typeName) as AccessControlMatrix; + acm.resetAccessForResource(fieldName); + } + this.convertRulesToRoles(acm, rules, fieldName); + this.authModelConfig.set(typeName, acm); + } else { + // if @auth is used without @model only generate static group rules in the resolver + // since we only protect the field for non models we store the typeName + fieldName + // in the authNonModelTypes map + const staticRules = rules.filter((rule: AuthRule) => rule.allow !== 'owner' && !rule.groupsField); + const typeFieldName = `${typeName}:${fieldName}`; + const acm = new AccessControlMatrix({ + operations: ['read'], + resources: [typeFieldName], + }); + this.convertRulesToRoles(acm, staticRules, typeFieldName, ['read']); + this.authNonModelConfig.set(typeFieldName, acm); + } + }; + + transformSchema = (context: TransformerTransformSchemaStepContextProvider): void => { + const searchableAggregateServiceDirectives = new Set(); + const getOwnerFields = (acm: AccessControlMatrix) => { + return acm.getRoles().reduce((prev: string[], role: string) => { + if (this.roleMap.get(role)!.strategy === 'owner') prev.push(this.roleMap.get(role)!.entity!); + return prev; + }, []); + }; + for (let [modelName, acm] of this.authModelConfig) { + const def = context.output.getObject(modelName)!; + const modelHasSearchable = def.directives.some(dir => dir.name.value === 'searchable'); + // collect ownerFields and them in the model + this.addFieldsToObject(context, modelName, getOwnerFields(acm)); + // Get the directives we need to add to the GraphQL nodes + const providers = this.getAuthProviders(acm.getRoles()); + const directives = this.getServiceDirectives(providers, providers.length === 0 ? this.shouldAddDefaultServiceDirective() : false); + if (modelHasSearchable) { + providers.forEach(p => searchableAggregateServiceDirectives.add(p)); + } + if (directives.length > 0) { + extendTypeWithDirectives(context, modelName, directives); + } + this.protectSchemaOperations(context, def, acm); + this.propagateAuthDirectivesToNestedTypes(context, context.output.getObject(modelName)!, providers); + } + for (let [typeFieldName, acm] of this.authNonModelConfig) { + // protect the non model field + const [typeName, fieldName] = typeFieldName.split(':'); + const providers = this.getAuthProviders(acm.getRoles()); + const directives = this.getServiceDirectives(providers, false); + if (directives.length > 0) { + addDirectivesToField(context, typeName, fieldName, directives); + } + } + // add the service directives to the searchable aggregate types + if (searchableAggregateServiceDirectives.size > 0) { + const serviceDirectives = this.getServiceDirectives(Array.from(searchableAggregateServiceDirectives), false); + for (let aggType of SEARCHABLE_AGGREGATE_TYPES) { + extendTypeWithDirectives(context, aggType, serviceDirectives); + } + } + }; + + generateResolvers = (context: TransformerContextProvider): void => { + // generate iam policies + this.generateIAMPolicies(context); + // generate auth resolver code + for (let [modelName, acm] of this.authModelConfig) { + const indexKeyName = `${modelName}:indicies`; + const def = context.output.getObject(modelName)!; + const searchableDirective = def.directives.find(dir => dir.name.value === 'searchable'); + // queries + const queryFields = getQueryFieldNames(this.modelDirectiveConfig.get(modelName)!); + for (let query of queryFields.values()) { + switch (query.type) { + case QueryFieldType.GET: + this.protectGetResolver(context, def, query.typeName, query.fieldName, acm); + break; + case QueryFieldType.LIST: + this.protectListResolver(context, def, query.typeName, query.fieldName, acm); + break; + case QueryFieldType.SYNC: + this.protectSyncResolver(context, def, query.typeName, query.fieldName, acm); + break; + default: + throw new TransformerContractError('Unkown query field type'); + } + } + // protect additional query fields if they exist + if (context.metadata.has(indexKeyName)) { + for (let index of context.metadata.get>(indexKeyName)!.values()) { + const [indexName, indexQueryName] = index.split(':'); + this.protectListResolver(context, def, def.name.value, indexQueryName, acm, indexName); + } + } + // check if searchable if included in the typeName + if (searchableDirective) { + // protect search query + const config = getSearchableConfig(searchableDirective, modelName); + this.protectSearchResolver(context, def, context.output.getQueryTypeName()!, config.queries.search, acm); + } + // get fields specified in the schema + // if there is a role that does not have read access on the field then we create a field resolver + // or there is a relational directive on the field then we should protect that as well + const readRoles = acm.getRolesPerOperation('read'); + const modelFields = def.fields?.filter(f => acm.hasResource(f.name.value)) ?? []; + for (let field of modelFields) { + const allowedRoles = readRoles.filter(r => acm.isAllowed(r, field.name.value, 'read')); + const needsFieldResolver = allowedRoles.length < readRoles.length; + if (needsFieldResolver && field.type.kind === Kind.NON_NULL_TYPE) { + throw new InvalidDirectiveError(`\nPer-field auth on the required field ${field.name.value} is not supported with subscriptions. + Either make the field optional, set auth on the object and not the field, or disable subscriptions for the object (setting level to off or public)\n`); + } + if (hasRelationalDirective(field)) { + this.protectRelationalResolver(context, def, modelName, field, needsFieldResolver ? allowedRoles : null); + } else if (needsFieldResolver) { + this.protectFieldResolver(context, def, modelName, field.name.value, allowedRoles); + } + } + const mutationFields = getMutationFieldNames(this.modelDirectiveConfig.get(modelName)!); + for (let mutation of mutationFields.values()) { + switch (mutation.type) { + case MutationFieldType.CREATE: + this.protectCreateResolver(context, def, mutation.typeName, mutation.fieldName, acm); + break; + case MutationFieldType.UPDATE: + this.protectUpdateResolver(context, def, mutation.typeName, mutation.fieldName, acm); + break; + case MutationFieldType.DELETE: + this.protectDeleteResolver(context, def, mutation.typeName, mutation.fieldName, acm); + break; + default: + throw new TransformerContractError('Unkown Mutation field type'); + } + } + + const subscriptionFieldNames = getSubscriptionFieldNames(this.modelDirectiveConfig.get(modelName)!); + const subscriptionRoles = acm + .getRolesPerOperation('read') + .map(role => this.roleMap.get(role)!) + // for subscriptions we only use static rules or owner rule where the field is not a list + .filter(roleDef => (roleDef.strategy === 'owner' && !fieldIsList(def.fields ?? [], roleDef.entity!)) || roleDef.static); + for (let subscription of subscriptionFieldNames) { + this.protectSubscriptionResolver(context, subscription.typeName, subscription.fieldName, subscriptionRoles); + } + } + + for (let [typeFieldName, acm] of this.authNonModelConfig) { + // field resolvers + const [typeName, fieldName] = typeFieldName.split(':'); + const def = context.output.getObject(typeName); + this.protectFieldResolver(context, def, typeName, fieldName, acm.getRoles()); + } + }; + + protectSchemaOperations = ( + ctx: TransformerTransformSchemaStepContextProvider, + def: ObjectTypeDefinitionNode, + acm: AccessControlMatrix, + ): void => { + const modelConfig = this.modelDirectiveConfig.get(def.name.value)!; + const indexKeyName = `${def.name.value}:indicies`; + const searchableDirective = def.directives.find(dir => dir.name.value === 'searchable'); + const addServiceDirective = (typeName: string, operation: ModelOperation, operationName: string | null = null) => { + if (operationName) { + const includeDefault = this.doesTypeHaveRulesForOperation(acm, operation); + const providers = this.getAuthProviders(acm.getRolesPerOperation(operation, operation === 'delete')); + const operationDirectives = this.getServiceDirectives(providers, includeDefault); + if (operationDirectives.length > 0) { + addDirectivesToOperation(ctx, typeName, operationName, operationDirectives); + } + this.addOperationToResourceReferences(typeName, operationName, acm.getRoles()); + } + }; + // default model operations + addServiceDirective(ctx.output.getQueryTypeName()!, 'read', modelConfig?.queries?.get); + addServiceDirective(ctx.output.getQueryTypeName()!, 'read', modelConfig?.queries?.list); + addServiceDirective(ctx.output.getQueryTypeName()!, 'read', modelConfig?.queries?.sync); + addServiceDirective(ctx.output.getMutationTypeName()!, 'create', modelConfig?.mutations?.create); + addServiceDirective(ctx.output.getMutationTypeName()!, 'update', modelConfig?.mutations?.update); + addServiceDirective(ctx.output.getMutationTypeName()!, 'delete', modelConfig?.mutations?.delete); + // @index queries + if (ctx.metadata.has(indexKeyName)) { + for (let index of ctx.metadata.get>(indexKeyName)!.values()) { + addServiceDirective(ctx.output.getQueryTypeName(), 'read', index.split(':')[1]); + } + } + // @searchable + if (searchableDirective) { + const config = getSearchableConfig(searchableDirective, def.name.value); + addServiceDirective(ctx.output.getQueryTypeName(), 'read', config.queries.search); + } + + const subscriptions = modelConfig?.subscriptions; + if (subscriptions?.level === SubscriptionLevel.on) { + const subscriptionArguments = acm + .getRolesPerOperation('read') + .map(role => this.roleMap.get(role)!) + .filter(roleDef => roleDef.strategy === 'owner' && !fieldIsList(def.fields ?? [], roleDef.entity!)); + if (subscriptions.onCreate && modelConfig?.mutations?.create) { + for (let onCreateSub of subscriptions.onCreate) { + addServiceDirective(ctx.output.getSubscriptionTypeName()!, 'read', onCreateSub); + addSubscriptionArguments(ctx, onCreateSub, subscriptionArguments); + } + } + if (subscriptions.onUpdate && modelConfig?.mutations?.update) { + for (let onUpdateSub of subscriptions.onUpdate) { + addServiceDirective(ctx.output.getSubscriptionTypeName()!, 'read', onUpdateSub); + addSubscriptionArguments(ctx, onUpdateSub, subscriptionArguments); + } + } + if (subscriptions.onDelete && modelConfig?.mutations?.delete) { + for (let onDeleteSub of subscriptions.onDelete) { + addServiceDirective(ctx.output.getSubscriptionTypeName()!, 'read', onDeleteSub); + addSubscriptionArguments(ctx, onDeleteSub, subscriptionArguments); + } + } + } + }; + + protectGetResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + fieldName: string, + acm: AccessControlMatrix, + ): void => { + const resolver = ctx.resolvers.getResolver(typeName, fieldName) as TransformerResolverProvider; + const roleDefinitions = acm.getRolesPerOperation('read').map(r => this.roleMap.get(r)!); + const primaryFields = getTable(ctx, def).keySchema.map(att => att.attributeName); + const authExpression = generateAuthExpressionForQueries(this.configuredAuthProviders, roleDefinitions, def.fields ?? [], primaryFields); + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(authExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.req.vtl`), + ); + }; + protectListResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + fieldName: string, + acm: AccessControlMatrix, + indexName?: string, + ): void => { + const resolver = ctx.resolvers.getResolver(typeName, fieldName) as TransformerResolverProvider; + const roleDefinitions = acm.getRolesPerOperation('read').map(r => this.roleMap.get(r)!); + let primaryFields: Array; + const table = getTable(ctx, def); + try { + if (indexName) { + primaryFields = table.globalSecondaryIndexes + .find((gsi: any) => gsi.indexName === indexName) + .keySchema.map((att: any) => att.attributeName); + } else { + primaryFields = table.keySchema.map((att: any) => att.attributeName); + } + } catch (err) { + throw new InvalidDirectiveError(`Could not fetch keySchema for ${def.name.value}.`); + } + const authExpression = generateAuthExpressionForQueries( + this.configuredAuthProviders, + roleDefinitions, + def.fields ?? [], + primaryFields, + !!indexName, + ); + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(authExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.req.vtl`), + ); + }; + protectRelationalResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + field: FieldDefinitionNode, + fieldRoles: Array | null, + ): void => { + let fieldAuthExpression: string; + let relatedAuthExpression: string; + const relatedModelObject = this.getRelatedModelObject(ctx, getBaseType(field.type)); + if (this.authModelConfig.has(relatedModelObject.name.value)) { + const acm = this.authModelConfig.get(relatedModelObject.name.value); + const roleDefinitions = acm.getRolesPerOperation('read').map(r => this.roleMap.get(r)!); + const relationalPrimaryMap = getRelationalPrimaryMap(ctx, def, field, relatedModelObject); + relatedAuthExpression = generateAuthExpressionForRelationQuery( + this.configuredAuthProviders, + roleDefinitions, + relatedModelObject.fields ?? [], + relationalPrimaryMap, + ); + } else { + // if the related @model does not have auth we need to add a sandbox mode expression + relatedAuthExpression = generateSandboxExpressionForField((ctx as any).resourceHelper.api.sandboxModeEnabled); + } + // if there is field auth on the relational query then we need to add field auth read rules first + // in the request we then add the rules of the related type + if (fieldRoles) { + const roleDefinitions = fieldRoles.map(r => this.roleMap.get(r)!); + const hasSubsEnabled = this.modelDirectiveConfig.get(typeName)!.subscriptions.level === 'on'; + relatedAuthExpression = setDeniedFieldFlag('Mutation', hasSubsEnabled) + '\n' + relatedAuthExpression; + fieldAuthExpression = generateAuthExpressionForField(this.configuredAuthProviders, roleDefinitions, def.fields ?? []); + } + const resolver = ctx.resolvers.getResolver(typeName, field.name.value) as TransformerResolverProvider; + if (fieldAuthExpression) { + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(fieldAuthExpression, `${typeName}.${field.name.value}.{slotName}.{slotIndex}.req.vtl`), + MappingTemplate.s3MappingTemplateFromString( + relatedAuthExpression, + `${typeName}.${field.name.value}.{slotName}.{slotIndex}.res.vtl`, + ), + ); + } else { + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString( + relatedAuthExpression, + `${typeName}.${field.name.value}.{slotName}.{slotIndex}.req.vtl`, + ), + ); + } + }; + protectSyncResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + fieldName: string, + acm: AccessControlMatrix, + ): void => { + if (ctx.isProjectUsingDataStore()) { + const resolver = ctx.resolvers.getResolver(typeName, fieldName) as TransformerResolverProvider; + const roleDefinitions = acm.getRolesPerOperation('read').map(r => this.roleMap.get(r)!); + const primaryFields = getTable(ctx, def).keySchema.map(att => att.attributeName); + const authExpression = generateAuthExpressionForQueries( + this.configuredAuthProviders, + roleDefinitions, + def.fields ?? [], + primaryFields, + ); + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(authExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.req.vtl`), + ); + } + }; + /* + Searchable Auth + Protects + - Search Query + - Agg Query + */ + protectSearchResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + fieldName: string, + acm: AccessControlMatrix, + ): void => { + const acmFields = acm.getResources(); + const modelFields = def.fields ?? []; + // only add readonly fields if they exist + const allowedAggFields = modelFields.map(f => f.name.value).filter(f => !acmFields.includes(f)); + let leastAllowedFields = acmFields; + const resolver = ctx.resolvers.getResolver(typeName, fieldName) as TransformerResolverProvider; + // to protect search and aggregation queries we need to collect all the roles which can query + // and the allowed fields to run field auth on aggregation queries + const readRoleDefinitions = acm.getRolesPerOperation('read').map(role => { + const allowedFields = acmFields.filter(resource => acm.isAllowed(role, resource, 'read')); + const roleDefinition = this.roleMap.get(role)!; + // we add the allowed fields if the role does not have full access + // or if the rule is a dynamic rule (ex. ownerField, groupField) + if (allowedFields.length !== acmFields.length || !roleDefinition.static) { + roleDefinition.allowedFields = allowedFields; + leastAllowedFields = leastAllowedFields.filter(f => allowedFields.includes(f)); + } else { + roleDefinition.allowedFields = null; + } + return roleDefinition; + }); + // add readonly fields with all the fields every role has access to + allowedAggFields.push(...leastAllowedFields); + const authExpression = generateAuthExpressionForSearchQueries( + this.configuredAuthProviders, + readRoleDefinitions, + modelFields, + allowedAggFields, + ); + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(authExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.req.vtl`), + ); + }; + /* + Field Resovler can protect the following + - model fields + - fields on an operation (query/mutation) + - protection on predictions/function/no directive + Order of precendence + - resolver in api host (ex. @function, @predictions) + - no resolver -> creates a blank resolver will return the source field + */ + protectFieldResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + fieldName: string, + roles: Array, + ): void => { + const roleDefinitions = roles.map(r => this.roleMap.get(r)!); + const hasModelDirective = def.directives.some(dir => dir.name.value === 'model'); + const stack = getStackForField(ctx, def, fieldName, hasModelDirective); + if (ctx.api.host.hasResolver(typeName, fieldName)) { + // TODO: move pipeline resolvers created in the api host to the resolver manager + const fieldResolver = ctx.api.host.getResolver(typeName, fieldName) as any; + const fieldAuthExpression = generateAuthExpressionForField(this.configuredAuthProviders, roleDefinitions, []); + if (!ctx.api.host.hasDataSource(NONE_DS)) { + ctx.api.host.addNoneDataSource(NONE_DS); + } + const authFunction = ctx.api.host.addAppSyncFunction( + `${toUpper(typeName)}${toUpper(fieldName)}AuthFN`, + MappingTemplate.s3MappingTemplateFromString(fieldAuthExpression, `${typeName}.${fieldName}.auth.req.vtl`), + MappingTemplate.inlineTemplateFromString('$util.toJson({})'), + NONE_DS, + stack, + ); + (fieldResolver.pipelineConfig.functions as string[]).unshift(authFunction.functionId); + } else { + const fieldAuthExpression = generateAuthExpressionForField(this.configuredAuthProviders, roleDefinitions, def.fields ?? []); + const subsEnabled = hasModelDirective ? this.modelDirectiveConfig.get(typeName)!.subscriptions.level === 'on' : false; + const fieldResponse = generateFieldAuthResponse('Mutation', fieldName, subsEnabled); + const resolver = ctx.resolvers.addResolver( + typeName, + fieldName, + new TransformerResolver( + typeName, + fieldName, + MappingTemplate.s3MappingTemplateFromString(fieldAuthExpression, `${typeName}.${fieldName}.req.vtl`), + MappingTemplate.s3MappingTemplateFromString(fieldResponse, `${typeName}.${fieldName}.res.vtl`), + ['init'], + ['finish'], + ), + ); + resolver.mapToStack(stack); + } + }; + protectCreateResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + fieldName: string, + acm: AccessControlMatrix, + ): void => { + const resolver = ctx.resolvers.getResolver(typeName, fieldName) as TransformerResolverProvider; + const fields = acm.getResources(); + const createRoles = acm.getRolesPerOperation('create').map(role => { + const allowedFields = fields.filter(resource => acm.isAllowed(role, resource, 'create')); + const roleDefinition = this.roleMap.get(role)!; + roleDefinition.allowedFields = allowedFields.length === fields.length ? [] : allowedFields; + return roleDefinition; + }); + const authExpression = generateAuthExpressionForCreate(this.configuredAuthProviders, createRoles, def.fields ?? []); + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(authExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.req.vtl`), + ); + }; + protectUpdateResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + fieldName: string, + acm: AccessControlMatrix, + ): void => { + const resolver = ctx.resolvers.getResolver(typeName, fieldName) as TransformerResolverProvider; + const fields = acm.getResources(); + const updateDeleteRoles = [...new Set([...acm.getRolesPerOperation('update'), ...acm.getRolesPerOperation('delete')])]; + // protect fields to be updated and fields that can't be set to null (partial delete on fields) + const totalRoles = updateDeleteRoles.map(role => { + const allowedFields = fields.filter(resource => acm.isAllowed(role, resource, 'update')); + const nullAllowedFileds = fields.filter(resource => acm.isAllowed(role, resource, 'delete')); + const roleDefinition = this.roleMap.get(role)!; + roleDefinition.allowedFields = allowedFields.length === fields.length ? [] : allowedFields; + roleDefinition.nullAllowedFields = nullAllowedFileds.length === fields.length ? [] : nullAllowedFileds; + return roleDefinition; + }); + const datasource = ctx.api.host.getDataSource(`${def.name.value}Table`) as DataSourceProvider; + const requestExpression = generateAuthRequestExpression(); + const authExpression = generateAuthExpressionForUpdate(this.configuredAuthProviders, totalRoles, def.fields ?? []); + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(requestExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.req.vtl`), + MappingTemplate.s3MappingTemplateFromString(authExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.res.vtl`), + datasource, + ); + }; + + protectDeleteResolver = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + typeName: string, + fieldName: string, + acm: AccessControlMatrix, + ): void => { + const resolver = ctx.resolvers.getResolver(typeName, fieldName) as TransformerResolverProvider; + // only roles with full delete on every field can delete + const deleteRoles = acm.getRolesPerOperation('delete', true).map(role => this.roleMap.get(role)!); + const datasource = ctx.api.host.getDataSource(`${def.name.value}Table`) as DataSourceProvider; + const requestExpression = generateAuthRequestExpression(); + const authExpression = geneateAuthExpressionForDelete(this.configuredAuthProviders, deleteRoles, def.fields ?? []); + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(requestExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.req.vtl`), + MappingTemplate.s3MappingTemplateFromString(authExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.res.vtl`), + datasource, + ); + }; + + protectSubscriptionResolver = ( + ctx: TransformerContextProvider, + typeName: string, + fieldName: string, + subscriptionRoles: Array, + ): void => { + const resolver = ctx.resolvers.getResolver(typeName, fieldName) as TransformerResolverProvider; + const authExpression = generateAuthExpressionForSubscriptions(this.configuredAuthProviders, subscriptionRoles); + resolver.addToSlot( + 'auth', + MappingTemplate.s3MappingTemplateFromString(authExpression, `${typeName}.${fieldName}.{slotName}.{slotIndex}.req.vtl`), + ); + }; + + /* + Role Helpers + */ + private convertRulesToRoles(acm: AccessControlMatrix, authRules: AuthRule[], field?: string, overideOperations?: ModelOperation[]) { + for (let rule of authRules) { + let operations: ModelOperation[] = overideOperations ? overideOperations : rule.operations || MODEL_OPERATIONS; + if (rule.groups && !rule.groupsField) { + rule.groups.forEach(group => { + const groupClaim = rule.groupClaim || DEFAULT_GROUP_CLAIM; + const roleName = `${rule.provider}:staticGroup:${group}:${groupClaim}`; + if (!(roleName in this.roleMap)) { + this.roleMap.set(roleName, { + provider: rule.provider!, + strategy: rule.allow, + static: true, + claim: groupClaim, + entity: group, + }); + } + acm.setRole({ role: roleName, resource: field, operations }); + }); + } else { + let roleName: string; + let roleDefinition: RoleDefinition; + switch (rule.provider) { + case 'apiKey': + roleName = 'apiKey:public'; + roleDefinition = { provider: rule.provider, strategy: rule.allow, static: true }; + break; + case 'iam': + roleName = `iam:${rule.allow}`; + roleDefinition = { + provider: rule.provider, + strategy: rule.allow, + static: true, + claim: rule.allow === 'private' ? 'authRole' : 'unauthRole', + }; + break; + case 'oidc': + case 'userPools': + if (rule.allow === 'groups') { + const groupClaim = rule.groupClaim || DEFAULT_GROUP_CLAIM; + const groupsField = rule.groupsField || DEFAULT_GROUPS_FIELD; + roleName = `${rule.provider}:dynamicGroup:${groupsField}:${groupClaim}`; + roleDefinition = { + provider: rule.provider, + strategy: rule.allow, + static: false, + claim: groupClaim, + entity: groupsField, + }; + } else if (rule.allow === 'owner') { + const ownerField = rule.ownerField || DEFAULT_OWNER_FIELD; + const ownerClaim = rule.identityClaim || DEFAULT_IDENTITY_CLAIM; + roleName = `${rule.provider}:owner:${ownerField}:${ownerClaim}`; + roleDefinition = { + provider: rule.provider, + strategy: rule.allow, + static: false, + claim: ownerClaim, + entity: ownerField, + }; + } else if (rule.allow === 'private') { + roleName = `${rule.provider}:${rule.allow}`; + roleDefinition = { + provider: rule.provider, + strategy: rule.allow, + static: true, + }; + } else { + throw new TransformerContractError(`Could not create a role from ${JSON.stringify(rule)}`); + } + break; + default: + throw new TransformerContractError(`Could not create a role from ${JSON.stringify(rule)}`); + } + if (!(roleName in this.roleMap)) { + this.roleMap.set(roleName, roleDefinition); + } + acm.setRole({ role: roleName, resource: field, operations }); + } + } + } + + private doesTypeHaveRulesForOperation(acm: AccessControlMatrix, operation: ModelOperation) { + const rolesHasDefaultProvider = (roles: Array) => { + return roles.some(r => this.roleMap.get(r)!.provider! === this.configuredAuthProviders.default); + }; + const roles = acm.getRolesPerOperation(operation, operation === 'delete'); + return rolesHasDefaultProvider(roles) || (roles.length === 0 && this.shouldAddDefaultServiceDirective()); + } + private getAuthProviders(roles: Array): Array { + const providers: Set = new Set(); + // get the roles created for type + for (let role of roles) { + providers.add(this.roleMap.get(role)!.provider); + } + if (this.configuredAuthProviders.hasAdminUIEnabled) { + providers.add('iam'); + } + return Array.from(providers); + } + + /* + Schema Generation Helpers + */ + private getRelatedModelObject(ctx: TransformerContextProvider, typeName: string) { + const modelObjectName: string = ModelResourceIDs.IsModelConnectionType(typeName) + ? ModelResourceIDs.GetModelFromConnectionType(typeName) + : typeName; + if (!ctx.output.hasType(modelObjectName)) { + throw new TransformerContractError(`Could not find type: ${modelObjectName}`); + } else { + return ctx.output.getObject(modelObjectName); + } + } + private addFieldsToObject(ctx: TransformerTransformSchemaStepContextProvider, modelName: string, ownerFields: Array) { + const modelObject = ctx.output.getObject(modelName)!; + const existingFields = collectFieldNames(modelObject); + const ownerFieldsToAdd = ownerFields.filter(field => !existingFields.includes(field)); + for (let ownerField of ownerFieldsToAdd) { + (modelObject as any).fields.push(makeField(ownerField, [], makeNamedType('String'))); + } + ctx.output.putType(modelObject); + } + private propagateAuthDirectivesToNestedTypes( + ctx: TransformerTransformSchemaStepContextProvider, + def: ObjectTypeDefinitionNode, + providers: Array, + ) { + const nonModelTypePredicate = (fieldType: TypeDefinitionNode): TypeDefinitionNode | undefined => { + if (fieldType) { + if (fieldType.kind !== 'ObjectTypeDefinition') { + return undefined; + } + const typeModel = fieldType.directives!.find(dir => dir.name.value === 'model'); + return typeModel !== undefined ? undefined : fieldType; + } + return fieldType; + }; + const nonModelFieldTypes = def + .fields!.map(f => ctx.output.getType(getBaseType(f.type)) as TypeDefinitionNode) + .filter(nonModelTypePredicate); + for (const nonModelFieldType of nonModelFieldTypes) { + const nonModelName = nonModelFieldType.name.value; + const hasSeenType = this.seenNonModelTypes.has(nonModelFieldType.name.value); + let directives = this.getServiceDirectives(providers, hasSeenType); + if (!hasSeenType) { + this.seenNonModelTypes.set(nonModelName, new Set([...directives.map(dir => dir.name.value)])); + // since we haven't seen this type before we add it to the iam policy resource sets + const hasIAM = directives.some(dir => dir.name.value === 'aws_iam') || this.configuredAuthProviders.default === 'iam'; + if (hasIAM) { + this.unauthPolicyResources.add(`${nonModelFieldType.name.value}/null`); + this.authPolicyResources.add(`${nonModelFieldType.name.value}/null`); + } + } else { + const currentDirectives = this.seenNonModelTypes.get(nonModelName)!; + directives = directives.filter(dir => !currentDirectives.has(dir.name.value)); + this.seenNonModelTypes.set(nonModelName, new Set([...directives.map(dir => dir.name.value), ...currentDirectives])); + } + // we continue to check the nested types if we find that directives list is not empty or if haven't seen the type before + if (directives.length > 0 || !hasSeenType) { + extendTypeWithDirectives(ctx, nonModelFieldType.name.value, directives); + this.propagateAuthDirectivesToNestedTypes(ctx, nonModelFieldType, providers); + } + } + } + + private getServiceDirectives(providers: Readonly>, addDefaultIfNeeded: boolean = true): Array { + if (providers.length === 0) { + return []; + } + const directives: Array = new Array(); + /* + We only add a service directive if it's not the default or + it's the default but there are other rules under different providers. + For fields we don't we don't add the default since it would open up access. + */ + const addDirectiveIfNeeded = (provider: AuthProvider, directiveName: string): void => { + if ( + (this.configuredAuthProviders.default !== provider && providers.some(p => p === provider)) || + (this.configuredAuthProviders.default === provider && providers.some(p => p !== provider && addDefaultIfNeeded === true)) + ) { + directives.push(makeDirective(directiveName, [])); + } + }; + + for (let [authProvider, directiveName] of AUTH_PROVIDER_DIRECTIVE_MAP) { + addDirectiveIfNeeded(authProvider, directiveName); + } + /* + If we have any rules for the default provider AND those with other providers, + we add the default provider directive, regardless of the addDefaultDirective value + + For example if we have this rule and the default is API_KEY + @auth(rules: [{ allow: owner }, { allow: public, operations: [read] }]) + + Then we need to add @aws_api_key on the queries along with @aws_cognito_user_pools, but we + cannot add @aws_api_key to other operations since their is no rule granted access to it + */ + if ( + providers.some(p => p === this.configuredAuthProviders.default) && + providers.some(p => p !== this.configuredAuthProviders.default) && + !directives.some(d => d.name.value === AUTH_PROVIDER_DIRECTIVE_MAP.get(this.configuredAuthProviders.default)) + ) { + directives.push(makeDirective(AUTH_PROVIDER_DIRECTIVE_MAP.get(this.configuredAuthProviders.default) as string, [])); + } + return directives; + } + /** + * When AdminUI is enabled, all the types and operations get IAM auth. If the default auth mode is + * not IAM all the fields will need to have the default auth mode directive to ensure both IAM and deault + * auth modes are allowed to access + * default auth provider needs to be added if AdminUI is enabled and default auth type is not IAM + * @returns boolean + */ + private shouldAddDefaultServiceDirective(): boolean { + return this.configuredAuthProviders.hasAdminUIEnabled && this.config.authConfig.defaultAuthentication.authenticationType !== 'AWS_IAM'; + } + /* + IAM Helpers + */ + private generateIAMPolicies(ctx: TransformerContextProvider) { + // iam + if (this.generateIAMPolicyforAuthRole) { + // Sanity check to make sure we're not generating invalid policies, where no resources are defined. + if (this.authPolicyResources.size === 0) { + // When AdminUI is enabled, IAM auth is added but it does not need any policies to be generated + if (!this.configuredAuthProviders.hasAdminUIEnabled) { + throw new TransformerContractError('AuthRole policies should be generated, but no resources were added.'); + } + } else { + const authRoleParameter = (ctx.stackManager.getParameter(IAM_AUTH_ROLE_PARAMETER) as cdk.CfnParameter).valueAsString; + const authPolicyDocuments = createPolicyDocumentForManagedPolicy(this.authPolicyResources); + const rootStack = ctx.stackManager.rootStack; + // we need to add the arn path as this is something cdk is looking for when using imported roles in policies + const iamAuthRoleArn = iam.Role.fromRoleArn( + rootStack, + 'auth-role-name', + `arn:aws:iam::${cdk.Stack.of(rootStack).account}:role/${authRoleParameter}`, + ); + for (let i = 0; i < authPolicyDocuments.length; i++) { + const paddedIndex = `${i + 1}`.padStart(2, '0'); + const resourceName = `${ResourceConstants.RESOURCES.AuthRolePolicy}${paddedIndex}`; + new iam.ManagedPolicy(rootStack, resourceName, { + document: iam.PolicyDocument.fromJson(authPolicyDocuments[i]), + roles: [iamAuthRoleArn], + }); + } + } + } + if (this.generateIAMPolicyforUnauthRole) { + // Sanity check to make sure we're not generating invalid policies, where no resources are defined. + if (this.unauthPolicyResources.size === 0) { + throw new TransformerContractError('UnauthRole policies should be generated, but no resources were added'); + } + const unauthRoleParameter = (ctx.stackManager.getParameter(IAM_UNAUTH_ROLE_PARAMETER) as cdk.CfnParameter).valueAsString; + const unauthPolicyDocuments = createPolicyDocumentForManagedPolicy(this.unauthPolicyResources); + const rootStack = ctx.stackManager.rootStack; + const iamUnauthRoleArn = iam.Role.fromRoleArn( + rootStack, + 'unauth-role-name', + `arn:aws:iam::${cdk.Stack.of(rootStack).account}:role/${unauthRoleParameter}`, + ); + for (let i = 0; i < unauthPolicyDocuments.length; i++) { + const paddedIndex = `${i + 1}`.padStart(2, '0'); + const resourceName = `${ResourceConstants.RESOURCES.UnauthRolePolicy}${paddedIndex}`; + new iam.ManagedPolicy(ctx.stackManager.rootStack, resourceName, { + document: iam.PolicyDocument.fromJson(unauthPolicyDocuments[i]), + roles: [iamUnauthRoleArn], + }); + } + } + } + private setAuthPolicyFlag(rules: AuthRule[]): void { + if (rules.length === 0 || this.generateIAMPolicyforAuthRole === true) { + return; + } + for (const rule of rules) { + if ((rule.allow === 'private' || rule.allow === 'public') && rule.provider === 'iam') { + this.generateIAMPolicyforAuthRole = true; + return; + } + } + } + + private setUnauthPolicyFlag(rules: AuthRule[]): void { + if (rules.length === 0 || this.generateIAMPolicyforUnauthRole === true) { + return; + } + for (const rule of rules) { + if (rule.allow === 'public' && rule.provider === 'iam') { + this.generateIAMPolicyforUnauthRole = true; + return; + } + } + } + + private addOperationToResourceReferences(operationName: string, fieldName: string, roles: Array) { + const iamPublicRolesExist = roles.some(r => this.roleMap.get(r)!.provider === 'iam' && this.roleMap.get(r)!.strategy === 'public'); + const iamPrivateRolesExist = roles.some(r => this.roleMap.get(r)!.provider === 'iam' && this.roleMap.get(r)!.strategy === 'private'); + + if (iamPublicRolesExist) { + this.unauthPolicyResources.add(`${operationName}/${fieldName}`); + this.authPolicyResources.add(`${operationName}/${fieldName}`); + } + if (iamPrivateRolesExist) { + this.authPolicyResources.add(`${operationName}/${fieldName}`); + } + } + + /** + * TODO: Change Resource Ref Object/Field Functions to work with roles + */ + private addTypeToResourceReferences(typeName: string, rules: AuthRule[]): void { + const iamPublicRulesExist = rules.some(r => r.allow === 'public' && r.provider === 'iam' && r.generateIAMPolicy); + const iamPrivateRulesExist = rules.some(r => r.allow === 'private' && r.provider === 'iam' && r.generateIAMPolicy); + + if (iamPublicRulesExist) { + this.unauthPolicyResources.add(`${typeName}/null`); + this.authPolicyResources.add(`${typeName}/null`); + } + if (iamPrivateRulesExist) { + this.authPolicyResources.add(`${typeName}/null`); + } + } + + private addFieldToResourceReferences(typeName: string, fieldName: string, rules: AuthRule[]): void { + const iamPublicRulesExist = rules.some(r => r.allow === 'public' && r.provider === 'iam' && r.generateIAMPolicy); + const iamPrivateRulesExist = rules.some(r => r.allow === 'private' && r.provider === 'iam' && r.generateIAMPolicy); + + if (iamPublicRulesExist) { + this.unauthPolicyResources.add(`${typeName}/${fieldName}`); + this.authPolicyResources.add(`${typeName}/${fieldName}`); + } + if (iamPrivateRulesExist) { + this.authPolicyResources.add(`${typeName}/${fieldName}`); + } + } +} diff --git a/packages/amplify-graphql-auth-transformer/src/index.ts b/packages/amplify-graphql-auth-transformer/src/index.ts new file mode 100644 index 00000000000..5382eacf29f --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/index.ts @@ -0,0 +1,4 @@ +export * from './graphql-auth-transformer'; +export * from './utils/constants'; +export * from './utils/definitions'; +export { AccessControlMatrix } from './accesscontrol'; diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/field.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/field.ts new file mode 100644 index 00000000000..c7e9e4bfa2d --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/field.ts @@ -0,0 +1,162 @@ +import { OPERATION_KEY } from '@aws-amplify/graphql-model-transformer'; +import { FieldDefinitionNode } from 'graphql'; +import { + Expression, + iff, + not, + ref, + equals, + str, + compoundExpression, + printBlock, + toJson, + set, + methodCall, + nul, + ifElse, + bool, + raw, + forEach, + qref, + notEquals, + obj, + list, +} from 'graphql-mapping-template'; +import { + RoleDefinition, + splitRoles, + COGNITO_AUTH_TYPE, + OIDC_AUTH_TYPE, + ConfiguredAuthProviders, + fieldIsList, + IS_AUTHORIZED_FLAG, + API_KEY_AUTH_TYPE, +} from '../utils'; +import { getOwnerClaim, generateStaticRoleExpression, apiKeyExpression, iamExpression, emptyPayload, getIdentityClaimExp } from './helpers'; + +// Field Read VTL Functions +const generateDynamicAuthReadExpression = (roles: Array, fields: ReadonlyArray) => { + const ownerExpressions = new Array(); + const dynamicGroupExpressions = new Array(); + roles.forEach((role, idx) => { + const entityIsList = fieldIsList(fields, role.entity!); + if (role.strategy === 'owner') { + ownerExpressions.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref(`ownerEntity${idx}`), methodCall(ref('util.defaultIfNull'), ref(`ctx.source.${role.entity!}`), nul())), + set(ref(`ownerClaim${idx}`), getOwnerClaim(role.claim!)), + ...(entityIsList + ? [ + forEach(ref('allowedOwner'), ref(`ownerEntity${idx}`), [ + iff( + equals(ref('allowedOwner'), ref(`ownerClaim${idx}`)), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), raw('#break')]), + ), + ]), + ] + : [iff(equals(ref(`ownerEntity${idx}`), ref(`ownerClaim${idx}`)), set(ref(IS_AUTHORIZED_FLAG), bool(true)))]), + ]), + ), + ); + } + if (role.strategy === 'groups') { + dynamicGroupExpressions.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref(`groupEntity${idx}`), methodCall(ref('util.defaultIfNull'), ref(`ctx.source.${role.entity!}`), nul())), + set(ref(`groupClaim${idx}`), getIdentityClaimExp(str(role.claim), list([]))), + entityIsList + ? forEach(ref('userGroup'), ref(`groupClaim${idx}`), [ + iff( + methodCall(ref(`groupEntity${idx}.contains`), ref('userGroup')), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), raw('#break')]), + ), + ]) + : iff(ref(`groupClaim${idx}.contains($groupEntity${idx})`), set(ref(IS_AUTHORIZED_FLAG), bool(true))), + ]), + ), + ); + } + }); + return [...(ownerExpressions.length > 0 || dynamicGroupExpressions.length > 0 ? [...ownerExpressions, ...dynamicGroupExpressions] : [])]; +}; + +export const generateAuthExpressionForField = ( + provider: ConfiguredAuthProviders, + roles: Array, + fields: ReadonlyArray, +): string => { + const { cogntoStaticRoles, cognitoDynamicRoles, oidcStaticRoles, oidcDynamicRoles, iamRoles, apiKeyRoles } = splitRoles(roles); + const totalAuthExpressions: Array = [set(ref(IS_AUTHORIZED_FLAG), bool(false))]; + if (provider.hasApiKey) { + totalAuthExpressions.push(apiKeyExpression(apiKeyRoles)); + } + if (provider.hasIAM) { + totalAuthExpressions.push(iamExpression(iamRoles, provider.hasAdminUIEnabled, provider.adminUserPoolID)); + } + if (provider.hasUserPools) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(COGNITO_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(cogntoStaticRoles), + ...generateDynamicAuthReadExpression(cognitoDynamicRoles, fields), + ]), + ), + ); + } + if (provider.hasOIDC) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(OIDC_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(oidcStaticRoles), + ...generateDynamicAuthReadExpression(oidcDynamicRoles, fields), + ]), + ), + ); + } + totalAuthExpressions.push(iff(not(ref(IS_AUTHORIZED_FLAG)), ref('util.unauthorized()'))); + return printBlock('Field Authorization Steps')(compoundExpression([...totalAuthExpressions, emptyPayload])); +}; + +/** + * This is the response resolver for fields to protect subscriptions + * @param subscriptionsEnabled + * @returns + */ +export const generateFieldAuthResponse = (operation: string, fieldName: string, subscriptionsEnabled: boolean): string => { + if (subscriptionsEnabled) { + return printBlock('Checking for allowed operations which can return this field')( + compoundExpression([ + set(ref('operation'), methodCall(ref('util.defaultIfNull'), methodCall(ref('ctx.source.get'), str(OPERATION_KEY)), nul())), + ifElse(equals(ref('operation'), str(operation)), toJson(nul()), toJson(ref(`context.source.${fieldName}`))), + ]), + ); + } + return printBlock('Return Source Field')(toJson(ref(`context.source.${fieldName}`))); +}; + +export const setDeniedFieldFlag = (operation: string, subscriptionsEnabled: boolean): string => { + if (subscriptionsEnabled) { + return printBlock('Check if subscriptions is protected')( + compoundExpression([ + iff( + equals(methodCall(ref('util.defaultIfNull'), methodCall(ref('ctx.source.get'), str(OPERATION_KEY)), nul()), str(operation)), + qref(methodCall(ref('ctx.result.put'), str('deniedField'), bool(true))), + ), + ]), + ); + } + return ''; +}; + +export const generateSandboxExpressionForField = (sandboxEnabled: boolean): string => { + let exp: Expression; + if (sandboxEnabled) exp = iff(notEquals(methodCall(ref('util.authType')), str(API_KEY_AUTH_TYPE)), methodCall(ref('util.unauthorized'))); + else exp = methodCall(ref('util.unauthorized')); + return printBlock(`Sandbox Mode ${sandboxEnabled ? 'Enabled' : 'Disabled'}`)(compoundExpression([exp, toJson(obj({}))])); +}; diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/helpers.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/helpers.ts new file mode 100644 index 00000000000..5ac4e417b94 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/helpers.ts @@ -0,0 +1,152 @@ +import { + qref, + Expression, + ifElse, + iff, + methodCall, + not, + ref, + set, + str, + raw, + obj, + bool, + compoundExpression, + printBlock, + toJson, + forEach, + list, + equals, + or, +} from 'graphql-mapping-template'; +import { NONE_VALUE } from 'graphql-transformer-common'; +import { + DEFAULT_COGNITO_IDENTITY_CLAIM, + RoleDefinition, + IS_AUTHORIZED_FLAG, + ALLOWED_FIELDS, + API_KEY_AUTH_TYPE, + ADMIN_ROLE, + IAM_AUTH_TYPE, + MANAGE_ROLE, +} from '../utils'; + +// note in the resolver that operation is protected by auth +export const setHasAuthExpression: Expression = qref(methodCall(ref('ctx.stash.put'), str('hasAuth'), bool(true))); + +// since the keySet returns a set we can convert it to a list by converting to json and parsing back as a list +export const getInputFields = (): Expression => { + return set(ref('inputFields'), methodCall(ref('util.parseJson'), methodCall(ref('util.toJson'), ref('ctx.args.input.keySet()')))); +}; + +export const getIdentityClaimExp = (value: Expression, defaultValueExp: Expression): Expression => { + return methodCall(ref('util.defaultIfNull'), methodCall(ref('ctx.identity.claims.get'), value), defaultValueExp); +}; + +// for create mutations and subscriptions +export const addAllowedFieldsIfElse = (fieldKey: string, breakLoop: boolean = false): Expression => { + return ifElse( + not(ref(`${fieldKey}.isEmpty()`)), + qref(methodCall(ref(`${ALLOWED_FIELDS}.addAll`), ref(fieldKey))), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), ...(breakLoop ? [raw('#break')] : [])]), + ); +}; + +// iam check +export const iamCheck = (claim: string, exp: Expression) => iff(equals(ref('ctx.identity.userArn'), ref(`ctx.stash.${claim}`)), exp); + +/** + * Behavior of auth v1 + * Order of how the owner value is retrieved from the jwt + * if claim is username + * 1. username + * 2. cognito:username + * 3. none value + * + * if claim is custom + * 1. custom + * 2. none value + */ +export const getOwnerClaim = (ownerClaim: string): Expression => { + if (ownerClaim === 'username') { + return getIdentityClaimExp(str(ownerClaim), getIdentityClaimExp(str(DEFAULT_COGNITO_IDENTITY_CLAIM), str(NONE_VALUE))); + } + return getIdentityClaimExp(str(ownerClaim), str(NONE_VALUE)); +}; + +export const responseCheckForErrors = () => + iff(ref('ctx.error'), methodCall(ref('util.error'), ref('ctx.error.message'), ref('ctx.error.type'))); + +// Common Expressions + +export const generateStaticRoleExpression = (roles: Array): Array => { + const staticRoleExpression: Array = new Array(); + const privateRoleIdx = roles.findIndex(r => r.strategy === 'private'); + if (privateRoleIdx > -1) { + staticRoleExpression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + roles.splice(privateRoleIdx, 1); + } + if (roles.length > 0) { + staticRoleExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref('staticGroupRoles'), raw(JSON.stringify(roles.map(r => ({ claim: r.claim, entity: r.entity }))))), + forEach(ref('groupRole'), ref('staticGroupRoles'), [ + set(ref('groupsInToken'), getIdentityClaimExp(ref('groupRole.claim'), list([]))), + iff( + methodCall(ref('groupsInToken.contains'), ref('groupRole.entity')), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), raw(`#break`)]), + ), + ]), + ]), + ), + ); + } + return staticRoleExpression; +}; + +export const apiKeyExpression = (roles: Array) => + iff( + equals(ref('util.authType()'), str(API_KEY_AUTH_TYPE)), + compoundExpression([...(roles.length > 0 ? [set(ref(IS_AUTHORIZED_FLAG), bool(true))] : [])]), + ); + +export const iamExpression = (roles: Array, adminuiEnabled: boolean = false, adminUserPoolID?: string) => { + const expression = new Array(); + // allow if using admin ui + if (adminuiEnabled) { + expression.push( + iff( + or([ + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID}${ADMIN_ROLE}`)), + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID}${MANAGE_ROLE}`)), + ]), + raw('#return($util.toJson({})'), + ), + ); + } + if (roles.length > 0) { + for (let role of roles) { + expression.push(iff(not(ref(IS_AUTHORIZED_FLAG)), iamCheck(role.claim!, set(ref(IS_AUTHORIZED_FLAG), bool(true))))); + } + } + return iff(equals(ref('util.authType()'), str(IAM_AUTH_TYPE)), compoundExpression(expression)); +}; + +// Get Request for Update and Delete +export const generateAuthRequestExpression = () => { + const statements = [ + set(ref('GetRequest'), obj({ version: str('2018-05-29'), operation: str('GetItem') })), + ifElse( + ref('ctx.stash.metadata.modelObjectKey'), + set(ref('key'), ref('ctx.stash.metadata.modelObjectKey')), + compoundExpression([set(ref('key'), obj({ id: methodCall(ref('util.dynamodb.toDynamoDB'), ref('ctx.args.input.id')) }))]), + ), + qref(methodCall(ref('GetRequest.put'), str('key'), ref('key'))), + toJson(ref('GetRequest')), + ]; + return printBlock('Get Request template')(compoundExpression(statements)); +}; + +export const emptyPayload = toJson(raw(JSON.stringify({ version: '2018-05-29', payload: {} }))); diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/index.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/index.ts new file mode 100644 index 00000000000..048579c9f84 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/index.ts @@ -0,0 +1,8 @@ +export { generateAuthExpressionForQueries, generateAuthExpressionForRelationQuery } from './query'; +export { generateAuthExpressionForSearchQueries } from './search'; +export { generateAuthExpressionForCreate } from './mutation.create'; +export { generateAuthExpressionForUpdate } from './mutation.update'; +export { geneateAuthExpressionForDelete } from './mutation.delete'; +export { generateAuthExpressionForField, generateFieldAuthResponse, setDeniedFieldFlag } from './field'; +export { generateAuthExpressionForSubscriptions } from './subscriptions'; +export { generateAuthRequestExpression } from './helpers'; diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.create.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.create.ts new file mode 100644 index 00000000000..22dd6a95d22 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.create.ts @@ -0,0 +1,274 @@ +import { FieldDefinitionNode } from 'graphql'; +import { + Expression, + compoundExpression, + set, + ref, + bool, + raw, + iff, + and, + isNullOrEmpty, + not, + methodCall, + qref, + list, + nul, + forEach, + equals, + str, + or, + printBlock, +} from 'graphql-mapping-template'; +import { + getOwnerClaim, + getIdentityClaimExp, + getInputFields, + addAllowedFieldsIfElse, + emptyPayload, + setHasAuthExpression, + iamCheck, +} from './helpers'; +import { + ADMIN_ROLE, + API_KEY_AUTH_TYPE, + COGNITO_AUTH_TYPE, + ConfiguredAuthProviders, + IAM_AUTH_TYPE, + MANAGE_ROLE, + OIDC_AUTH_TYPE, + RoleDefinition, + splitRoles, + fieldIsList, + IS_AUTHORIZED_FLAG, + ALLOWED_FIELDS, + DENIED_FIELDS, +} from '../utils'; + +/** + * There is only one role for ApiKey we can use the first index + * @param roles + * @returns Expression | null + */ +const apiKeyExpression = (roles: Array) => { + const expression = new Array(); + if (roles.length === 0) { + return iff(equals(ref('util.authType()'), str(API_KEY_AUTH_TYPE)), ref('util.unauthorized()')); + } + if (roles[0].allowedFields!.length > 0) { + expression.push(set(ref(`${ALLOWED_FIELDS}`), raw(JSON.stringify(roles[0].allowedFields)))); + } else { + expression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + } + return iff(equals(ref('util.authType()'), str(API_KEY_AUTH_TYPE)), compoundExpression(expression)); +}; + +/** + * No need to combine allowed fields as the request can only be signed by one iam role + * @param roles + * @returns + */ +const iamExpression = (roles: Array, hasAdminUIEnabled: boolean = false, adminUserPoolID?: string) => { + const expression = new Array(); + // allow if using admin ui + if (hasAdminUIEnabled) { + expression.push( + iff( + or([ + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID!}${ADMIN_ROLE}`)), + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID!}${MANAGE_ROLE}`)), + ]), + raw('#return($util.toJson({})'), + ), + ); + } + if (roles.length > 0) { + for (let role of roles) { + if (role.allowedFields!.length > 0) { + expression.push( + iamCheck(role.claim!, compoundExpression([set(ref(`${ALLOWED_FIELDS}`), raw(JSON.stringify(role.allowedFields)))])), + ); + } else { + expression.push(iamCheck(role.claim!, set(ref(IS_AUTHORIZED_FLAG), bool(true)))); + } + } + } else { + expression.push(ref('util.unauthorized()')); + } + return iff(equals(ref('util.authType()'), str(IAM_AUTH_TYPE)), compoundExpression(expression)); +}; + +const generateStaticRoleExpression = (roles: Array): Array => { + const staticRoleExpression: Array = new Array(); + const privateRoleIdx = roles.findIndex(r => r.strategy === 'private'); + if (privateRoleIdx > -1) { + const privateRole = roles[privateRoleIdx]; + if (privateRole.allowedFields!.length > 0) { + staticRoleExpression.push(qref(methodCall(ref(`${ALLOWED_FIELDS}.addAll`), raw(JSON.stringify(privateRole.allowedFields))))); + } else { + staticRoleExpression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + } + roles.splice(privateRoleIdx, 1); + } + if (roles.length > 0) { + staticRoleExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set( + ref('staticGroupRoles'), + raw(JSON.stringify(roles.map(r => ({ claim: r.claim, entity: r.entity, allowedFields: r.allowedFields ?? [] })))), + ), + forEach(/** for */ ref('groupRole'), /** in */ ref('staticGroupRoles'), [ + set(ref('groupsInToken'), getIdentityClaimExp(ref('groupRole.claim'), list([]))), + iff( + methodCall(ref('groupsInToken.contains'), ref('groupRole.entity')), + addAllowedFieldsIfElse('groupRole.allowedFields', true), + ), + ]), + ]), + ), + ); + } + return staticRoleExpression; +}; + +const dynamicGroupRoleExpression = (roles: Array, fields: ReadonlyArray): Array => { + const ownerExpression = new Array(); + const dynamicGroupExpression = new Array(); + roles.forEach((role, idx) => { + const entityIsList = fieldIsList(fields, role.entity!); + if (role.strategy === 'owner') { + ownerExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set( + ref(`ownerEntity${idx}`), + methodCall(ref('util.defaultIfNull'), ref(`ctx.args.input.${role.entity!}`), entityIsList ? list([]) : nul()), + ), + set(ref(`ownerClaim${idx}`), getOwnerClaim(role.claim!)), + set(ref(`ownerAllowedFields${idx}`), raw(JSON.stringify(role.allowedFields))), + ...(entityIsList + ? [ + forEach(ref('allowedOwner'), ref(`ownerEntity${idx}`), [ + iff(equals(ref('allowedOwner'), ref(`ownerClaim${idx}`)), addAllowedFieldsIfElse(`ownerAllowedFields${idx}`, true)), + ]), + ] + : [iff(equals(ref(`ownerClaim${idx}`), ref(`ownerEntity${idx}`)), addAllowedFieldsIfElse(`ownerAllowedFields${idx}`))]), + iff( + and([isNullOrEmpty(ref(`ownerEntity${idx}`)), not(methodCall(ref('ctx.args.input.containsKey'), str(role.entity!)))]), + compoundExpression([ + qref( + methodCall( + ref('ctx.args.input.put'), + str(role.entity!), + entityIsList ? list([ref(`ownerClaim${idx}`)]) : ref(`ownerClaim${idx}`), + ), + ), + addAllowedFieldsIfElse(`ownerAllowedFields${idx}`), + ]), + ), + ]), + ), + ); + } + if (role.strategy === 'groups') { + dynamicGroupExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set( + ref(`groupEntity${idx}`), + methodCall(ref('util.defaultIfNull'), ref(`ctx.args.input.${role.entity!}`), entityIsList ? list([]) : nul()), + ), + set(ref(`groupClaim${idx}`), getIdentityClaimExp(str(role.claim!), list([]))), + set(ref(`groupAllowedFields${idx}`), raw(JSON.stringify(role.allowedFields))), + forEach(ref('userGroup'), ref(`groupClaim${idx}`), [ + iff( + entityIsList + ? methodCall(ref(`groupEntity${idx}.contains`), ref('userGroup')) + : equals(ref(`groupEntity${idx}`), ref('userGroup')), + addAllowedFieldsIfElse(`groupAllowedFields${idx}`, true), + ), + ]), + ]), + ), + ); + } + }); + + return [...(ownerExpression.length > 0 ? ownerExpression : []), ...(dynamicGroupExpression.length > 0 ? dynamicGroupExpression : [])]; +}; + +/** + * Unauthorized if + * - auth conditions could not be met + * - there are fields conditions that could not be met + * @param providers + * @param roles + * @param fields + * @returns + */ +export const generateAuthExpressionForCreate = ( + providers: ConfiguredAuthProviders, + roles: Array, + fields: ReadonlyArray, +): string => { + const { + cogntoStaticRoles: cognitoStaticGroupRoles, + cognitoDynamicRoles, + oidcStaticRoles: oidcStaticGroupRoles, + oidcDynamicRoles, + apiKeyRoles, + iamRoles, + } = splitRoles(roles); + const totalAuthExpressions: Array = [ + setHasAuthExpression, + getInputFields(), + set(ref(IS_AUTHORIZED_FLAG), bool(false)), + set(ref(ALLOWED_FIELDS), list([])), + ]; + if (providers.hasApiKey) { + totalAuthExpressions.push(apiKeyExpression(apiKeyRoles)); + } + if (providers.hasIAM) { + totalAuthExpressions.push(iamExpression(iamRoles, providers.hasAdminUIEnabled, providers.adminUserPoolID)); + } + if (providers.hasUserPools) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(COGNITO_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(cognitoStaticGroupRoles), + ...dynamicGroupRoleExpression(cognitoDynamicRoles, fields), + ]), + ), + ); + } + if (providers.hasOIDC) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(OIDC_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(oidcStaticGroupRoles), + ...dynamicGroupRoleExpression(oidcDynamicRoles, fields), + ]), + ), + ); + } + totalAuthExpressions.push( + iff(and([not(ref(IS_AUTHORIZED_FLAG)), ref(`${ALLOWED_FIELDS}.isEmpty()`)]), ref('util.unauthorized()')), + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref(DENIED_FIELDS), methodCall(ref('util.list.copyAndRemoveAll'), ref('inputFields'), ref(ALLOWED_FIELDS))), + iff( + ref(`${DENIED_FIELDS}.size() > 0`), + methodCall(ref('util.error'), str(`Unauthorized on \${${DENIED_FIELDS}}`), str('Unauthorized')), + ), + ]), + ), + ); + return printBlock('Authorization Steps')(compoundExpression([...totalAuthExpressions, emptyPayload])); +}; diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.delete.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.delete.ts new file mode 100644 index 00000000000..c16ea3d9af6 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.delete.ts @@ -0,0 +1,189 @@ +import { FieldDefinitionNode } from 'graphql'; +import { + Expression, + printBlock, + compoundExpression, + bool, + equals, + iff, + raw, + ref, + set, + str, + methodCall, + or, + forEach, + list, + not, + nul, +} from 'graphql-mapping-template'; +import { emptyPayload, getIdentityClaimExp, getOwnerClaim, iamCheck, setHasAuthExpression } from './helpers'; +import { + ADMIN_ROLE, + API_KEY_AUTH_TYPE, + COGNITO_AUTH_TYPE, + ConfiguredAuthProviders, + fieldIsList, + IAM_AUTH_TYPE, + IS_AUTHORIZED_FLAG, + MANAGE_ROLE, + OIDC_AUTH_TYPE, + RoleDefinition, + splitRoles, +} from '../utils'; + +/** + * There is only one role for ApiKey we can use the first index + * @param roles + * @returns Expression | null + */ +const apiKeyExpression = (roles: Array) => { + const expression = new Array(); + if (roles.length === 0) { + return iff(equals(ref('util.authType()'), str(API_KEY_AUTH_TYPE)), ref('util.unauthorized()')); + } + if (roles.length > 0) { + expression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + } + return iff(equals(ref('util.authType()'), str(API_KEY_AUTH_TYPE)), compoundExpression(expression)); +}; +/** + * No need to combine allowed fields as the request can only be signed by one iam role + * @param roles + * @returns + */ +const iamExpression = (roles: Array, hasAdminUIEnabled: boolean = false, adminUserPoolID?: string) => { + const expression = new Array(); + // allow if using admin ui + if (hasAdminUIEnabled) { + expression.push( + iff( + or([ + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID}${ADMIN_ROLE}`)), + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID}${MANAGE_ROLE}`)), + ]), + raw('#return($util.toJson({})'), + ), + ); + } + if (roles.length > 0) { + for (let role of roles) { + expression.push(iamCheck(role.claim!, set(ref(IS_AUTHORIZED_FLAG), bool(true)))); + } + } else { + expression.push(ref('util.unauthorized()')); + } + return iff(equals(ref('util.authType()'), str(IAM_AUTH_TYPE)), compoundExpression(expression)); +}; + +const generateStaticRoleExpression = (roles: Array): Array => { + const staticRoleExpression: Array = new Array(); + const privateRoleIdx = roles.findIndex(r => r.strategy === 'private'); + if (privateRoleIdx > -1) { + staticRoleExpression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + roles.splice(privateRoleIdx, -1); + } + if (roles.length > 0) { + staticRoleExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref('staticGroupRoles'), raw(JSON.stringify(roles.map(r => ({ claim: r.claim, entity: r.entity }))))), + forEach(/** for */ ref('groupRole'), /** in */ ref('staticGroupRoles'), [ + set(ref('groupsInToken'), getIdentityClaimExp(ref('groupRole.claim'), list([]))), + iff( + methodCall(ref('groupsInToken.contains'), ref('groupRole.entity')), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), raw('#break')]), + ), + ]), + ]), + ), + ); + } + return staticRoleExpression; +}; + +const dynamicGroupRoleExpression = (roles: Array, fields: ReadonlyArray) => { + const ownerExpression = new Array(); + const dynamicGroupExpression = new Array(); + roles.forEach((role, idx) => { + const entityIsList = fieldIsList(fields, role.entity!); + if (role.strategy === 'owner') { + ownerExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref(`ownerEntity${idx}`), methodCall(ref('util.defaultIfNull'), ref(`ctx.result.${role.entity!}`), nul())), + set(ref(`ownerClaim${idx}`), getOwnerClaim(role.claim!)), + ...(entityIsList + ? [ + forEach(ref('allowedOwner'), ref(`ownerEntity${idx}`), [ + iff(equals(ref('allowedOwner'), ref(`ownerClaim${idx}`)), set(ref(IS_AUTHORIZED_FLAG), bool(true))), + ]), + ] + : [iff(equals(ref(`ownerEntity${idx}`), ref(`ownerClaim${idx}`)), set(ref(IS_AUTHORIZED_FLAG), bool(true)))]), + ]), + ), + ); + } + if (role.strategy === 'groups') { + dynamicGroupExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set( + ref(`groupEntity${idx}`), + methodCall(ref('util.defaultIfNull'), ref(`ctx.result.${role.entity}`), entityIsList ? list([]) : nul()), + ), + set(ref(`groupClaim${idx}`), getIdentityClaimExp(str(role.claim!), list([]))), + forEach(ref('userGroup'), ref(`groupClaim${idx}`), [ + iff( + entityIsList + ? methodCall(ref(`groupEntity${idx}.contains`), ref('userGroup')) + : equals(ref(`groupEntity${idx}`), ref('userGroup')), + set(ref(IS_AUTHORIZED_FLAG), bool(true)), + ), + ]), + ]), + ), + ); + } + }); + return [...(ownerExpression.length > 0 ? ownerExpression : []), ...(dynamicGroupExpression.length > 0 ? dynamicGroupExpression : [])]; +}; + +export const geneateAuthExpressionForDelete = ( + providers: ConfiguredAuthProviders, + roles: Array, + fields: ReadonlyArray, +) => { + const { cogntoStaticRoles, cognitoDynamicRoles, oidcStaticRoles, oidcDynamicRoles, apiKeyRoles, iamRoles } = splitRoles(roles); + const totalAuthExpressions: Array = [setHasAuthExpression, set(ref(IS_AUTHORIZED_FLAG), bool(false))]; + if (providers.hasApiKey) { + totalAuthExpressions.push(apiKeyExpression(apiKeyRoles)); + } + if (providers.hasIAM) { + totalAuthExpressions.push(iamExpression(iamRoles, providers.hasAdminUIEnabled, providers.adminUserPoolID)); + } + if (providers.hasUserPools) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(COGNITO_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(cogntoStaticRoles), + ...dynamicGroupRoleExpression(cognitoDynamicRoles, fields), + ]), + ), + ); + } + if (providers.hasOIDC) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(OIDC_AUTH_TYPE)), + compoundExpression([...generateStaticRoleExpression(oidcStaticRoles), ...dynamicGroupRoleExpression(oidcDynamicRoles, fields)]), + ), + ); + } + totalAuthExpressions.push(iff(not(ref(IS_AUTHORIZED_FLAG)), ref('util.unauthorized()'))); + return printBlock('Authorization Steps')(compoundExpression([...totalAuthExpressions, emptyPayload])); +}; diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.update.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.update.ts new file mode 100644 index 00000000000..7d4f20ec30d --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/mutation.update.ts @@ -0,0 +1,318 @@ +import { FieldDefinitionNode } from 'graphql'; +import { + compoundExpression, + iff, + raw, + set, + ref, + forEach, + bool, + Expression, + not, + obj, + list, + qref, + equals, + str, + and, + methodCall, + toJson, + printBlock, + ifElse, + nul, + or, +} from 'graphql-mapping-template'; +import { + ADMIN_ROLE, + API_KEY_AUTH_TYPE, + COGNITO_AUTH_TYPE, + ConfiguredAuthProviders, + IAM_AUTH_TYPE, + MANAGE_ROLE, + OIDC_AUTH_TYPE, + RoleDefinition, + splitRoles, + fieldIsList, + IS_AUTHORIZED_FLAG, + ALLOWED_FIELDS, + NULL_ALLOWED_FIELDS, + DENIED_FIELDS, +} from '../utils'; +import { getIdentityClaimExp, responseCheckForErrors, getOwnerClaim, getInputFields, setHasAuthExpression, iamCheck } from './helpers'; + +/** + * There is only one role for ApiKey we can use the first index + * @param roles + * @returns Expression | null + */ +const apiKeyExpression = (roles: Array) => { + const expression = new Array(); + if (roles.length === 0) { + return iff(equals(ref('util.authType()'), str(API_KEY_AUTH_TYPE)), ref('util.unauthorized()')); + } + if (roles[0].allowedFields!.length > 0 || roles[0].nullAllowedFields!.length > 0) { + expression.push( + set(ref(`${ALLOWED_FIELDS}`), raw(JSON.stringify(roles[0].allowedFields))), + set(ref(`${NULL_ALLOWED_FIELDS}`), raw(JSON.stringify(roles[0].nullAllowedFields))), + ); + } else { + expression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + } + return iff(equals(ref('util.authType()'), str(API_KEY_AUTH_TYPE)), compoundExpression(expression)); +}; + +const iamExpression = (roles: Array, hasAdminUIEnabled: boolean = false, adminUserPoolID?: string) => { + const expression = new Array(); + // allow if using admin ui + if (hasAdminUIEnabled) { + expression.push( + iff( + or([ + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID}${ADMIN_ROLE}`)), + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID}${MANAGE_ROLE}`)), + ]), + raw('#return($util.toJson({})'), + ), + ); + } + if (roles.length > 0) { + for (let role of roles) { + if (role.allowedFields!.length > 0 || role.nullAllowedFields!.length > 0) { + expression.push( + iamCheck( + role.claim!, + compoundExpression([ + set(ref(`${ALLOWED_FIELDS}`), raw(JSON.stringify(role.allowedFields))), + set(ref(`${NULL_ALLOWED_FIELDS}`), raw(JSON.stringify(role.nullAllowedFields))), + ]), + ), + ); + } else { + expression.push(iamCheck(role.claim!, set(ref(IS_AUTHORIZED_FLAG), bool(true)))); + } + } + } else { + expression.push(ref('util.unauthorized()')); + } + return iff(equals(ref('util.authType()'), str(IAM_AUTH_TYPE)), compoundExpression(expression)); +}; + +const generateStaticRoleExpression = (roles: Array) => { + const staticRoleExpression: Array = new Array(); + const privateRoleIdx = roles.findIndex(r => r.strategy === 'private'); + if (privateRoleIdx > -1) { + const privateRole = roles[privateRoleIdx]; + if (privateRole.allowedFields!.length > 0 || privateRole.nullAllowedFields!.length > 0) { + staticRoleExpression.push( + qref(methodCall(ref(`${ALLOWED_FIELDS}.addAll`), raw(JSON.stringify(privateRole.allowedFields)))), + qref(methodCall(ref(`${NULL_ALLOWED_FIELDS}.addAll`), raw(JSON.stringify(privateRole.nullAllowedFields)))), + ); + } else { + staticRoleExpression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + } + roles.splice(privateRoleIdx, 1); + } + if (roles.length > 0) { + staticRoleExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set( + ref('staticGroupRoles'), + raw( + JSON.stringify( + roles.map(r => ({ + claim: r.claim, + entity: r.entity, + allowedFields: r.allowedFields, + nullAllowedFields: r.nullAllowedFields, + })), + ), + ), + ), + forEach(/** for */ ref('groupRole'), /** in */ ref('staticGroupRoles'), [ + set(ref('groupsInToken'), getIdentityClaimExp(ref('groupRole.claim'), list([]))), + iff( + methodCall(ref('groupsInToken.contains'), ref('groupRole.entity')), + compoundExpression([ + // if we find that it's not fully allowed on update (update/delete) we add the field conditions + // otherwise we set to true and break + ifElse( + or([not(ref(`groupRole.allowedFields.isEmpty()`)), not(ref('groupRole.nullAllowedFields.isEmpty()'))]), + compoundExpression([ + qref(methodCall(ref(`${ALLOWED_FIELDS}.addAll`), ref('groupRole.allowedFields'))), + qref(methodCall(ref(`${NULL_ALLOWED_FIELDS}.addAll`), ref('groupRole.nullAllowedFields'))), + ]), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), raw('#break')]), + ), + ]), + ), + ]), + ]), + ), + ); + } + return staticRoleExpression; +}; +const dynamicGroupRoleExpression = (roles: Array, fields: ReadonlyArray): Array => { + const ownerExpression = new Array(); + const dynamicGroupExpression = new Array(); + roles.forEach((role, idx) => { + const entityIsList = fieldIsList(fields, role.entity!); + if (role.strategy === 'owner') { + ownerExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set( + ref(`ownerEntity${idx}`), + methodCall(ref('util.defaultIfNull'), ref(`ctx.result.${role.entity!}`), entityIsList ? list([]) : nul()), + ), + set(ref(`ownerClaim${idx}`), getOwnerClaim(role.claim!)), + set(ref(`ownerAllowedFields${idx}`), raw(JSON.stringify(role.allowedFields))), + set(ref(`ownerNullAllowedFields${idx}`), raw(JSON.stringify(role.nullAllowedFields))), + ...(entityIsList + ? [ + forEach(ref('allowedOwner'), ref(`ownerEntity${idx}`), [ + iff( + equals(ref('allowedOwner'), ref(`ownerClaim${idx}`)), + ifElse( + or([not(ref(`ownerAllowedFields${idx}.isEmpty()`)), not(ref(`ownerNullAllowedFields${idx}.isEmpty()`))]), + compoundExpression([ + qref(methodCall(ref(`${ALLOWED_FIELDS}.addAll`), ref(`ownerAllowedFields${idx}`))), + qref(methodCall(ref(`${NULL_ALLOWED_FIELDS}.addAll`), ref(`ownerNullAllowedFields${idx}`))), + ]), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), raw('#break')]), + ), + ), + ]), + ] + : [ + iff( + equals(ref(`ownerEntity${idx}`), ref(`ownerClaim${idx}`)), + ifElse( + or([not(ref(`ownerAllowedFields${idx}.isEmpty()`)), not(ref(`ownerNullAllowedFields${idx}.isEmpty()`))]), + compoundExpression([ + qref(methodCall(ref(`${ALLOWED_FIELDS}.addAll`), ref(`ownerAllowedFields${idx}`))), + qref(methodCall(ref(`${NULL_ALLOWED_FIELDS}.addAll`), ref(`ownerNullAllowedFields${idx}`))), + ]), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true))]), + ), + ), + ]), + ]), + ), + ); + } + if (role.strategy === 'groups') { + dynamicGroupExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set( + ref(`groupEntity${idx}`), + methodCall(ref('util.defaultIfNull'), ref(`ctx.result.${role.entity}`), entityIsList ? list([]) : nul()), + ), + set(ref(`groupClaim${idx}`), getIdentityClaimExp(str(role.claim!), list([]))), + set(ref(`groupAllowedFields${idx}`), raw(JSON.stringify(role.allowedFields))), + set(ref(`groupNullAllowedFields${idx}`), raw(JSON.stringify(role.nullAllowedFields))), + forEach(ref('userGroup'), ref(`groupClaim${idx}`), [ + iff( + entityIsList + ? methodCall(ref(`groupEntity${idx}.contains`), ref('userGroup')) + : equals(ref(`groupEntity${idx}`), ref('userGroup')), + ifElse( + or([not(ref(`groupAllowedFields${idx}.isEmpty()`)), not(ref(`groupNullAllowedFields${idx}.isEmpty()`))]), + compoundExpression([ + qref(methodCall(ref(`${ALLOWED_FIELDS}.addAll`), ref('groupRole.allowedFields'))), + qref(methodCall(ref(`${NULL_ALLOWED_FIELDS}.addAll`), ref('groupRole.nullAllowedFields'))), + ]), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), raw('#break')]), + ), + ), + ]), + ]), + ), + ); + } + }); + return [...(ownerExpression.length > 0 ? ownerExpression : []), ...(dynamicGroupExpression.length > 0 ? dynamicGroupExpression : [])]; +}; + +/** + * For update we need to check for allowed fields and null allowed fields + * unauthorized if + * - none of the roles have been met and there are no field conditions + * - role is partially allowed but the field conditions have not been met + * @param providers + * @param roles + * @param fields + * @returns + */ +export const generateAuthExpressionForUpdate = ( + providers: ConfiguredAuthProviders, + roles: Array, + fields: ReadonlyArray, +) => { + const { cogntoStaticRoles, cognitoDynamicRoles, oidcStaticRoles, oidcDynamicRoles, apiKeyRoles, iamRoles } = splitRoles(roles); + const totalAuthExpressions: Array = [ + setHasAuthExpression, + responseCheckForErrors(), + getInputFields(), + set(ref(IS_AUTHORIZED_FLAG), bool(false)), + set(ref(`${ALLOWED_FIELDS}`), list([])), + set(ref(`${NULL_ALLOWED_FIELDS}`), list([])), + set(ref(`${DENIED_FIELDS}`), obj({})), + ]; + if (providers.hasApiKey) { + totalAuthExpressions.push(apiKeyExpression(apiKeyRoles)); + } + if (providers.hasIAM) { + totalAuthExpressions.push(iamExpression(iamRoles, providers.hasAdminUIEnabled, providers.adminUserPoolID)); + } + if (providers.hasUserPools) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(COGNITO_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(cogntoStaticRoles), + ...dynamicGroupRoleExpression(cognitoDynamicRoles, fields), + ]), + ), + ); + } + if (providers.hasOIDC) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(OIDC_AUTH_TYPE)), + compoundExpression([...generateStaticRoleExpression(oidcStaticRoles), ...dynamicGroupRoleExpression(oidcDynamicRoles, fields)]), + ), + ); + } + totalAuthExpressions.push( + iff( + and([not(ref(IS_AUTHORIZED_FLAG)), ref(`${ALLOWED_FIELDS}.isEmpty()`), ref(`${NULL_ALLOWED_FIELDS}.isEmpty()`)]), + ref('util.unauthorized()'), + ), + // if not authorized we check the field conditions + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + forEach(ref('entry'), ref('util.map.copyAndRetainAllKeys($ctx.args.input, $inputFields).entrySet()'), [ + iff( + and([methodCall(ref('util.isNull'), ref('entry.value')), not(ref(`${NULL_ALLOWED_FIELDS}.contains($entry.key)`))]), + qref(methodCall(ref(`${DENIED_FIELDS}.put`), ref('entry.key'), str(''))), + ), + ]), + forEach(ref('deniedField'), ref(`util.list.copyAndRemoveAll($inputFields, \$${ALLOWED_FIELDS})`), [ + qref(methodCall(ref(`${DENIED_FIELDS}.put`), ref('deniedField'), str(''))), + ]), + ]), + ), + iff( + ref(`${DENIED_FIELDS}.keySet().size() > 0`), + methodCall(ref('util.error'), str(`Unauthorized on \${${DENIED_FIELDS}.keySet()}`), str('Unauthorized')), + ), + ); + return printBlock('Authorization Steps')(compoundExpression([...totalAuthExpressions, toJson(obj({}))])); +}; diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/query.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/query.ts new file mode 100644 index 00000000000..3b186108bd5 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/query.ts @@ -0,0 +1,347 @@ +import { FieldDefinitionNode } from 'graphql'; +import { + compoundExpression, + Expression, + obj, + printBlock, + and, + equals, + iff, + methodCall, + not, + ref, + str, + bool, + forEach, + list, + qref, + raw, + set, + ifElse, + nul, +} from 'graphql-mapping-template'; +import { getIdentityClaimExp, getOwnerClaim, apiKeyExpression, iamExpression, emptyPayload, setHasAuthExpression } from './helpers'; +import { + COGNITO_AUTH_TYPE, + OIDC_AUTH_TYPE, + RoleDefinition, + splitRoles, + ConfiguredAuthProviders, + IS_AUTHORIZED_FLAG, + fieldIsList, + RelationalPrimaryMapConfig, +} from '../utils'; +import { NONE_VALUE } from 'graphql-transformer-common'; + +const generateStaticRoleExpression = (roles: Array): Array => { + const staticRoleExpression: Array = []; + let privateRoleIdx = roles.findIndex(r => r.strategy === 'private'); + if (privateRoleIdx > -1) { + staticRoleExpression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + roles.splice(privateRoleIdx, 1); + } + if (roles.length > 0) { + staticRoleExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref('staticGroupRoles'), raw(JSON.stringify(roles.map(r => ({ claim: r.claim, entity: r.entity }))))), + forEach(ref('groupRole'), ref('staticGroupRoles'), [ + set(ref('groupsInToken'), getIdentityClaimExp(ref('groupRole.claim'), list([]))), + iff( + methodCall(ref('groupsInToken.contains'), ref('groupRole.entity')), + compoundExpression([set(ref(IS_AUTHORIZED_FLAG), bool(true)), raw(`#break`)]), + ), + ]), + ]), + ), + ); + } + return staticRoleExpression; +}; + +const generateAuthOnRelationalModelQueryExpression = ( + roles: Array, + primaryFieldMap: RelationalPrimaryMapConfig, +): Array => { + const modelQueryExpression = new Array(); + const primaryRoles = roles.filter(r => primaryFieldMap.has(r.entity)); + if (primaryRoles.length > 0) { + primaryRoles.forEach((role, idx) => { + const { claim, field } = primaryFieldMap.get(role.entity); + modelQueryExpression.push( + set( + ref(`primaryRole${idx}`), + role.strategy === 'owner' ? getOwnerClaim(role.claim!) : getIdentityClaimExp(str(role.claim!), str(NONE_VALUE)), + ), + ifElse( + not(ref(`util.isNull($ctx.${claim}.${field})`)), + compoundExpression([ + iff( + equals(ref(`ctx.${claim}.${field}`), ref(`primaryRole${idx}`)), + compoundExpression([ + set(ref(IS_AUTHORIZED_FLAG), bool(true)), + qref(methodCall(ref('ctx.stash.put'), str('authFilter'), nul())), + ]), + ), + ]), + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + qref(methodCall(ref(`ctx.${claim}.put`), str(field), ref(`primaryRole${idx}`))), + set(ref('primaryFieldAuth'), bool(true)), + ]), + ), + ), + ); + }); + return [ + iff( + and([not(ref(IS_AUTHORIZED_FLAG)), methodCall(ref('util.isNull'), ref('ctx.stash.authFilter'))]), + compoundExpression(modelQueryExpression), + ), + ]; + } + return modelQueryExpression; +}; + +/** + * In the event that an owner/group field is the same as a primary field we can validate against the args if provided + * if the field is not in the args we include it in the KeyConditionExpression which is formed as a part of the query + */ +const generateAuthOnModelQueryExpression = ( + roles: Array, + primaryFields: Array, + isIndexQuery = false, +): Array => { + const modelQueryExpression = new Array(); + const primaryRoles = roles.filter(r => primaryFields.includes(r.entity)); + if (primaryRoles.length > 0) { + if (isIndexQuery) { + for (let role of primaryRoles) { + const claimExpression = + role.strategy === 'owner' ? getOwnerClaim(role.claim!) : getIdentityClaimExp(str(role.claim!), str(NONE_VALUE)); + modelQueryExpression.push( + ifElse( + not(ref(`util.isNull($ctx.args.${role.entity})`)), + iff( + equals(ref(`ctx.args.${role.entity}`), claimExpression), + compoundExpression([ + set(ref(IS_AUTHORIZED_FLAG), bool(true)), + qref(methodCall(ref('ctx.stash.put'), str('authFilter'), nul())), + ]), + ), + qref(methodCall(ref('primaryFieldMap.put'), str(role.entity), claimExpression)), + ), + ); + } + modelQueryExpression.push( + iff( + and([ + not(ref(IS_AUTHORIZED_FLAG)), + methodCall(ref('util.isNull'), ref('ctx.stash.authFilter')), + not(ref('primaryFieldMap.isEmpty()')), + ]), + compoundExpression([ + forEach(ref('entry'), ref('primaryFieldMap.entrySet()'), [ + qref(methodCall(ref('ctx.args.put'), ref('entry.key'), ref('entry.value'))), + ]), + ]), + ), + ); + } else { + for (let role of primaryRoles) { + const claimExpression = + role.strategy === 'owner' ? getOwnerClaim(role.claim!) : getIdentityClaimExp(str(role.claim!), str(NONE_VALUE)); + modelQueryExpression.push( + ifElse( + not(ref(`util.isNull($ctx.args.${role.entity})`)), + compoundExpression([iff(equals(ref(`ctx.args.${role.entity}`), claimExpression), set(ref(IS_AUTHORIZED_FLAG), bool(true)))]), + qref(methodCall(ref('primaryFieldMap.put'), str(role.entity), claimExpression)), + ), + ); + } + modelQueryExpression.push( + iff( + and([ + not(ref(IS_AUTHORIZED_FLAG)), + methodCall(ref('util.isNull'), ref('ctx.stash.authFilter')), + not(ref('primaryFieldMap.isEmpty()')), + ]), + compoundExpression([ + set(ref('modelQueryExpression'), ref('ctx.stash.modelQueryExpression')), + forEach(ref('entry'), ref('primaryFieldMap.entrySet()'), [ + set(ref('modelQueryExpression.expression'), str('${modelQueryExpression.expression} AND #${entry.key} = :${entry.value}')), + qref(ref('modelQueryExpression.expressionNames.put("#${entry.key}", $entry.key)')), + qref(ref('modelQueryExpression.expressionValues.put(":${entry.value}", $util.dynamodb.toDynamoDB($entry.value))')), + ]), + qref(methodCall(ref('ctx.stash.put'), str('modelQueryExpression'), ref('modelQueryExpression'))), + ]), + ), + ); + } + return modelQueryExpression; + } + return []; +}; + +const generateAuthFilter = (roles: Array, fields: ReadonlyArray): Array => { + const authFilter = new Array(); + const groupMap = new Map>(); + const groupContainsExpression = new Array(); + if (!(roles.length > 0)) return []; + /** + * if ownerField is string + * ownerField: { eq: "cognito:owner" } + * if ownerField is a List + * ownerField: { contains: "cognito:owner"} + * + * if groupsField is a string + * groupsField: { in: "cognito:groups" } + * if groupsField is a list + * we create contains experession for each cognito group + * */ + for (let role of roles) { + const entityIsList = fieldIsList(fields, role.entity); + if (role.strategy === 'owner') { + const ownerCondition = entityIsList ? 'contains' : 'eq'; + authFilter.push(obj({ [role.entity]: obj({ [ownerCondition]: getOwnerClaim(role.claim!) }) })); + } + if (role.strategy === 'groups') { + // for fields where the group is a list and the token is a list we must add every group in the claim + if (entityIsList) { + if (groupMap.has(role.claim!)) { + groupMap.get(role.claim).push(role.entity); + } else { + groupMap.set(role.claim!, [role.entity]); + } + } else { + authFilter.push(obj({ [role.entity]: obj({ in: getIdentityClaimExp(str(role.claim!), list([str(NONE_VALUE)])) }) })); + } + } + } + for (let [groupClaim, fieldList] of groupMap) { + groupContainsExpression.push( + forEach( + ref('group'), + ref(`util.defaultIfNull($ctx.identity.claims.get("${groupClaim}"), ["${NONE_VALUE}"])`), + fieldList.map(field => qref(methodCall(ref('authFilter.add'), raw(`{"${field}": { "contains": $group }}`)))), + ), + ); + } + return [ + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref('authFilter'), list(authFilter)), + ...(groupContainsExpression.length > 0 ? groupContainsExpression : []), + qref(methodCall(ref('ctx.stash.put'), str('authFilter'), raw('{ "or": $authFilter }'))), + ]), + ), + ]; +}; + +export const generateAuthExpressionForQueries = ( + providers: ConfiguredAuthProviders, + roles: Array, + fields: ReadonlyArray, + primaryFields: Array, + isIndexQuery = false, +): string => { + const { cogntoStaticRoles, cognitoDynamicRoles, oidcStaticRoles, oidcDynamicRoles, apiKeyRoles, iamRoles } = splitRoles(roles); + const getNonPrimaryFieldRoles = (roles: RoleDefinition[]) => roles.filter(roles => !primaryFields.includes(roles.entity)); + const totalAuthExpressions: Array = [ + setHasAuthExpression, + set(ref(IS_AUTHORIZED_FLAG), bool(false)), + set(ref('primaryFieldMap'), obj({})), + ]; + if (providers.hasApiKey) { + totalAuthExpressions.push(apiKeyExpression(apiKeyRoles)); + } + if (providers.hasIAM) { + totalAuthExpressions.push(iamExpression(iamRoles, providers.hasAdminUIEnabled, providers.adminUserPoolID)); + } + if (providers.hasUserPools) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(COGNITO_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(cogntoStaticRoles), + ...generateAuthFilter(getNonPrimaryFieldRoles(cognitoDynamicRoles), fields), + ...generateAuthOnModelQueryExpression(cognitoDynamicRoles, primaryFields, isIndexQuery), + ]), + ), + ); + } + if (providers.hasOIDC) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(OIDC_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(oidcStaticRoles), + ...generateAuthFilter(getNonPrimaryFieldRoles(oidcDynamicRoles), fields), + ...generateAuthOnModelQueryExpression(oidcDynamicRoles, primaryFields, isIndexQuery), + ]), + ), + ); + } + totalAuthExpressions.push( + iff( + and([not(ref(IS_AUTHORIZED_FLAG)), methodCall(ref('util.isNull'), ref('ctx.stash.authFilter')), ref('primaryFieldMap.isEmpty()')]), + ref('util.unauthorized()'), + ), + ); + return printBlock('Authorization Steps')(compoundExpression([...totalAuthExpressions, emptyPayload])); +}; + +export const generateAuthExpressionForRelationQuery = ( + providers: ConfiguredAuthProviders, + roles: Array, + fields: ReadonlyArray, + primaryFieldMap: RelationalPrimaryMapConfig, +) => { + const { cogntoStaticRoles, cognitoDynamicRoles, oidcStaticRoles, oidcDynamicRoles, apiKeyRoles, iamRoles } = splitRoles(roles); + const getNonPrimaryFieldRoles = (roles: RoleDefinition[]) => roles.filter(roles => !primaryFieldMap.has(roles.entity)); + const totalAuthExpressions: Array = [ + setHasAuthExpression, + set(ref(IS_AUTHORIZED_FLAG), bool(false)), + set(ref('primaryFieldAuth'), bool(false)), + ]; + if (providers.hasApiKey) { + totalAuthExpressions.push(apiKeyExpression(apiKeyRoles)); + } + if (providers.hasIAM) { + totalAuthExpressions.push(iamExpression(iamRoles, providers.hasAdminUIEnabled, providers.adminUserPoolID)); + } + if (providers.hasUserPools) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(COGNITO_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(cogntoStaticRoles), + ...generateAuthFilter(getNonPrimaryFieldRoles(cognitoDynamicRoles), fields), + ...generateAuthOnRelationalModelQueryExpression(cognitoDynamicRoles, primaryFieldMap), + ]), + ), + ); + } + if (providers.hasOIDC) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(OIDC_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(oidcStaticRoles), + ...generateAuthFilter(getNonPrimaryFieldRoles(oidcDynamicRoles), fields), + ...generateAuthOnRelationalModelQueryExpression(oidcDynamicRoles, primaryFieldMap), + ]), + ), + ); + } + totalAuthExpressions.push( + iff( + and([not(ref(IS_AUTHORIZED_FLAG)), methodCall(ref('util.isNull'), ref('ctx.stash.authFilter')), not(ref('primaryFieldAuth'))]), + ref('util.unauthorized()'), + ), + ); + return printBlock('Authorization Steps')(compoundExpression([...totalAuthExpressions, emptyPayload])); +}; diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/search.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/search.ts new file mode 100644 index 00000000000..efcc06ef9ff --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/search.ts @@ -0,0 +1,271 @@ +import { FieldDefinitionNode } from 'graphql'; +import { + compoundExpression, + Expression, + obj, + printBlock, + and, + equals, + notEquals, + iff, + methodCall, + not, + ref, + str, + bool, + forEach, + list, + qref, + raw, + set, + ifElse, + or, +} from 'graphql-mapping-template'; +import { getIdentityClaimExp, getOwnerClaim, emptyPayload, setHasAuthExpression, iamCheck } from './helpers'; +import { + COGNITO_AUTH_TYPE, + OIDC_AUTH_TYPE, + RoleDefinition, + splitRoles, + ConfiguredAuthProviders, + IS_AUTHORIZED_FLAG, + fieldIsList, + ADMIN_ROLE, + API_KEY_AUTH_TYPE, + IAM_AUTH_TYPE, + MANAGE_ROLE, +} from '../utils'; +import { NONE_VALUE } from 'graphql-transformer-common'; + +const allowedAggFieldsList = 'allowedAggFields'; +const aggFieldsFilterMap = 'aggFieldsFilterMap'; +const totalFields = 'totalFields'; + +const apiKeyExpression = (roles: Array): Expression => { + const expression = Array(); + if (roles.length === 0) { + expression.push(ref('util.unauthorized()')); + } else if (roles[0].allowedFields) { + expression.push( + set(ref(IS_AUTHORIZED_FLAG), bool(true)), + qref(methodCall(ref(`${allowedAggFieldsList}.addAll`), raw(JSON.stringify(roles[0].allowedFields)))), + ); + } else { + expression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true)), set(ref(allowedAggFieldsList), ref(totalFields))); + } + return iff(equals(ref('util.authType()'), str(API_KEY_AUTH_TYPE)), compoundExpression(expression)); +}; + +const iamExpression = (roles: Array, adminuiEnabled: boolean = false, adminUserPoolID?: string) => { + const expression = new Array(); + // allow if using admin ui + if (adminuiEnabled) { + expression.push( + iff( + or([ + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID}${ADMIN_ROLE}`)), + methodCall(ref('ctx.identity.userArn.contains'), str(`${adminUserPoolID}${MANAGE_ROLE}`)), + ]), + raw('#return($util.toJson({})'), + ), + ); + } + if (roles.length === 0) { + expression.push(ref('util.unauthorized()')); + } else { + for (let role of roles) { + const exp: Expression[] = [set(ref(IS_AUTHORIZED_FLAG), bool(true))]; + if (role.allowedFields) { + exp.push(qref(methodCall(ref(`${allowedAggFieldsList}.addAll`), raw(JSON.stringify(role.allowedFields))))); + } else { + exp.push(set(ref(allowedAggFieldsList), ref(totalFields))); + } + expression.push(iff(not(ref(IS_AUTHORIZED_FLAG)), iamCheck(role.claim!, compoundExpression(exp)))); + } + } + return iff(equals(ref('util.authType()'), str(IAM_AUTH_TYPE)), compoundExpression(expression)); +}; + +const generateStaticRoleExpression = (roles: Array): Array => { + const staticRoleExpression: Array = []; + let privateRoleIdx = roles.findIndex(r => r.strategy === 'private'); + if (privateRoleIdx > -1) { + if (roles[privateRoleIdx].allowedFields) { + staticRoleExpression.push( + qref(methodCall(ref(`${allowedAggFieldsList}.addAll`), raw(JSON.stringify(roles[privateRoleIdx].allowedFields)))), + ); + } else { + staticRoleExpression.push(set(ref(allowedAggFieldsList), ref(totalFields))); + } + staticRoleExpression.push(set(ref(IS_AUTHORIZED_FLAG), bool(true))); + roles.splice(privateRoleIdx, 1); + } + if (roles.length > 0) { + staticRoleExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set( + ref('staticGroupRoles'), + raw( + JSON.stringify( + roles.map(r => ({ claim: r.claim, entity: r.entity, ...(r.allowedFields ? { allowedFields: r.allowedFields } : {}) })), + ), + ), + ), + forEach(ref('groupRole'), ref('staticGroupRoles'), [ + set(ref('groupsInToken'), getIdentityClaimExp(ref('groupRole.claim'), list([]))), + iff( + methodCall(ref('groupsInToken.contains'), ref('groupRole.entity')), + compoundExpression([ + set(ref(IS_AUTHORIZED_FLAG), bool(true)), + ifElse( + methodCall(ref('util.isNull'), ref('groupRole.allowedFields')), + compoundExpression([set(ref(allowedAggFieldsList), ref(totalFields)), raw(`#break`)]), + qref(methodCall(ref(`${allowedAggFieldsList}.addAll`), ref('groupRole.allowedFields'))), + ), + ]), + ), + ]), + ]), + ), + ); + } + return staticRoleExpression; +}; + +const generateAuthFilter = ( + roles: Array, + fields: ReadonlyArray, + allowedAggFields: Array, +): Array => { + const filterExpression = new Array(); + const authFilter = new Array(); + const aggFieldMap: Record> = {}; + if (!(roles.length > 0)) return []; + /** + * for opensearch + * we create a terms_set where the field (role.entity) has to match at least element in the terms + * if the field is a list it will look for a subset of elements in the list which should exist in the terms list + * */ + roles.forEach((role, idx) => { + // for the terms search it's best to go by keyword for non list dynamic auth fields + const entityIsList = fieldIsList(fields, role.entity); + const roleKey = entityIsList ? role.entity : `${role.entity}.keyword`; + if (role.strategy === 'owner') { + filterExpression.push( + set( + ref(`owner${idx}`), + obj({ + terms_set: obj({ + [roleKey]: obj({ + terms: list([getOwnerClaim(role.claim!)]), + minimum_should_match_script: obj({ source: str('1') }), + }), + }), + }), + ), + ); + authFilter.push(ref(`owner${idx}`)); + if (role.allowedFields) { + role.allowedFields.forEach(field => { + if (!allowedAggFields.includes(field)) { + aggFieldMap[field] = [...(aggFieldMap[field] ?? []), `$owner${idx}`]; + } + }); + } + } else if (role.strategy === 'groups') { + filterExpression.push( + set( + ref(`group${idx}`), + obj({ + terms_set: obj({ + [roleKey]: obj({ + terms: getIdentityClaimExp(str(role.claim!), list([str(NONE_VALUE)])), + minimum_should_match_script: obj({ source: str('1') }), + }), + }), + }), + ), + ); + authFilter.push(ref(`group${idx}`)); + if (role.allowedFields) { + role.allowedFields.forEach(field => { + if (!allowedAggFields.includes(field)) { + aggFieldMap[field] = [...(aggFieldMap[field] ?? []), `$group${idx}`]; + } + }); + } + } + }); + filterExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + qref(methodCall(ref('ctx.stash.put'), str('authFilter'), obj({ bool: obj({ should: list(authFilter) }) }))), + ), + ); + if (Object.keys(aggFieldMap).length > 0) { + filterExpression.push( + iff( + notEquals(ref(`${allowedAggFieldsList}.size()`), ref(`${totalFields}.size()`)), + // regex is there so we can remove the quotes from the array values in VTL as they contain objects + // ex. "$owner0" to $owner0 + qref(methodCall(ref('ctx.stash.put'), str(aggFieldsFilterMap), raw(JSON.stringify(aggFieldMap).replace(/"\$(.*?)"/g, '$$$1')))), + ), + ); + } + return filterExpression; +}; + +/* +creates the auth expression for searchable +- handles object level search query +- creates field auth expression for aggregation query +*/ +export const generateAuthExpressionForSearchQueries = ( + providers: ConfiguredAuthProviders, + roles: Array, + fields: ReadonlyArray, + allowedAggFields: Array, +): string => { + const { cogntoStaticRoles, cognitoDynamicRoles, oidcStaticRoles, oidcDynamicRoles, apiKeyRoles, iamRoles } = splitRoles(roles); + const totalAuthExpressions: Array = [ + setHasAuthExpression, + set(ref(IS_AUTHORIZED_FLAG), bool(false)), + set(ref(totalFields), raw(JSON.stringify(fields.map(f => f.name.value)))), + set(ref(allowedAggFieldsList), raw(JSON.stringify(allowedAggFields))), + ]; + if (providers.hasApiKey) { + totalAuthExpressions.push(apiKeyExpression(apiKeyRoles)); + } + if (providers.hasIAM) { + totalAuthExpressions.push(iamExpression(iamRoles, providers.hasAdminUIEnabled, providers.adminUserPoolID)); + } + if (providers.hasUserPools) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(COGNITO_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(cogntoStaticRoles), + ...generateAuthFilter(cognitoDynamicRoles, fields, allowedAggFields), + ]), + ), + ); + } + if (providers.hasOIDC) { + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(OIDC_AUTH_TYPE)), + compoundExpression([ + ...generateStaticRoleExpression(oidcStaticRoles), + ...generateAuthFilter(oidcDynamicRoles, fields, allowedAggFields), + ]), + ), + ); + } + totalAuthExpressions.push( + qref(methodCall(ref('ctx.stash.put'), str(allowedAggFieldsList), ref(allowedAggFieldsList))), + iff(and([not(ref(IS_AUTHORIZED_FLAG)), methodCall(ref('util.isNull'), ref('ctx.stash.authFilter'))]), ref('util.unauthorized()')), + ); + return printBlock('Authorization Steps')(compoundExpression([...totalAuthExpressions, emptyPayload])); +}; diff --git a/packages/amplify-graphql-auth-transformer/src/resolvers/subscriptions.ts b/packages/amplify-graphql-auth-transformer/src/resolvers/subscriptions.ts new file mode 100644 index 00000000000..8fcc6ab086f --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/resolvers/subscriptions.ts @@ -0,0 +1,71 @@ +import { + bool, + compoundExpression, + equals, + Expression, + iff, + methodCall, + not, + ref, + set, + str, + nul, + printBlock, +} from 'graphql-mapping-template'; +import { COGNITO_AUTH_TYPE, ConfiguredAuthProviders, IS_AUTHORIZED_FLAG, OIDC_AUTH_TYPE, RoleDefinition, splitRoles } from '../utils'; +import { + generateStaticRoleExpression, + getOwnerClaim, + apiKeyExpression, + iamExpression, + emptyPayload, + setHasAuthExpression, +} from './helpers'; + +const dynamicRoleExpression = (roles: Array): Array => { + const ownerExpression = new Array(); + // we only check against owner rules which are not list fields + roles.forEach((role, idx) => { + if (role.strategy === 'owner') { + ownerExpression.push( + iff( + not(ref(IS_AUTHORIZED_FLAG)), + compoundExpression([ + set(ref(`ownerEntity${idx}`), methodCall(ref('util.defaultIfNull'), ref(`ctx.args.${role.entity!}`), nul())), + set(ref(`ownerClaim${idx}`), getOwnerClaim(role.claim!)), + iff(equals(ref(`ownerEntity${idx}`), ref(`ownerClaim${idx}`)), set(ref(IS_AUTHORIZED_FLAG), bool(true))), + ]), + ), + ); + } + }); + + return [...(ownerExpression.length > 0 ? ownerExpression : [])]; +}; + +export const generateAuthExpressionForSubscriptions = (providers: ConfiguredAuthProviders, roles: Array): string => { + const { cogntoStaticRoles, cognitoDynamicRoles, oidcStaticRoles, oidcDynamicRoles, iamRoles, apiKeyRoles } = splitRoles(roles); + const totalAuthExpressions: Array = [setHasAuthExpression, set(ref(IS_AUTHORIZED_FLAG), bool(false))]; + if (providers.hasApiKey) { + totalAuthExpressions.push(apiKeyExpression(apiKeyRoles)); + } + if (providers.hasIAM) { + totalAuthExpressions.push(iamExpression(iamRoles, providers.hasAdminUIEnabled, providers.adminUserPoolID)); + } + if (providers.hasUserPools) + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(COGNITO_AUTH_TYPE)), + compoundExpression([...generateStaticRoleExpression(cogntoStaticRoles), ...dynamicRoleExpression(cognitoDynamicRoles)]), + ), + ); + if (providers.hasOIDC) + totalAuthExpressions.push( + iff( + equals(ref('util.authType()'), str(OIDC_AUTH_TYPE)), + compoundExpression([...generateStaticRoleExpression(oidcStaticRoles), ...dynamicRoleExpression(oidcDynamicRoles)]), + ), + ); + totalAuthExpressions.push(iff(not(ref(IS_AUTHORIZED_FLAG)), ref('util.unauthorized()'))); + return printBlock('Authorization Steps')(compoundExpression([...totalAuthExpressions, emptyPayload])); +}; diff --git a/packages/amplify-graphql-auth-transformer/src/utils/constants.ts b/packages/amplify-graphql-auth-transformer/src/utils/constants.ts new file mode 100644 index 00000000000..85f62f075d4 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/utils/constants.ts @@ -0,0 +1,41 @@ +import { AuthProvider, ModelOperation } from './definitions'; +export const DEFAULT_OWNER_FIELD = 'owner'; +export const DEFAULT_GROUPS_FIELD = 'groups'; +export const DEFAULT_IDENTITY_CLAIM = 'username'; +export const DEFAULT_COGNITO_IDENTITY_CLAIM = 'cognito:username'; +export const DEFAULT_GROUP_CLAIM = 'cognito:groups'; +export const ON_CREATE_FIELD = 'onCreate'; +export const ON_UPDATE_FIELD = 'onUpdate'; +export const ON_DELETE_FIELD = 'onDelete'; +export const AUTH_NON_MODEL_TYPES = 'authNonModelTypes'; +export const MODEL_OPERATIONS: ModelOperation[] = ['create', 'read', 'update', 'delete']; +export const AUTH_PROVIDER_DIRECTIVE_MAP = new Map([ + ['apiKey', 'aws_api_key'], + ['iam', 'aws_iam'], + ['oidc', 'aws_oidc'], + ['userPools', 'aws_cognito_user_pools'], +]); +// values for $util.authType() https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference.html +export const COGNITO_AUTH_TYPE = 'User Pool Authorization'; +export const OIDC_AUTH_TYPE = 'Open ID Connect Authorization'; +export const IAM_AUTH_TYPE = 'IAM Authorization'; +export const API_KEY_AUTH_TYPE = 'API Key Authorization'; +// resolver refs +export const IS_AUTHORIZED_FLAG = 'isAuthorized'; +export const ALLOWED_FIELDS = 'allowedFields'; +export const NULL_ALLOWED_FIELDS = 'nullAllowedFields'; +export const DENIED_FIELDS = 'deniedFields'; +// Admin Roles +export const ADMIN_ROLE = '_Full-access/CognitoIdentityCredentials'; +export const MANAGE_ROLE = '_Manage-only/CognitoIdentityCredentials'; +// resolver +export const NONE_DS = 'NONE_DS'; +// relational directives +export const RELATIONAL_DIRECTIVES = ['hasOne', 'belongsTo', 'hasMany', 'manyToMany']; +// searchable directive +export const SEARCHABLE_AGGREGATE_TYPES = [ + 'SearchableAggregateResult', + 'SearchableAggregateScalarResult', + 'SearchableAggregateBucketResult', + 'SearchableAggregateBucketResultItem', +]; diff --git a/packages/amplify-graphql-auth-transformer/src/utils/definitions.ts b/packages/amplify-graphql-auth-transformer/src/utils/definitions.ts new file mode 100644 index 00000000000..abeefde7257 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/utils/definitions.ts @@ -0,0 +1,99 @@ +import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; +export type AuthStrategy = 'owner' | 'groups' | 'public' | 'private'; +export type AuthProvider = 'apiKey' | 'iam' | 'oidc' | 'userPools'; +export type ModelQuery = 'get' | 'list'; +export type ModelMutation = 'create' | 'update' | 'delete'; +export type ModelOperation = 'create' | 'update' | 'delete' | 'read'; + +export type RelationalPrimaryMapConfig = Map; +export interface SearchableConfig { + queries: { + search: string; + }; +} + +export interface RolesByProvider { + cogntoStaticRoles: Array; + cognitoDynamicRoles: Array; + oidcStaticRoles: Array; + oidcDynamicRoles: Array; + iamRoles: Array; + apiKeyRoles: Array; +} + +export interface AuthRule { + allow: AuthStrategy; + provider?: AuthProvider; + ownerField?: string; + identityClaim?: string; + groupsField?: string; + groupClaim?: string; + groups?: string[]; + operations?: ModelOperation[]; + // Used only for IAM provider to decide if an IAM policy needs to be generated. IAM auth with AdminUI does not need IAM policies + generateIAMPolicy?: boolean; +} + +export interface RoleDefinition { + provider: AuthProvider; + strategy: AuthStrategy; + static: boolean; + claim?: string; + entity?: string; + // specific to mutations + allowedFields?: Array; + nullAllowedFields?: Array; +} + +export interface AuthDirective { + rules: AuthRule[]; +} + +export interface ConfiguredAuthProviders { + default: AuthProvider; + onlyDefaultAuthProviderConfigured: boolean; + hasApiKey: boolean; + hasUserPools: boolean; + hasOIDC: boolean; + hasIAM: boolean; + hasAdminUIEnabled: boolean; + adminUserPoolID?: string; +} + +export interface AuthTransformerConfig { + addAwsIamAuthInOutputSchema: boolean; + authConfig?: AppSyncAuthConfiguration; + adminUserPoolID?: string; +} + +export const authDirectiveDefinition = ` + directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION + input AuthRule { + allow: AuthStrategy! + provider: AuthProvider + identityClaim: String + groupClaim: String + ownerField: String + groupsField: String + groups: [String] + operations: [ModelOperation] + } + enum AuthStrategy { + owner + groups + private + public + } + enum AuthProvider { + apiKey + iam + oidc + userPools + } + enum ModelOperation { + create + update + delete + read + } +`; diff --git a/packages/amplify-graphql-auth-transformer/src/utils/iam.ts b/packages/amplify-graphql-auth-transformer/src/utils/iam.ts new file mode 100644 index 00000000000..d0bf86b849e --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/utils/iam.ts @@ -0,0 +1,70 @@ +import * as cdk from '@aws-cdk/core'; +interface PolicyDocument { + [key: string]: any; +} + +export const createPolicyDocumentForManagedPolicy = (resources: Set) => { + const policyDocuments = new Array(); + let policyDocumentResources = new Array(); + let resourceSize = 0; + + // 6144 bytes is the maximum policy payload size, but there is structural overhead, hence the 6000 bytes + const MAX_BUILT_SIZE_BYTES = 6000; + // The overhead is the amount of static policy arn contents like region, accountid, etc. + // arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:apis/${apiId}/types/${typeName}/fields/${fieldName} + // 16 15 13 5 27 6 X+1 7 Y + // 89 + 11 extra = 100 + const RESOURCE_OVERHEAD = 100; + + const createPolicyDocument = (newPolicyDocumentResources: Array): PolicyDocument => { + return { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: ['appsync:GraphQL'], + Resource: newPolicyDocumentResources, + }, + ], + }; + }; + + for (const resource of resources) { + // We always have 2 parts, no need to check + const [typeName, fieldName] = resource.split('/'); + + if (fieldName !== 'null') { + policyDocumentResources.push( + cdk.Fn.sub('arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:apis/${apiId}/types/${typeName}/fields/${fieldName}', { + apiId: cdk.Fn.getAtt('GraphQLAPI', 'ApiId').toString(), + typeName, + fieldName, + }).toString(), + ); + resourceSize += RESOURCE_OVERHEAD + typeName.length + fieldName.length; + } else { + policyDocumentResources.push( + cdk.Fn.sub('arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:apis/${apiId}/types/${typeName}/*', { + apiId: cdk.Fn.getAtt('GraphQLAPI', 'ApiId').toString(), + typeName, + }).toString(), + ); + resourceSize += RESOURCE_OVERHEAD + typeName.length; + } + // + // Check size of resource and if needed create a new one and clear the resources and + // reset accumulated size + // + if (resourceSize > MAX_BUILT_SIZE_BYTES) { + const policyDocument = createPolicyDocument(policyDocumentResources.slice(0, policyDocumentResources.length - 1)); + policyDocuments.push(policyDocument); + // Remove all but the last item + policyDocumentResources = policyDocumentResources.slice(-1); + resourceSize = 0; + } + } + if (policyDocumentResources.length > 0) { + policyDocuments.push(createPolicyDocument(policyDocumentResources)); + } + return policyDocuments; +}; diff --git a/packages/amplify-graphql-auth-transformer/src/utils/index.ts b/packages/amplify-graphql-auth-transformer/src/utils/index.ts new file mode 100644 index 00000000000..e5e7760884a --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/utils/index.ts @@ -0,0 +1,105 @@ +import { AppSyncAuthMode } from '@aws-amplify/graphql-transformer-interfaces'; +import { TransformerContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; +import { Stack } from '@aws-cdk/core'; +import { ObjectTypeDefinitionNode } from 'graphql'; +import { AuthProvider, AuthRule, AuthTransformerConfig, ConfiguredAuthProviders, RoleDefinition, RolesByProvider } from './definitions'; + +export * from './constants'; +export * from './definitions'; +export * from './validations'; +export * from './schema'; +export * from './iam'; + +export const splitRoles = (roles: Array): RolesByProvider => { + return { + cogntoStaticRoles: roles.filter(r => r.static && r.provider === 'userPools'), + cognitoDynamicRoles: roles.filter(r => !r.static && r.provider === 'userPools'), + oidcStaticRoles: roles.filter(r => r.static && r.provider === 'oidc'), + oidcDynamicRoles: roles.filter(r => !r.static && r.provider === 'oidc'), + iamRoles: roles.filter(r => r.provider === 'iam'), + apiKeyRoles: roles.filter(r => r.provider === 'apiKey'), + }; +}; +/** + * Ensure the following defaults + * - provider + * - iam policy generation + */ +export const ensureAuthRuleDefaults = (rules: AuthRule[]) => { + // We assign the default provider if an override is not present make further handling easier. + for (const rule of rules) { + if (!rule.provider) { + switch (rule.allow) { + case 'owner': + case 'groups': + rule.provider = 'userPools'; + break; + case 'private': + rule.provider = 'userPools'; + break; + case 'public': + rule.provider = 'apiKey'; + break; + default: + throw new Error(`Need to specify an allow to assigned a provider: ${rule}`); + } + } + // by default we generate an IAM policy for every rule + if (rule.provider === 'iam' && !rule.generateIAMPolicy) { + rule.generateIAMPolicy = true; + } + } +}; + +/** + * gets stack name if the field is paired with function, predictions, or by itself + */ +export const getStackForField = ( + ctx: TransformerContextProvider, + obj: ObjectTypeDefinitionNode, + fieldName: string, + hasModelDirective: boolean, +): Stack => { + const fieldNode = obj.fields.find(f => f.name.value === fieldName); + const fieldDirectives = fieldNode.directives.map(d => d.name.value); + if (fieldDirectives.includes('function')) { + return ctx.stackManager.getStack('FunctionDirectiveStack'); + } else if (fieldDirectives.includes('predictions')) { + return ctx.stackManager.getStack('PredictionsDirectiveStack'); + } else if (hasModelDirective) { + return ctx.stackManager.getStack(obj.name.value); + } else { + return ctx.stackManager.rootStack; + } +}; + +export const getConfiguredAuthProviders = (config: AuthTransformerConfig): ConfiguredAuthProviders => { + const providers = [ + config.authConfig.defaultAuthentication.authenticationType, + ...config.authConfig.additionalAuthenticationProviders.map(p => p.authenticationType), + ]; + const getAuthProvider = (authType: AppSyncAuthMode): AuthProvider => { + switch (authType) { + case 'AMAZON_COGNITO_USER_POOLS': + return 'userPools'; + case 'API_KEY': + return 'apiKey'; + case 'AWS_IAM': + return 'iam'; + case 'OPENID_CONNECT': + return 'oidc'; + } + }; + const hasIAM = providers.some(p => p === 'AWS_IAM'); + const configuredProviders: ConfiguredAuthProviders = { + default: getAuthProvider(config.authConfig.defaultAuthentication.authenticationType), + onlyDefaultAuthProviderConfigured: config.authConfig.additionalAuthenticationProviders.length === 0, + hasAdminUIEnabled: hasIAM && config.addAwsIamAuthInOutputSchema, + adminUserPoolID: config.adminUserPoolID!, + hasApiKey: providers.some(p => p === 'API_KEY'), + hasUserPools: providers.some(p => p === 'AMAZON_COGNITO_USER_POOLS'), + hasOIDC: providers.some(p => p === 'OPENID_CONNECT'), + hasIAM, + }; + return configuredProviders; +}; diff --git a/packages/amplify-graphql-auth-transformer/src/utils/schema.ts b/packages/amplify-graphql-auth-transformer/src/utils/schema.ts new file mode 100644 index 00000000000..2ee502e3168 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/utils/schema.ts @@ -0,0 +1,337 @@ +import { ModelDirectiveConfiguration, SubscriptionLevel } from '@aws-amplify/graphql-model-transformer'; +import { DirectiveWrapper, InvalidDirectiveError, TransformerContractError } from '@aws-amplify/graphql-transformer-core'; +import { + QueryFieldType, + MutationFieldType, + TransformerTransformSchemaStepContextProvider, + TransformerContextProvider, +} from '@aws-amplify/graphql-transformer-interfaces'; +import { DynamoDbDataSource } from '@aws-cdk/aws-appsync'; +import { ObjectTypeDefinitionNode, FieldDefinitionNode, DirectiveNode, NamedTypeNode } from 'graphql'; +import { + blankObjectExtension, + extendFieldWithDirectives, + extensionWithDirectives, + graphqlName, + isListType, + makeInputValueDefinition, + makeNamedType, + ModelResourceIDs, + plurality, + toCamelCase, + toUpper, +} from 'graphql-transformer-common'; +import { RELATIONAL_DIRECTIVES } from './constants'; +import { RelationalPrimaryMapConfig, RoleDefinition, SearchableConfig } from './definitions'; + +export const collectFieldNames = (object: ObjectTypeDefinitionNode): Array => { + return object.fields!.map((field: FieldDefinitionNode) => field.name.value); +}; + +export const fieldIsList = (fields: ReadonlyArray, fieldName: string) => { + return fields.some(field => field.name.value === fieldName && isListType(field.type)); +}; + +export const getModelConfig = (directive: DirectiveNode, typeName: string, isDataStoreEnabled = false): ModelDirectiveConfiguration => { + const directiveWrapped: DirectiveWrapper = new DirectiveWrapper(directive); + const options = directiveWrapped.getArguments({ + queries: { + get: toCamelCase(['get', typeName]), + list: toCamelCase(['list', plurality(typeName, true)]), + ...(isDataStoreEnabled ? { sync: toCamelCase(['sync', plurality(typeName, true)]) } : undefined), + }, + mutations: { + create: toCamelCase(['create', typeName]), + update: toCamelCase(['update', typeName]), + delete: toCamelCase(['delete', typeName]), + }, + subscriptions: { + level: SubscriptionLevel.on, + onCreate: [toCamelCase(['onCreate', typeName])], + onDelete: [toCamelCase(['onDelete', typeName])], + onUpdate: [toCamelCase(['onUpdate', typeName])], + }, + timestamps: { + createdAt: 'createdAt', + updatedAt: 'updatedAt', + }, + }); + return options; +}; + +export const getSearchableConfig = (directive: DirectiveNode, typeName: string): SearchableConfig | null => { + const directiveWrapped: DirectiveWrapper = new DirectiveWrapper(directive); + const options = directiveWrapped.getArguments({ + queries: { + search: graphqlName(`search${plurality(toUpper(typeName), true)}`), + }, + }); + return options; +}; +/* + This handles the scenario where a @auth field is also included in the keyschema of a related @model + since a filter expression cannot contain partition key or sort key attributes. We need to run auth on the query expression + https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression + @hasMany + - we get the keyschema (default or provided index) and then check that against the fields provided in the argument + - we then create a map of this relation if the field is included in the directive then we use ctx.source.relatedField + otherwise we use ctx.args.relatedField + @hasOne, @belongsTo + - we check the key schema against the fields provided by the directive + - if they don't have the same length then we throw an error + - All of the fields specified are checked against the ctx.source.relatedField + since this isn't a many relational we don't need to get values from ctx.args + */ +export const getRelationalPrimaryMap = ( + ctx: TransformerContextProvider, + def: ObjectTypeDefinitionNode, + field: FieldDefinitionNode, + relatedModel: ObjectTypeDefinitionNode, +): RelationalPrimaryMapConfig => { + const relationalDirective = field.directives.find(dir => RELATIONAL_DIRECTIVES.includes(dir.name.value)); + const directiveWrapped: DirectiveWrapper = new DirectiveWrapper(relationalDirective); + const primaryFieldMap = new Map(); + if (relationalDirective.name.value === 'hasMany') { + const args = directiveWrapped.getArguments({ + indexName: undefined, + fields: undefined, + }); + // we only generate a primary map if a index name or field is specified + // if both are undefined then @hasMany will create a new gsi with a new readonly field + // we don't need a primary map since this readonly field is not a auth field + if (args.indexName || args.fields) { + // get related types keyschema + const fields = args.fields ? args.fields : [getTable(ctx, def).keySchema.find((att: any) => att.keyType === 'HASH').attributeName]; + const relatedTable = args.indexName + ? (getTable(ctx, relatedModel) + .globalSecondaryIndexes.find((gsi: any) => gsi.indexName === args.indexName) + .keySchema.map((att: any) => att.attributeName) as Array) + : (getTable(ctx, relatedModel).keySchema.map((att: any) => att.attributeName) as Array); + relatedTable.forEach((att, idx) => { + primaryFieldMap.set(att, { + claim: fields[idx] ? 'source' : 'args', + field: fields[idx] ?? att, + }); + }); + } + } // manyToMany doesn't need a primaryMap since it will create it's own gsis + // to the join table between related @models + else if (relationalDirective.name.value !== 'manyToMany') { + const args = directiveWrapped.getArguments({ + fields: [toCamelCase([def.name.value, field.name.value, 'id'])], + }); + // get related types keyschema + const relatedPrimaryFields = getTable(ctx, relatedModel).keySchema.map((att: any) => att.attributeName) as Array; + // the fields provided by the directive (implicit/explicit) need to match the total amount of fields used for the primary key in the related table + // otherwise the get request is incomplete + if (args.fields.length !== relatedPrimaryFields.length) { + throw new InvalidDirectiveError( + `Invalid @${relationalDirective.name.value} on ${def.name.value}:${field.name.value}. Provided fields do not match the size of primary key(s) for ${relatedModel.name.value}`, + ); + } + relatedPrimaryFields.forEach((field, idx) => { + primaryFieldMap.set(field, { + claim: 'source', + field: args.fields[idx], + }); + }); + } + return primaryFieldMap; +}; + +export const hasRelationalDirective = (field: FieldDefinitionNode): boolean => { + return field.directives && field.directives.some(dir => RELATIONAL_DIRECTIVES.includes(dir.name.value)); +}; + +export const getTable = (ctx: TransformerContextProvider, def: ObjectTypeDefinitionNode): any => { + try { + const dbSource = ctx.dataSources.get(def) as DynamoDbDataSource; + const tableName = ModelResourceIDs.ModelTableResourceID(def.name.value); + return dbSource.ds.stack.node.findChild(tableName) as any; + } catch (err) { + throw new TransformerContractError(`Could not load primary fields of @model: ${def.name.value}`); + } +}; + +export const extendTypeWithDirectives = ( + ctx: TransformerTransformSchemaStepContextProvider, + typeName: string, + directives: Array, +): void => { + let objectTypeExtension = blankObjectExtension(typeName); + objectTypeExtension = extensionWithDirectives(objectTypeExtension, directives); + ctx.output.addObjectExtension(objectTypeExtension); +}; + +export const addDirectivesToField = ( + ctx: TransformerTransformSchemaStepContextProvider, + typeName: string, + fieldName: string, + directives: Array, +) => { + const type = ctx.output.getType(typeName) as ObjectTypeDefinitionNode; + if (type) { + const field = type.fields?.find(f => f.name.value === fieldName); + if (field) { + const newFields = [...type.fields!.filter(f => f.name.value !== field.name.value), extendFieldWithDirectives(field, directives)]; + + const newType = { + ...type, + fields: newFields, + }; + + ctx.output.putType(newType); + } + } +}; + +export const addSubscriptionArguments = ( + ctx: TransformerTransformSchemaStepContextProvider, + operationName: string, + subscriptionRoles: Array, +) => { + let subscription = ctx.output.getSubscription()!; + let createField: FieldDefinitionNode = subscription!.fields!.find(field => field.name.value === operationName) as FieldDefinitionNode; + const subcriptionArgumentList = subscriptionRoles.map(role => { + return makeInputValueDefinition(role.entity!, makeNamedType('String')); + }); + createField = { + ...createField, + arguments: subcriptionArgumentList, + }; + subscription = { + ...subscription, + fields: subscription!.fields!.map(field => (field.name.value === operationName ? createField : field)), + }; + ctx.output.putType(subscription); +}; + +export const addDirectivesToOperation = ( + ctx: TransformerTransformSchemaStepContextProvider, + typeName: string, + operationName: string, + directives: Array, +) => { + // add directives to the given operation + addDirectivesToField(ctx, typeName, operationName, directives); + + // add the directives to the result type of the operation + const type = ctx.output.getType(typeName) as ObjectTypeDefinitionNode; + if (type) { + const field = type.fields!.find(f => f.name.value === operationName); + + if (field) { + const returnFieldType = field.type as NamedTypeNode; + + if (returnFieldType.name) { + const returnTypeName = returnFieldType.name.value; + + extendTypeWithDirectives(ctx, returnTypeName, directives); + } + } + } +}; + +export const getQueryFieldNames = ( + modelDirectiveConfig: ModelDirectiveConfiguration, +): Set<{ fieldName: string; typeName: string; type: QueryFieldType }> => { + const fields: Set<{ fieldName: string; typeName: string; type: QueryFieldType }> = new Set(); + if (modelDirectiveConfig?.queries?.get) { + fields.add({ + typeName: 'Query', + fieldName: modelDirectiveConfig.queries.get, + type: QueryFieldType.GET, + }); + } + + if (modelDirectiveConfig?.queries?.list) { + fields.add({ + typeName: 'Query', + fieldName: modelDirectiveConfig.queries.list, + type: QueryFieldType.LIST, + }); + } + + if (modelDirectiveConfig?.queries?.sync) { + fields.add({ + typeName: 'Query', + fieldName: modelDirectiveConfig.queries.sync, + type: QueryFieldType.SYNC, + }); + } + return fields; +}; + +export const getMutationFieldNames = ( + modelDirectiveConfig: ModelDirectiveConfiguration, +): Set<{ fieldName: string; typeName: string; type: MutationFieldType }> => { + // Todo: get fields names from the directives + const getMutationType = (type: string): MutationFieldType => { + switch (type) { + case 'create': + return MutationFieldType.CREATE; + case 'update': + return MutationFieldType.UPDATE; + case 'delete': + return MutationFieldType.DELETE; + default: + throw new Error('Unknown mutation type'); + } + }; + + const fieldNames: Set<{ fieldName: string; typeName: string; type: MutationFieldType }> = new Set(); + for (let [mutationType, mutationName] of Object.entries(modelDirectiveConfig?.mutations || {})) { + if (mutationName) { + fieldNames.add({ + typeName: 'Mutation', + fieldName: mutationName, + type: getMutationType(mutationType), + }); + } + } + + return fieldNames; +}; + +export const getSubscriptionFieldNames = ( + modelDirectiveConfig: ModelDirectiveConfiguration, +): Set<{ + fieldName: string; + typeName: string; +}> => { + const fields: Set<{ + fieldName: string; + typeName: string; + }> = new Set(); + + if (modelDirectiveConfig?.subscriptions?.level === SubscriptionLevel.on) { + if (modelDirectiveConfig?.subscriptions?.onCreate && modelDirectiveConfig.mutations?.create) { + for (const fieldName of modelDirectiveConfig.subscriptions.onCreate) { + fields.add({ + typeName: 'Subscription', + fieldName: fieldName, + }); + } + } + + if (modelDirectiveConfig?.subscriptions?.onUpdate && modelDirectiveConfig.mutations?.update) { + for (const fieldName of modelDirectiveConfig.subscriptions.onUpdate) { + fields.add({ + typeName: 'Subscription', + fieldName: fieldName, + }); + } + } + + if (modelDirectiveConfig?.subscriptions?.onDelete && modelDirectiveConfig.mutations?.delete) { + for (const fieldName of modelDirectiveConfig.subscriptions.onDelete) { + fields.add({ + typeName: 'Subscription', + fieldName: fieldName, + }); + } + } + } + + return fields; +}; diff --git a/packages/amplify-graphql-auth-transformer/src/utils/validations.ts b/packages/amplify-graphql-auth-transformer/src/utils/validations.ts new file mode 100644 index 00000000000..d68646b90e9 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/utils/validations.ts @@ -0,0 +1,123 @@ +import { InvalidDirectiveError } from '@aws-amplify/graphql-transformer-core'; +import { AuthRule, ConfiguredAuthProviders } from './definitions'; + +export const validateRuleAuthStrategy = (rule: AuthRule, configuredAuthProviders: ConfiguredAuthProviders) => { + // + // Groups + // + if (rule.allow === 'groups' && rule.provider !== 'userPools' && rule.provider !== 'oidc') { + throw new InvalidDirectiveError( + `@auth directive with 'groups' strategy only supports 'userPools' and 'oidc' providers, but found '${rule.provider}' assigned.`, + ); + } + if (rule.allow === 'groups' && !rule.groups && !rule.groupsField) { + throw new InvalidDirectiveError(`@auth directive with 'groups' should have a defined groups list or a groupsField.`); + } + + // + // Owner + // + if (rule.allow === 'owner') { + if (rule.provider !== null && rule.provider !== 'userPools' && rule.provider !== 'oidc') { + throw new InvalidDirectiveError( + `@auth directive with 'owner' strategy only supports 'userPools' (default) and 'oidc' providers, but \ +found '${rule.provider}' assigned.`, + ); + } + } + + // + // Public + // + if (rule.allow === 'public') { + if (rule.provider !== null && rule.provider !== 'apiKey' && rule.provider !== 'iam') { + throw new InvalidDirectiveError( + `@auth directive with 'public' strategy only supports 'apiKey' (default) and 'iam' providers, but \ +found '${rule.provider}' assigned.`, + ); + } + } + + // + // Private + // + if (rule.allow === 'private') { + if (rule.provider !== null && rule.provider !== 'userPools' && rule.provider !== 'iam') { + throw new InvalidDirectiveError( + `@auth directive with 'private' strategy only supports 'userPools' (default) and 'iam' providers, but \ +found '${rule.provider}' assigned.`, + ); + } + } + + // + // Validate provider values against project configuration. + // + if (rule.provider === 'apiKey' && configuredAuthProviders.hasApiKey === false) { + throw new InvalidDirectiveError( + `@auth directive with 'apiKey' provider found, but the project has no API Key authentication provider configured.`, + ); + } else if (rule.provider === 'oidc' && configuredAuthProviders.hasOIDC === false) { + throw new InvalidDirectiveError( + `@auth directive with 'oidc' provider found, but the project has no OPENID_CONNECT authentication provider configured.`, + ); + } else if (rule.provider === 'userPools' && configuredAuthProviders.hasUserPools === false) { + throw new InvalidDirectiveError( + `@auth directive with 'userPools' provider found, but the project has no Cognito User Pools authentication provider configured.`, + ); + } else if (rule.provider === 'iam' && configuredAuthProviders.hasIAM === false) { + throw new InvalidDirectiveError( + `@auth directive with 'iam' provider found, but the project has no IAM authentication provider configured.`, + ); + } +}; + +export const validateRules = (rules: AuthRule[], configuredAuthProviders: ConfiguredAuthProviders) => { + for (const rule of rules) { + validateRuleAuthStrategy(rule, configuredAuthProviders); + commonRuleValidation(rule); + } +}; + +export const validateFieldRules = ( + rules: AuthRule[], + isParentTypeBuiltinType: boolean, + parentHasModelDirective: boolean, + authProviderConfig: ConfiguredAuthProviders, +) => { + for (const rule of rules) { + validateRuleAuthStrategy(rule, authProviderConfig); + + if (isParentTypeBuiltinType && rule.operations && rule.operations.length > 0) { + throw new InvalidDirectiveError( + `@auth rules on fields within Query, Mutation, Subscription cannot specify 'operations' argument as these rules \ +are already on an operation already.`, + ); + } + + if (!parentHasModelDirective && rule.operations && rule.operations.length > 0) { + throw new InvalidDirectiveError( + `@auth rules on fields within types that does not have @model directive cannot specify 'operations' argument as there are \ +operations will be generated by the CLI.`, + ); + } + + commonRuleValidation(rule); + } +}; + +// commmon rule validation between obj and field +export const commonRuleValidation = (rule: AuthRule) => { + const { identityClaim, allow, groups, groupsField, groupClaim } = rule; + if (allow === 'groups' && identityClaim) { + throw new InvalidDirectiveError(` + @auth identityClaim can only be used for 'allow: owner'`); + } + if (allow === 'owner' && groupClaim) { + throw new InvalidDirectiveError(` + @auth groupClaim can only be used 'allow: groups'`); + } + if (groupsField && groups) { + throw new InvalidDirectiveError('This rule has groupsField and groups, please use one or the other'); + } +}; diff --git a/packages/amplify-graphql-auth-transformer/tsconfig.json b/packages/amplify-graphql-auth-transformer/tsconfig.json new file mode 100644 index 00000000000..e00f8011a38 --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "strict": false, // TODO enable + "rootDir": "src", + "outDir": "lib" + }, + "references": [ + {"path": "../amplify-graphql-transformer-interfaces"}, + {"path": "../amplify-graphql-transformer-core"}, + {"path": "../amplify-graphql-model-transformer"}, + {"path": "../graphql-mapping-template"}, + {"path": "../graphql-transformer-common"} + ] +} diff --git a/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap b/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap index 3aacb6dbcde..0c8ac4a025e 100644 --- a/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap +++ b/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap @@ -120,7 +120,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } diff --git a/packages/amplify-graphql-function-transformer/src/__tests__/__snapshots__/amplify-graphql-function-transformer.test.ts.snap b/packages/amplify-graphql-function-transformer/src/__tests__/__snapshots__/amplify-graphql-function-transformer.test.ts.snap index cd54de62ac8..894f4794050 100644 --- a/packages/amplify-graphql-function-transformer/src/__tests__/__snapshots__/amplify-graphql-function-transformer.test.ts.snap +++ b/packages/amplify-graphql-function-transformer/src/__tests__/__snapshots__/amplify-graphql-function-transformer.test.ts.snap @@ -7,8 +7,8 @@ Object { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Invoke\\", \\"payload\\": { - \\"typeName\\": $ctx.stash.get(\\"typeName\\"), - \\"fieldName\\": $ctx.stash.get(\\"fieldName\\"), + \\"typeName\\": $util.toJson($ctx.stash.get(\\"typeName\\")), + \\"fieldName\\": $util.toJson($ctx.stash.get(\\"fieldName\\")), \\"arguments\\": $util.toJson($ctx.arguments), \\"identity\\": $util.toJson($ctx.identity), \\"source\\": $util.toJson($ctx.source), @@ -23,11 +23,6 @@ Object { #end $util.toJson($ctx.result) ## [End] Handle error or return result. **", - "Query.echo.req.vtl": "## [Start] Stash resolver specific context.. ** -$util.qr($ctx.stash.put(\\"typeName\\", \\"Query\\")) -$util.qr($ctx.stash.put(\\"fieldName\\", \\"echo\\")) -{} -## [End] Stash resolver specific context.. **", "Query.echo.res.vtl": "$util.toJson($ctx.prev.result)", } `; diff --git a/packages/amplify-graphql-function-transformer/src/__tests__/amplify-graphql-function-transformer.test.ts b/packages/amplify-graphql-function-transformer/src/__tests__/amplify-graphql-function-transformer.test.ts index 7f0ee96fc67..4f3dc104065 100644 --- a/packages/amplify-graphql-function-transformer/src/__tests__/amplify-graphql-function-transformer.test.ts +++ b/packages/amplify-graphql-function-transformer/src/__tests__/amplify-graphql-function-transformer.test.ts @@ -116,9 +116,7 @@ test('it generates the expected resources', () => { PipelineConfig: { Functions: [{ 'Fn::GetAtt': [anything(), 'FunctionId'] }], }, - RequestMappingTemplateS3Location: { - 'Fn::Join': ['', ['s3://', { Ref: anything() }, '/', { Ref: anything() }, '/pipelineFunctions/Query.echo.req.vtl']], - }, + RequestMappingTemplate: anything(), ResponseMappingTemplateS3Location: { 'Fn::Join': ['', ['s3://', { Ref: anything() }, '/', { Ref: anything() }, '/pipelineFunctions/Query.echo.res.vtl']], }, diff --git a/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts b/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts index c6a193ddd58..16afe63d1d2 100644 --- a/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts +++ b/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts @@ -1,8 +1,15 @@ -import { DirectiveWrapper, MappingTemplate, TransformerPluginBase } from '@aws-amplify/graphql-transformer-core'; +import { + DirectiveWrapper, + IAM_AUTH_ROLE_PARAMETER, + IAM_UNAUTH_ROLE_PARAMETER, + MappingTemplate, + TransformerPluginBase, +} from '@aws-amplify/graphql-transformer-core'; import { TransformerContextProvider, TransformerSchemaVisitStepContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; import * as lambda from '@aws-cdk/aws-lambda'; +import { AuthorizationType } from '@aws-cdk/aws-appsync'; import * as cdk from '@aws-cdk/core'; -import { obj, str, ref, printBlock, compoundExpression, qref, raw, iff } from 'graphql-mapping-template'; +import { obj, str, ref, printBlock, compoundExpression, qref, raw, iff, Expression } from 'graphql-mapping-template'; import { FunctionResourceIDs, ResolverResourceIDs, ResourceConstants } from 'graphql-transformer-common'; import { DirectiveNode, ObjectTypeDefinitionNode, InterfaceTypeDefinitionNode, FieldDefinitionNode } from 'graphql'; @@ -92,8 +99,8 @@ export class FunctionTransformer extends TransformerPluginBase { version: str('2018-05-29'), operation: str('Invoke'), payload: obj({ - typeName: ref('ctx.stash.get("typeName")'), - fieldName: ref('ctx.stash.get("fieldName")'), + typeName: ref('util.toJson($ctx.stash.get("typeName"))'), + fieldName: ref('util.toJson($ctx.stash.get("fieldName"))'), arguments: ref('util.toJson($ctx.arguments)'), identity: ref('util.toJson($ctx.identity)'), source: ref('util.toJson($ctx.source)'), @@ -124,20 +131,37 @@ export class FunctionTransformer extends TransformerPluginBase { const resolverId = ResolverResourceIDs.ResolverResourceID(config.resolverTypeName, config.resolverFieldName); let resolver = createdResources.get(resolverId); + const requestTemplate: Array = [ + qref(`$ctx.stash.put("typeName", "${config.resolverTypeName}")`), + qref(`$ctx.stash.put("fieldName", "${config.resolverFieldName}")`), + ]; + const authModes = [context.authConfig.defaultAuthentication, ...(context.authConfig.additionalAuthenticationProviders || [])].map( + mode => mode?.authenticationType, + ); + if (authModes.includes(AuthorizationType.IAM)) { + const authRoleParameter = (context.stackManager.getParameter(IAM_AUTH_ROLE_PARAMETER) as cdk.CfnParameter).valueAsString; + const unauthRoleParameter = (context.stackManager.getParameter(IAM_UNAUTH_ROLE_PARAMETER) as cdk.CfnParameter).valueAsString; + requestTemplate.push( + qref( + `$ctx.stash.put("authRole", "arn:aws:sts::${ + cdk.Stack.of(context.stackManager.rootStack).account + }:assumed-role/${authRoleParameter}/CognitoIdentityCredentials")`, + ), + qref( + `$ctx.stash.put("unauthRole", "arn:aws:sts::${ + cdk.Stack.of(context.stackManager.rootStack).account + }:assumed-role/${unauthRoleParameter}/CognitoIdentityCredentials")`, + ), + ); + } + requestTemplate.push(obj({})); + if (resolver === undefined) { + // TODO: update function to use resolver manager resolver = context.api.host.addResolver( config.resolverTypeName, config.resolverFieldName, - MappingTemplate.s3MappingTemplateFromString( - printBlock('Stash resolver specific context.')( - compoundExpression([ - qref(`$ctx.stash.put("typeName", "${config.resolverTypeName}")`), - qref(`$ctx.stash.put("fieldName", "${config.resolverFieldName}")`), - obj({}), - ]), - ), - `${config.resolverTypeName}.${config.resolverFieldName}.req.vtl`, - ), + MappingTemplate.inlineTemplateFromString(printBlock('Stash resolver specific context.')(compoundExpression(requestTemplate))), MappingTemplate.s3MappingTemplateFromString( '$util.toJson($ctx.prev.result)', `${config.resolverTypeName}.${config.resolverFieldName}.res.vtl`, @@ -146,7 +170,6 @@ export class FunctionTransformer extends TransformerPluginBase { [], stack, ); - createdResources.set(resolverId, resolver); } @@ -159,7 +182,7 @@ export class FunctionTransformer extends TransformerPluginBase { function lambdaArnResource(env: cdk.CfnParameter, name: string, region?: string): string { const substitutions: { [key: string]: string } = {}; if (name.includes('${env}')) { - substitutions.env = (env as unknown) as string; + substitutions.env = env as unknown as string; } return cdk.Fn.conditionIf( ResourceConstants.CONDITIONS.HasEnvironmentParameter, diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-index-transformer.test.ts.snap b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-index-transformer.test.ts.snap index 094ac57e866..fc6402fe39c 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-index-transformer.test.ts.snap +++ b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-index-transformer.test.ts.snap @@ -13,7 +13,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -41,25 +47,6 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -124,14 +111,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -201,13 +195,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -217,7 +212,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -373,35 +374,67 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", "Query.listByEmailKindDate.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -520,12 +553,38 @@ $util.toJson($GetRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listByEmailKindDate.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listTests.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -535,8 +594,20 @@ $util.toJson($ctx.result)", #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -560,13 +631,13 @@ $util.toJson($ctx.result)", #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.testsByCategory.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -639,12 +710,77 @@ $util.toJson($ListRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByCategory.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -661,7 +797,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -689,25 +831,6 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -772,14 +895,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -849,13 +979,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -865,7 +996,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1021,35 +1158,67 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", "Query.listByEmailKindDate.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -1168,12 +1337,38 @@ $util.toJson($GetRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listByEmailKindDate.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listTests.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -1183,8 +1378,20 @@ $util.toJson($ctx.result)", #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -1208,13 +1415,58 @@ $util.toJson($ctx.result)", #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -1231,7 +1483,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1259,25 +1517,6 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -1342,14 +1581,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1419,13 +1665,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -1435,7 +1682,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1591,35 +1844,67 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", "Query.listByEmailKindDate.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -1738,12 +2023,38 @@ $util.toJson($GetRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listByEmailKindDate.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listTests.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -1753,8 +2064,20 @@ $util.toJson($ctx.result)", #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -1778,13 +2101,13 @@ $util.toJson($ctx.result)", #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.testsByCategory.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -1857,7 +2180,27 @@ $util.toJson($ListRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByCategory.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -1895,36 +2238,82 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByEmail.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; exports[`GSI composite sort keys are wrapped in conditional to check presence in mutation 1`] = ` "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -2136,7 +2525,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2149,25 +2544,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -2232,14 +2608,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2307,13 +2690,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2323,7 +2707,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2464,14 +2854,21 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email), \\"createdAt\\": $util.dynamodb.toDynamoDB($ctx.args.createdAt) @@ -2481,25 +2878,50 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) -#else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) +#else + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", "Query.listByEmailKindDate.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -2618,13 +3040,39 @@ $util.toJson($GetRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listByEmailKindDate.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.email) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'email'.\\", \\"InvalidArgumentsError\\") #end @@ -2695,8 +3143,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -2720,13 +3180,13 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.testsByCategory.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -2799,7 +3259,27 @@ $util.toJson($ListRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByCategory.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -2837,12 +3317,77 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByEmail.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -2859,7 +3404,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.addContentToCategory.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.addContentToCategory.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.addContentToCategory.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2887,25 +3438,6 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end {}", "Mutation.addContentToCategory.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -2970,13 +3502,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"ContentCategory\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.addContentToCategory.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.addContentToCategory.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createBlog.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -2988,26 +3521,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createBlog.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createBlog.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3072,13 +3592,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Blog\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createBlog.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createBlog.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createCall.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3090,7 +3611,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createCall.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createCall.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createCall.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3103,25 +3630,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createCall.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3186,13 +3694,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Call\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createCall.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createCall.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createItem.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3204,7 +3713,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createItem.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createItem.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3224,25 +3739,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { $util.qr($ctx.args.input.put(\\"status#createdAt\\",\\"\${mergedValues.status}#\${mergedValues.createdAt}\\")) {}", "Mutation.createItem.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3307,13 +3803,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Item\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createItem.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createItem.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createPerson.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3325,7 +3822,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createPerson.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createPerson.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createPerson.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3344,7 +3847,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { #end $util.qr($ctx.args.input.put(\\"firstName#lastName\\",\\"\${mergedValues.firstName}#\${mergedValues.lastName}\\")) {}", - "Mutation.createPerson.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createPerson.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3371,7 +3874,7 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) $util.qr($ctx.args.input.put(\\"age#birthDate\\",\\"\${mergedValues.age}#\${mergedValues.birthDate}\\")) #end {}", - "Mutation.createPerson.postAuth.3.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createPerson.preAuth.3.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3399,25 +3902,6 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end {}", "Mutation.createPerson.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3482,13 +3966,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Person\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPerson.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPerson.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createReview.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3500,7 +3985,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createReview.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createReview.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createReview.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3512,7 +4003,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { ## [End] Set the primary key. ** {}", - "Mutation.createReview.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createReview.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3540,25 +4031,6 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end {}", "Mutation.createReview.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3623,13 +4095,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Review\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createReview.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createReview.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3641,7 +4114,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3654,25 +4133,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3737,13 +4197,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTodo.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3755,26 +4216,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTodo.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTodo.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3839,13 +4287,20 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Todo\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTodo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTodo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteBlog.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -3903,14 +4358,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteBlog.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteBlog.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteCall.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteCall.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteCall.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3978,14 +4440,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteCall.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteCall.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteContentFromCategory.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteContentFromCategory.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteContentFromCategory.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4055,14 +4524,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteContentFromCategory.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteContentFromCategory.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteItem.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteItem.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4131,14 +4607,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) $util.qr($DeleteRequest.put(\\"_version\\", $util.defaultIfNull($ctx.args.input[\\"_version\\"], \\"0\\"))) $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteItem.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteItem.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deletePerson.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deletePerson.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deletePerson.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4149,7 +4632,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { })) ## [End] Set the primary key. ** {}", - "Mutation.deletePerson.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.deletePerson.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4162,7 +4645,7 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end $util.qr($ctx.args.input.put(\\"age#birthDate\\",\\"\${mergedValues.age}#\${mergedValues.birthDate}\\")) {}", - "Mutation.deletePerson.postAuth.3.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.deletePerson.preAuth.3.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4232,14 +4715,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePerson.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePerson.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteReview.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteReview.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteReview.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4250,7 +4740,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { })) ## [End] Set the primary key. ** {}", - "Mutation.deleteReview.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.deleteReview.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4320,14 +4810,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteReview.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteReview.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4395,13 +4892,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteTodo.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -4459,14 +4963,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTodo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTodo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.testDelete.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.testDelete.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.testDelete.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4534,13 +5045,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.testDelete.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.testDelete.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateBlog.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -4550,6 +5062,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateBlog.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -4679,13 +5197,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateBlog.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateBlog.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateCall.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -4695,7 +5214,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateCall.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateCall.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateCall.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -4836,13 +5361,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateCall.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateCall.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateItem.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -4852,7 +5378,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateItem.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateItem.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -5001,13 +5533,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateItem.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateItem.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePerson.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5017,7 +5550,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updatePerson.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updatePerson.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updatePerson.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -5036,7 +5575,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { #end $util.qr($ctx.args.input.put(\\"firstName#lastName\\",\\"\${mergedValues.firstName}#\${mergedValues.lastName}\\")) {}", - "Mutation.updatePerson.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updatePerson.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -5063,7 +5602,7 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) $util.qr($ctx.args.input.put(\\"age#birthDate\\",\\"\${mergedValues.age}#\${mergedValues.birthDate}\\")) #end {}", - "Mutation.updatePerson.postAuth.3.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updatePerson.preAuth.3.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -5219,13 +5758,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePerson.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePerson.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateReview.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5235,7 +5775,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateReview.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateReview.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateReview.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -5247,7 +5793,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { ## [End] Set the primary key. ** {}", - "Mutation.updateReview.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateReview.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -5403,13 +5949,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateReview.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateReview.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5419,7 +5966,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -5560,13 +6113,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTodo.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5576,6 +6130,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateTodo.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -5705,13 +6265,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTodo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTodo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.byCreatedAt.req.vtl": "## [Start] Set query expression for key ** #if( !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"sortDirection is not supported for List operations without a Sort key defined.\\", \\"InvalidArgumentsError\\") @@ -5744,35 +6305,92 @@ $util.toJson($UpdateItem) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.byCreatedAt.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Query.getBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getBlog.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getBlog.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getBlog.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getCall.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.getCall.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getCall.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"receiverId\\": $util.dynamodb.toDynamoDB($ctx.args.receiverId), \\"senderId\\": $util.dynamodb.toDynamoDB($ctx.args.senderId) @@ -5782,26 +6400,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getCall.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getCall.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getCall.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.getItem.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getItem.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"orderId\\": $util.dynamodb.toDynamoDB($ctx.args.orderId), \\"status#createdAt\\": $util.dynamodb.toDynamoDB(\\"\${ctx.args.status}#\${ctx.args.createdAt}\\") @@ -5811,26 +6460,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getItem.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getItem.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getItem.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", - "Query.getPerson.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] Get Response template. **", + "Query.getPerson.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getPerson.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id), \\"firstName#lastName\\": $util.dynamodb.toDynamoDB(\\"\${ctx.args.firstName}#\${ctx.args.lastName}\\") @@ -5840,25 +6520,50 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getPerson.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getPerson.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getPerson.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", "Query.getPersonByNameByDate.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -5977,13 +6682,39 @@ $util.toJson($GetRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.getPersonByNameByDate.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", - "Query.getReview.postAuth.1.req.vtl": "## [Start] Set the primary key. ** + "Query.getReview.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getReview.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"owner\\": $util.dynamodb.toDynamoDB($ctx.args.owner), \\"serviceId\\": $util.dynamodb.toDynamoDB($ctx.args.serviceId) @@ -5993,26 +6724,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getReview.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getReview.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getReview.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email), \\"createdAt\\": $util.dynamodb.toDynamoDB($ctx.args.createdAt) @@ -6022,47 +6784,103 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTodo.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTodo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTodo.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", "Query.itemsByCreatedAt.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -6135,7 +6953,27 @@ $util.toJson($GetRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.itemsByCreatedAt.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -6213,12 +7051,38 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.itemsByStatus.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Query.listBlogs.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listBlogs.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6228,8 +7092,20 @@ $util.toJson($ctx.result)", #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6253,13 +7129,13 @@ $util.toJson($ctx.result)", #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listBlogs.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listBlogs.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.listByEmailKindDate.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -6378,13 +7254,39 @@ $util.toJson($ListRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listByEmailKindDate.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", - "Query.listCalls.postAuth.1.req.vtl": "## [Start] Set query expression for key ** + "Query.listCalls.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listCalls.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.receiverId) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'receiverId'.\\", \\"InvalidArgumentsError\\") #end @@ -6455,8 +7357,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6480,13 +7394,13 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listCalls.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listCalls.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.listContentByCategory.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -6622,13 +7536,39 @@ $util.toJson($ListRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listContentByCategory.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", - "Query.listItems.postAuth.1.req.vtl": "## [Start] Set query expression for key ** + "Query.listItems.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listItems.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.orderId) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'orderId'.\\", \\"InvalidArgumentsError\\") #end @@ -6745,8 +7685,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6770,14 +7722,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listItems.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listItems.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.listPeople.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] ResponseTemplate. **", + "Query.listPeople.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listPeople.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.id) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'id'.\\", \\"InvalidArgumentsError\\") #end @@ -6894,8 +7852,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6919,14 +7889,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listPeople.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listPeople.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.listReviews.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] ResponseTemplate. **", + "Query.listReviews.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listReviews.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.owner) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'owner'.\\", \\"InvalidArgumentsError\\") #end @@ -6997,8 +7973,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -7022,13 +8010,13 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listReviews.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listReviews.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.listReviewsById.req.vtl": "## [Start] Set query expression for key ** #if( !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"sortDirection is not supported for List operations without a Sort key defined.\\", \\"InvalidArgumentsError\\") @@ -7061,7 +8049,27 @@ $util.toJson($ListRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listReviewsById.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -7139,7 +8147,27 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listReviewsByService.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -7263,13 +8291,39 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listReviewsByStatus.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.email) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'email'.\\", \\"InvalidArgumentsError\\") #end @@ -7340,8 +8394,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -7365,14 +8431,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.syncItems.postAuth.1.req.vtl": "## [Start] Set map initialization for @key ** +## [End] ResponseTemplate. **", + "Query.syncItems.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.syncItems.preAuth.1.req.vtl": "## [Start] Set map initialization for @key ** #set( $index = \\"\\" ) #set( $scan = true ) #set( $filterMap = {} ) @@ -7505,7 +8577,7 @@ null #end ## [End] Set query expression for @key ** ", - "Query.syncItems.postAuth.2.req.vtl": "## [Start] Set map initialization for @key ** + "Query.syncItems.preAuth.2.req.vtl": "## [Start] Set map initialization for @key ** #set( $index = \\"\\" ) #set( $scan = true ) #set( $filterMap = {} ) @@ -7635,11 +8707,32 @@ null ## [End] Set query expression for @key ** ", "Query.syncItems.req.vtl": "## [Start] Sync Request template. ** +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end +#end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Sync\\", - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -7648,13 +8741,13 @@ null \\"nextToken\\": $util.toJson($util.defaultIfNull($ctx.args.nextToken, null)) } ## [End] Sync Request template. **", - "Query.syncItems.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.syncItems.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.testsByCategory.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -7727,7 +8820,27 @@ null #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByCategory.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -7765,7 +8878,27 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByEmail.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -7843,12 +8976,332 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByEmailByUpdatedAt.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Subscription.onCreateBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateBlog.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateBlog.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateCall.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateCall.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateCall.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateContentCategory.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateContentCategory.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateContentCategory.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateItem.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateItem.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreatePerson.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePerson.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePerson.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTodo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTodo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteBlog.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteBlog.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteCall.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteCall.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteCall.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteContentCategory.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteContentCategory.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteContentCategory.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteItem.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteItem.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePerson.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePerson.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePerson.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTodo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTodo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateBlog.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateBlog.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateCall.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateCall.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateCall.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateItem.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateItem.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePerson.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePerson.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePerson.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTodo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTodo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -7865,7 +9318,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.addContentToCategory.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.addContentToCategory.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.addContentToCategory.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -7893,25 +9352,6 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end {}", "Mutation.addContentToCategory.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -7976,13 +9416,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"ContentCategory\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.addContentToCategory.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.addContentToCategory.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createBlog.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -7994,26 +9435,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createBlog.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createBlog.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8078,13 +9506,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Blog\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createBlog.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createBlog.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createItem.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8096,45 +9525,32 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createItem.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createItem.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** ## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"orderId\\": $util.dynamodb.toDynamoDB($mergedValues.orderId), - \\"status#createdAt\\": $util.dynamodb.toDynamoDB(\\"\${mergedValues.status}#\${mergedValues.createdAt}\\") -})) -## [End] Set the primary key. ** -#if( $util.isNull($ctx.stash.metadata.dynamodbNameOverrideMap) ) - $util.qr($ctx.stash.metadata.put(\\"dynamodbNameOverrideMap\\", { - \\"status#createdAt\\": \\"statusCreatedAt\\" -})) -#else - $util.qr($ctx.stash.metadata.dynamodbNameOverrideMap.put(\\"status#createdAt\\", \\"statusCreatedAt\\")) -#end -$util.qr($ctx.args.input.put(\\"status#createdAt\\",\\"\${mergedValues.status}#\${mergedValues.createdAt}\\")) -{}", - "Mutation.createItem.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } + \\"status#createdAt\\": $util.dynamodb.toDynamoDB(\\"\${mergedValues.status}#\${mergedValues.createdAt}\\") +})) +## [End] Set the primary key. ** +#if( $util.isNull($ctx.stash.metadata.dynamodbNameOverrideMap) ) + $util.qr($ctx.stash.metadata.put(\\"dynamodbNameOverrideMap\\", { + \\"status#createdAt\\": \\"statusCreatedAt\\" })) +#else + $util.qr($ctx.stash.metadata.dynamodbNameOverrideMap.put(\\"status#createdAt\\", \\"statusCreatedAt\\")) #end -## End - key condition ** +$util.qr($ctx.args.input.put(\\"status#createdAt\\",\\"\${mergedValues.status}#\${mergedValues.createdAt}\\")) +{}", + "Mutation.createItem.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8199,13 +9615,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Item\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createItem.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createItem.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8217,7 +9634,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -8230,25 +9653,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8313,13 +9717,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTodo.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8331,26 +9736,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTodo.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTodo.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8415,13 +9807,20 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Todo\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTodo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTodo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteBlog.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -8479,14 +9878,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteBlog.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteBlog.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteContentFromCategory.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteContentFromCategory.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteContentFromCategory.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -8556,14 +9962,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteContentFromCategory.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteContentFromCategory.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteItem.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteItem.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -8631,14 +10044,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteItem.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteItem.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -8706,13 +10126,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteTodo.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -8770,13 +10197,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTodo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTodo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateBlog.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -8786,6 +10214,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateBlog.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -8915,13 +10349,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateBlog.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateBlog.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateItem.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -8931,7 +10366,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateItem.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateItem.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -9079,13 +10520,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateItem.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateItem.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -9095,7 +10537,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -9236,13 +10684,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTodo.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -9252,6 +10701,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateTodo.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -9381,13 +10836,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTodo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTodo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.byCreatedAt.req.vtl": "## [Start] Set query expression for key ** #if( !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"sortDirection is not supported for List operations without a Sort key defined.\\", \\"InvalidArgumentsError\\") @@ -9420,35 +10876,92 @@ $util.toJson($UpdateItem) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.byCreatedAt.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Query.getBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getBlog.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getBlog.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getBlog.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", - "Query.getItem.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] Get Response template. **", + "Query.getItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getItem.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"orderId\\": $util.dynamodb.toDynamoDB($ctx.args.orderId), \\"status#createdAt\\": $util.dynamodb.toDynamoDB(\\"\${ctx.args.status}#\${ctx.args.createdAt}\\") @@ -9458,26 +10971,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getItem.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getItem.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getItem.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] Get Response template. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email), \\"createdAt\\": $util.dynamodb.toDynamoDB($ctx.args.createdAt) @@ -9487,47 +11031,103 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTodo.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTodo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTodo.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", "Query.itemsByCreatedAt.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -9600,7 +11200,27 @@ $util.toJson($GetRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.itemsByCreatedAt.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -9678,12 +11298,38 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.itemsByStatus.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Query.listBlogs.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listBlogs.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9693,8 +11339,20 @@ $util.toJson($ctx.result)", #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9718,13 +11376,13 @@ $util.toJson($ctx.result)", #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listBlogs.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listBlogs.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.listByEmailKindDate.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -9843,7 +11501,27 @@ $util.toJson($ListRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listByEmailKindDate.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -9984,13 +11662,39 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.listContentByCategory.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", - "Query.listItems.postAuth.1.req.vtl": "## [Start] Set query expression for key ** + "Query.listItems.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listItems.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.orderId) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'orderId'.\\", \\"InvalidArgumentsError\\") #end @@ -10107,8 +11811,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -10132,14 +11848,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listItems.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listItems.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] ResponseTemplate. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.email) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'email'.\\", \\"InvalidArgumentsError\\") #end @@ -10210,8 +11932,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -10235,13 +11969,13 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.testsByCategory.req.vtl": "## [Start] Set query expression for key ** #set( $modelQueryExpression = {} ) ## [Start] Validate key arguments. ** @@ -10314,7 +12048,27 @@ $util.toJson($ListRequest) #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByCategory.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -10352,7 +12106,27 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByEmail.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) @@ -10430,11 +12204,241 @@ $util.toJson($ctx.result)", #set( $QueryRequest.scanIndexForward = true ) #end #if( $context.args.nextToken ) #set( $QueryRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) #set( $QueryRequest.filter = $util.parseJson(\\"$util.transform.toDynamoDBFilterExpression($ctx.args.filter)\\") ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $QueryRequest.filter = $filterExpression ) + #end +#end $util.toJson($QueryRequest)", "Query.testsByEmailByUpdatedAt.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)", + "Subscription.onCreateBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateBlog.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateBlog.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateContentCategory.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateContentCategory.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateContentCategory.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateItem.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateItem.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTodo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTodo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteBlog.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteBlog.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteContentCategory.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteContentCategory.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteContentCategory.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteItem.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteItem.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTodo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTodo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateBlog.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateBlog.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateBlog.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateItem.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateItem.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateItem.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTodo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTodo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTodo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap index 327c5ae5eca..4ec7e6b8267 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap +++ b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap @@ -13,7 +13,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -33,25 +39,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { $util.qr($ctx.args.input.put(\\"kind#other\\",\\"\${mergedValues.kind}#\${mergedValues.other}\\")) {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -116,14 +103,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -191,13 +185,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -207,7 +202,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -355,14 +356,21 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email), \\"kind#other\\": $util.dynamodb.toDynamoDB(\\"\${ctx.args.kind}#\${ctx.args.other}\\") @@ -372,26 +380,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] Get Response template. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.email) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'email'.\\", \\"InvalidArgumentsError\\") #end @@ -508,8 +547,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -533,13 +584,58 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -556,7 +652,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -569,25 +671,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -652,14 +735,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -727,13 +817,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -743,7 +834,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -884,14 +981,21 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email), \\"kind\\": $util.dynamodb.toDynamoDB($ctx.args.kind) @@ -901,26 +1005,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] Get Response template. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.email) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'email'.\\", \\"InvalidArgumentsError\\") #end @@ -991,8 +1126,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -1016,13 +1163,58 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -1039,7 +1231,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1051,25 +1249,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -1134,14 +1313,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1208,13 +1394,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -1224,7 +1411,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1364,14 +1557,21 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email) })) @@ -1380,26 +1580,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"sortDirection is not supported for List operations without a Sort key defined.\\", \\"InvalidArgumentsError\\") #end @@ -1427,8 +1658,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -1452,13 +1695,58 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -1475,7 +1763,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1488,25 +1782,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -1571,14 +1846,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1646,13 +1928,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -1662,7 +1945,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1803,14 +2092,21 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"status\\": $util.dynamodb.toDynamoDB($ctx.args.status), \\"lastStatus\\": $util.dynamodb.toDynamoDB($ctx.args.lastStatus) @@ -1820,26 +2116,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.status) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'status'.\\", \\"InvalidArgumentsError\\") #end @@ -1910,8 +2237,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -1935,13 +2274,58 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -1958,7 +2342,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -1971,25 +2361,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -2054,14 +2425,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2129,13 +2507,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.testCreate.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -2147,7 +2526,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.testCreate.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.testCreate.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.testCreate.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2160,25 +2545,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.testCreate.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -2243,14 +2609,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.testCreate.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.testCreate.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.testDelete.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.testDelete.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.testDelete.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2318,13 +2691,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.testDelete.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.testDelete.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.testUpdate.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2334,7 +2708,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.testUpdate.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.testUpdate.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.testUpdate.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2475,13 +2855,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.testUpdate.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.testUpdate.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2491,7 +2872,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2632,14 +3019,21 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email) })) @@ -2648,26 +3042,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"sortDirection is not supported for List operations without a Sort key defined.\\", \\"InvalidArgumentsError\\") #end @@ -2695,8 +3120,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -2720,14 +3157,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.testGet.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.testGet.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.testGet.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id), \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email) @@ -2737,26 +3180,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.testGet.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.testGet.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.testGet.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", - "Query.testList.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] Get Response template. **", + "Query.testList.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.testList.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.id) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'id'.\\", \\"InvalidArgumentsError\\") #end @@ -2827,8 +3301,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -2852,13 +3338,58 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.testList.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.testList.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -2875,7 +3406,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -2887,25 +3424,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -2970,14 +3488,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3044,13 +3569,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.testCreate.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3062,7 +3588,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.testCreate.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.testCreate.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.testCreate.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3075,25 +3607,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.testCreate.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3158,14 +3671,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.testCreate.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.testCreate.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.testDelete.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.testDelete.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.testDelete.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3233,13 +3753,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.testDelete.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.testDelete.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.testUpdate.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -3249,7 +3770,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.testUpdate.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.testUpdate.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.testUpdate.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3390,13 +3917,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.testUpdate.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.testUpdate.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -3406,7 +3934,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -3546,14 +4080,21 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.getTest.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email) })) @@ -3562,26 +4103,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.listTests.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTests.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"sortDirection is not supported for List operations without a Sort key defined.\\", \\"InvalidArgumentsError\\") #end @@ -3609,8 +4181,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3634,14 +4218,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.testGet.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +## [End] ResponseTemplate. **", + "Query.testGet.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.testGet.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id), \\"email\\": $util.dynamodb.toDynamoDB($ctx.args.email) @@ -3651,26 +4241,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.testGet.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.testGet.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.testGet.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", - "Query.testList.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] Get Response template. **", + "Query.testList.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.testList.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.id) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'id'.\\", \\"InvalidArgumentsError\\") #end @@ -3741,8 +4362,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3766,12 +4399,57 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.testList.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.testList.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts index a9871dab0d2..ceae35b364f 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts +++ b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts @@ -867,3 +867,20 @@ it('should support index/primary key with sync resolvers', () => { validateModelSchema(parse(definition)); }); + +test('LSI creation regression test', () => { + const inputSchema = ` + type Test @model { + id: ID! @primaryKey + index: ID! @index(name: "index1", sortKeyFields: ["id"]) + }`; + + const transformer = new GraphQLTransform({ + transformers: [new ModelTransformer(), new IndexTransformer(), new PrimaryKeyTransformer()], + }); + + const out = transformer.transform(inputSchema); + expect(out).toBeDefined(); + const schema = parse(out.schema); + validateModelSchema(schema); +}); diff --git a/packages/amplify-graphql-index-transformer/src/graphql-index-transformer.ts b/packages/amplify-graphql-index-transformer/src/graphql-index-transformer.ts index 17cf10a4ef4..929f6a001a1 100644 --- a/packages/amplify-graphql-index-transformer/src/graphql-index-transformer.ts +++ b/packages/amplify-graphql-index-transformer/src/graphql-index-transformer.ts @@ -110,7 +110,10 @@ function validate(config: IndexDirectiveConfiguration, ctx: TransformerContextPr if (peerDirective.name.value === 'primaryKey') { config.primaryKeyField = objectField; - if (!peerDirective.arguments!.some((arg: any) => arg.name.value === 'sortKeyFields')) { + if ( + objectField.name.value === field.name.value && + !peerDirective.arguments!.some((arg: any) => arg.name.value === 'sortKeyFields') + ) { throw new InvalidDirectiveError( `Invalid @index '${name}'. You may not create an index where the partition key ` + 'is the same as that of the primary key unless the primary key has a sort field. ' + diff --git a/packages/amplify-graphql-index-transformer/src/resolvers.ts b/packages/amplify-graphql-index-transformer/src/resolvers.ts index d2bc85396b5..699472a0c8f 100644 --- a/packages/amplify-graphql-index-transformer/src/resolvers.ts +++ b/packages/amplify-graphql-index-transformer/src/resolvers.ts @@ -12,13 +12,17 @@ import { bool, compoundExpression, DynamoDBMappingTemplate, + equals, Expression, forEach, ifElse, iff, + int, + isNullOrEmpty, list, methodCall, nul, + not, obj, print, printBlock, @@ -302,7 +306,7 @@ function setQuerySnippet(config: PrimaryKeyDirectiveConfiguration, ctx: Transfor expressions.push( set(ref(ResourceConstants.SNIPPETS.ModelQueryExpression), obj({})), - applyKeyExpressionForCompositeKey(keyNames, keyTypes, ResourceConstants.SNIPPETS.ModelQueryExpression), + applyKeyExpressionForCompositeKey(keyNames, keyTypes, ResourceConstants.SNIPPETS.ModelQueryExpression)!, ); return block(`Set query expression for key`, expressions); @@ -427,6 +431,7 @@ function makeQueryResolver(config: IndexDirectiveConfiguration, ctx: Transformer const dataSource = ctx.api.host.getDataSource(`${object.name.value}Table`); const queryTypeName = ctx.output.getQueryTypeName() as string; const table = getTable(ctx, object); + const authFilter = ref('ctx.stash.authFilter'); const requestVariable = 'QueryRequest'; assert(dataSource); @@ -457,10 +462,35 @@ function makeQueryResolver(config: IndexDirectiveConfiguration, ctx: Transformer set(ref(`${requestVariable}.scanIndexForward`), bool(true)), ), iff(ref('context.args.nextToken'), set(ref(`${requestVariable}.nextToken`), ref('context.args.nextToken')), true), + ifElse( + not(isNullOrEmpty(authFilter)), + compoundExpression([ + set(ref('filter'), authFilter), + iff( + not(isNullOrEmpty(ref('ctx.args.filter'))), + set(ref('filter'), obj({ and: list([ref('filter'), ref('ctx.args.filter')]) })), + ), + ]), + iff(not(isNullOrEmpty(ref('ctx.args.filter'))), set(ref('filter'), ref('ctx.args.filter'))), + ), iff( - ref('context.args.filter'), - set(ref(`${requestVariable}.filter`), ref('util.parseJson("$util.transform.toDynamoDBFilterExpression($ctx.args.filter)")')), - true, + not(isNullOrEmpty(ref('filter'))), + compoundExpression([ + set( + ref(`filterExpression`), + methodCall(ref('util.parseJson'), methodCall(ref('util.transform.toDynamoDBFilterExpression'), ref('filter'))), + ), + iff( + not(methodCall(ref('util.isNullOrBlank'), ref('filterExpression.expression'))), + compoundExpression([ + iff( + equals(methodCall(ref('filterEpression.expressionValues.size')), int(0)), + qref(methodCall(ref('filterEpression.remove'), str('expressionValues'))), + ), + set(ref(`${requestVariable}.filter`), ref(`filterExpression`)), + ]), + ), + ]), ), raw(`$util.toJson($${requestVariable})`), ]), @@ -522,7 +552,7 @@ function addIndexToResolverSlot(resolver: TransformerResolverProvider, lines: st const res = resolver as any; res.addToSlot( - 'postAuth', + 'preAuth', MappingTemplate.s3MappingTemplateFromString( lines.join('\n') + '\n' + (!isSync ? '{}' : ''), `${res.typeName}.${res.fieldName}.{slotName}.{slotIndex}.req.vtl`, diff --git a/packages/amplify-graphql-index-transformer/src/schema.ts b/packages/amplify-graphql-index-transformer/src/schema.ts index 759b1d24800..83a1152f26e 100644 --- a/packages/amplify-graphql-index-transformer/src/schema.ts +++ b/packages/amplify-graphql-index-transformer/src/schema.ts @@ -317,11 +317,21 @@ function replaceDeleteInput(config: PrimaryKeyDirectiveConfiguration, input: Inp } export function ensureQueryField(config: IndexDirectiveConfiguration, ctx: TransformerContextProvider): void { - const { object, queryField, sortKey } = config; + const { name, object, queryField, sortKey } = config; if (!queryField) { return; } + // add query field to metadata + const keyName = `${object.name.value}:indicies`; + let indicies: Set; + if (!ctx.metadata.has(keyName)) { + indicies = new Set([`${name}:${queryField}`]); + } else { + indicies = ctx.metadata.get>(keyName)!; + indicies.add(`${name}:${queryField}`); + } + ctx.metadata.set(keyName, indicies); const args = [createHashField(config)]; @@ -353,7 +363,7 @@ function generateModelXConnectionType(config: IndexDirectiveConfiguration, ctx: let connectionTypeExtension = blankObjectExtension(tableXConnectionName); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [ - makeField('items', [], makeListType(makeNamedType(tableXConnectionName))), + makeField('items', [], makeNonNullType(makeListType(makeNonNullType(makeNamedType(object.name.value))))), ]); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [makeField('nextToken', [], makeNamedType('String'))]); diff --git a/packages/amplify-graphql-model-transformer/src/__tests__/__snapshots__/model-transformer.test.ts.snap b/packages/amplify-graphql-model-transformer/src/__tests__/__snapshots__/model-transformer.test.ts.snap index cedcc76d586..20cda2065f2 100644 --- a/packages/amplify-graphql-model-transformer/src/__tests__/__snapshots__/model-transformer.test.ts.snap +++ b/packages/amplify-graphql-model-transformer/src/__tests__/__snapshots__/model-transformer.test.ts.snap @@ -107,7 +107,7 @@ enum ModelSortDirection { } type ModelTestConnection { - items: [Test] + items: [Test!]! nextToken: String } @@ -162,7 +162,7 @@ type Subscription { } type ModelEmailConnection { - items: [Email] + items: [Email!]! nextToken: String } @@ -356,7 +356,7 @@ input EntityMetadataInput { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -463,7 +463,7 @@ type Subscription { } type ModelAuthorConnection { - items: [Author] + items: [Author!]! nextToken: String } @@ -501,7 +501,7 @@ input DeleteAuthorInput { } type ModelRequireConnection { - items: [Require] + items: [Require!]! nextToken: String } @@ -539,7 +539,7 @@ input DeleteRequireInput { } type ModelCommentConnection { - items: [Comment] + items: [Comment!]! nextToken: String } @@ -583,7 +583,7 @@ input DeleteCommentInput { " `; -exports[`ModelTransformer: should generate sync resolver with ConflictHandlerType.AUTOMERGE 1`] = ` +exports[`ModelTransformer: should generate sync resolver with ConflictHandlerType.Automerge 1`] = ` Object { "Mutation.createAuthor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) @@ -596,26 +596,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createAuthor.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createAuthor.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -680,13 +667,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Author\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createComment.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -698,26 +686,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createComment.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createComment.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -782,13 +757,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Comment\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createEmail.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -800,26 +776,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createEmail.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createEmail.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -884,13 +847,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Email\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createPost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -902,26 +866,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -986,13 +937,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Post\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createRequire.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -1004,26 +956,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createRequire.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createRequire.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -1088,13 +1027,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Require\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -1106,26 +1046,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -1190,13 +1117,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createUser.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -1208,26 +1136,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createUser.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createUser.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -1292,13 +1207,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"User\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.customCreatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -1310,26 +1226,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.customCreatePost.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.customCreatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.customCreatePost.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -1394,13 +1297,20 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Post\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.customCreatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customCreatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.customDeletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.customDeletePost.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -1458,13 +1368,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.customDeletePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customDeletePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.customUpdatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -1474,6 +1385,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.customUpdatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.customUpdatePost.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -1603,13 +1520,20 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.customUpdatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customUpdatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteAuthor.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -1667,13 +1591,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteComment.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -1731,13 +1662,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteEmail.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -1795,13 +1733,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deletePost.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -1860,13 +1805,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) $util.qr($DeleteRequest.put(\\"_version\\", $util.defaultIfNull($ctx.args.input[\\"_version\\"], \\"0\\"))) $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteRequire.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -1924,13 +1876,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteTest.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -1988,13 +1947,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteUser.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -2052,13 +2018,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateAuthor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2068,6 +2035,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateAuthor.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -2197,13 +2170,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateComment.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2213,6 +2187,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateComment.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -2342,13 +2322,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateEmail.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2358,6 +2339,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateEmail.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -2487,13 +2474,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2503,6 +2491,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updatePost.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -2633,13 +2627,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateRequire.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2649,6 +2644,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateRequire.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -2778,13 +2779,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2794,6 +2796,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateTest.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -2923,13 +2931,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateUser.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -2939,6 +2948,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateUser.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -3068,35 +3083,73 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.customGetPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.customGetPost.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.customGetPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.customGetPost.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.customListPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.customListPost.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -3106,8 +3159,20 @@ $util.toJson($GetRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3131,189 +3196,443 @@ $util.toJson($GetRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.customListPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.customListPost.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.getAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getAuthor.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getAuthor.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getComment.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getComment.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getEmail.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getEmail.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getEntity.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getEntity.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getEntity.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getEntity.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getPost.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getPost.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getRequire.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getRequire.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getUser.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getUser.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.listAuthors.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listAuthors.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -3323,9 +3642,21 @@ $util.toJson($GetRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) - #if( !$util.isNullOrBlank($filterExpression.expression) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) #end @@ -3348,13 +3679,19 @@ $util.toJson($GetRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listAuthors.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listAuthors.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listComments.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listComments.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -3364,8 +3701,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3389,13 +3738,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listComments.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listComments.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listEmails.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listEmails.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -3405,8 +3760,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3430,13 +3797,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listEmails.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listEmails.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listPosts.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listPosts.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -3446,8 +3819,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3471,13 +3856,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listPosts.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listPosts.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listRequires.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listRequires.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -3487,8 +3878,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3512,13 +3915,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listRequires.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listRequires.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listTests.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -3528,8 +3937,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3553,13 +3974,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listUsers.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listUsers.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -3569,8 +3996,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -3594,19 +4033,46 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listUsers.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listUsers.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.syncPosts.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.syncPosts.req.vtl": "## [Start] Sync Request template. ** +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end +#end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Sync\\", - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -3615,13 +4081,328 @@ null \\"nextToken\\": $util.toJson($util.defaultIfNull($ctx.args.nextToken, null)) } ## [End] Sync Request template. **", - "Query.syncPosts.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.syncPosts.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -3638,26 +4419,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createAuthor.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createAuthor.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3722,13 +4490,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Author\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createComment.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3740,26 +4509,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createComment.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createComment.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3824,13 +4580,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Comment\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createEmail.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3842,26 +4599,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createEmail.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createEmail.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -3926,13 +4670,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Email\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createPost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -3944,26 +4689,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -4028,13 +4760,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Post\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createRequire.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -4046,26 +4779,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createRequire.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createRequire.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -4130,13 +4850,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Require\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -4148,26 +4869,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -4232,13 +4940,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createUser.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -4250,26 +4959,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createUser.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createUser.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -4334,44 +5030,32 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"User\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) - $util.error($ctx.error.message, $ctx.error.type) -#else - $util.toJson($ctx.result) -#end -## [End] Get ResponseTemplate. **", - "Mutation.customCreatePost.init.1.req.vtl": "## [Start] Initialization default values. ** -$util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) -#set( $createdAt = $util.time.nowISO8601() ) -$util.qr($ctx.stash.defaultValues.put(\\"id\\", $util.autoId())) -$util.qr($ctx.stash.defaultValues.put(\\"createdAt\\", $createdAt)) -$util.qr($ctx.stash.defaultValues.put(\\"updatedAt\\", $createdAt)) -$util.toJson({ - \\"version\\": \\"2018-05-29\\", - \\"payload\\": {} -}) -## [End] Initialization default values. **", - "Mutation.customCreatePost.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + $util.error($ctx.error.message, $ctx.error.type) +#else + $util.toJson($ctx.result) #end -## End - key condition ** +## [End] ResponseTemplate. **", + "Mutation.customCreatePost.init.1.req.vtl": "## [Start] Initialization default values. ** +$util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) +#set( $createdAt = $util.time.nowISO8601() ) +$util.qr($ctx.stash.defaultValues.put(\\"id\\", $util.autoId())) +$util.qr($ctx.stash.defaultValues.put(\\"createdAt\\", $createdAt)) +$util.qr($ctx.stash.defaultValues.put(\\"updatedAt\\", $createdAt)) +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Initialization default values. **", + "Mutation.customCreatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.customCreatePost.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -4436,13 +5120,20 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Post\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.customCreatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customCreatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.customDeletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.customDeletePost.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -4500,13 +5191,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.customDeletePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customDeletePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.customUpdatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -4516,6 +5208,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.customUpdatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.customUpdatePost.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -4645,13 +5343,20 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.customUpdatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customUpdatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteAuthor.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -4709,13 +5414,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteComment.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -4773,13 +5485,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteEmail.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -4837,13 +5556,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deletePost.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -4902,13 +5628,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) $util.qr($DeleteRequest.put(\\"_version\\", $util.defaultIfNull($ctx.args.input[\\"_version\\"], \\"0\\"))) $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteRequire.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -4966,13 +5699,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteTest.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -5030,13 +5770,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteUser.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -5094,13 +5841,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateAuthor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5110,6 +5858,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateAuthor.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -5239,13 +5993,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateComment.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5255,6 +6010,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateComment.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -5384,13 +6145,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateEmail.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5400,6 +6162,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateEmail.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -5529,13 +6297,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5545,6 +6314,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updatePost.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -5675,13 +6450,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateRequire.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5691,6 +6467,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateRequire.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -5820,13 +6602,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5836,6 +6619,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateTest.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -5965,13 +6754,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateUser.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -5981,6 +6771,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateUser.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -6110,35 +6906,73 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.customGetPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.customGetPost.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.customGetPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.customGetPost.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.customListPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.customListPost.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6148,8 +6982,20 @@ $util.toJson($GetRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6173,189 +7019,443 @@ $util.toJson($GetRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.customListPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.customListPost.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.getAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getAuthor.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getAuthor.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getComment.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getComment.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getEmail.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getEmail.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getEntity.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getEntity.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getEntity.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getEntity.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getPost.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getPost.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getRequire.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getRequire.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getUser.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getUser.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.listAuthors.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listAuthors.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6365,8 +7465,20 @@ $util.toJson($GetRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6390,13 +7502,19 @@ $util.toJson($GetRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listAuthors.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listAuthors.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listComments.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listComments.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6406,8 +7524,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6431,13 +7561,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listComments.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listComments.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listEmails.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listEmails.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6447,8 +7583,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6472,13 +7620,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listEmails.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listEmails.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listPosts.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listPosts.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6488,8 +7642,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6513,13 +7679,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listPosts.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listPosts.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listRequires.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listRequires.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6529,8 +7701,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6554,13 +7738,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listRequires.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listRequires.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listTests.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6570,8 +7760,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6595,13 +7797,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listUsers.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listUsers.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -6611,8 +7819,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -6636,38 +7856,380 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listUsers.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listUsers.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.syncPosts.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.syncPosts.req.vtl": "## [Start] Sync Request template. ** +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end +#end { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"Sync\\", - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) - #else -null - #end, - \\"limit\\": $util.defaultIfNull($ctx.args.limit, 100), - \\"lastSync\\": $util.toJson($util.defaultIfNull($ctx.args.lastSync, null)), - \\"nextToken\\": $util.toJson($util.defaultIfNull($ctx.args.nextToken, null)) -} -## [End] Sync Request template. **", - "Query.syncPosts.res.vtl": "## [Start] Get ResponseTemplate. ** -#if( $ctx.error ) - $util.error($ctx.error.message, $ctx.error.type, $ctx.result) -#else - $util.toJson($ctx.result) -#end -## [End] Get ResponseTemplate. **", + \\"operation\\": \\"Sync\\", + \\"filter\\": #if( $filter ) +$util.toJson($filter) + #else +null + #end, + \\"limit\\": $util.defaultIfNull($ctx.args.limit, 100), + \\"lastSync\\": $util.toJson($util.defaultIfNull($ctx.args.lastSync, null)), + \\"nextToken\\": $util.toJson($util.defaultIfNull($ctx.args.nextToken, null)) +} +## [End] Sync Request template. **", + "Query.syncPosts.res.vtl": "## [Start] ResponseTemplate. ** +#if( $ctx.error ) + $util.error($ctx.error.message, $ctx.error.type, $ctx.result) +#else + $util.toJson($ctx.result) +#end +## [End] ResponseTemplate. **", + "Subscription.onCreateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; -exports[`ModelTransformer: should generate sync resolver with ConflictHandlerType.OPTIMISTIC 1`] = ` +exports[`ModelTransformer: should generate sync resolver with ConflictHandlerType.Optimistic 1`] = ` Object { "Mutation.createAuthor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) @@ -6680,26 +8242,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createAuthor.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createAuthor.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -6764,13 +8313,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Author\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createComment.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -6782,26 +8332,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createComment.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createComment.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -6866,13 +8403,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Comment\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createEmail.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -6884,26 +8422,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createEmail.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createEmail.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -6968,13 +8493,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Email\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createPost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -6986,26 +8512,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -7070,13 +8583,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Post\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createRequire.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -7088,26 +8602,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createRequire.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createRequire.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -7172,13 +8673,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Require\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -7190,26 +8692,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -7274,13 +8763,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createUser.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -7292,26 +8782,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createUser.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createUser.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -7376,13 +8853,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"User\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.customCreatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -7394,26 +8872,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.customCreatePost.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.customCreatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.customCreatePost.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -7478,13 +8943,20 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Post\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.customCreatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customCreatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.customDeletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.customDeletePost.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -7542,13 +9014,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.customDeletePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customDeletePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.customUpdatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -7558,6 +9031,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.customUpdatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.customUpdatePost.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -7687,13 +9166,20 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.customUpdatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.customUpdatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteAuthor.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -7751,13 +9237,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteComment.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -7815,13 +9308,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteEmail.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -7879,13 +9379,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deletePost.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -7944,13 +9451,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) $util.qr($DeleteRequest.put(\\"_version\\", $util.defaultIfNull($ctx.args.input[\\"_version\\"], \\"0\\"))) $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteRequire.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -8008,13 +9522,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteTest.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -8072,13 +9593,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteUser.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -8136,13 +9664,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateAuthor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -8152,6 +9681,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateAuthor.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -8281,13 +9816,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateComment.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -8297,6 +9833,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateComment.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -8426,13 +9968,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateEmail.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -8442,6 +9985,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateEmail.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -8571,13 +10120,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateEmail.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -8587,6 +10137,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updatePost.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -8717,13 +10273,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateRequire.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -8733,6 +10290,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateRequire.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -8862,13 +10425,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateRequire.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -8878,6 +10442,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateTest.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -9007,13 +10577,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateUser.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -9023,6 +10594,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateUser.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -9152,35 +10729,73 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.customGetPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.customGetPost.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.customGetPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.customGetPost.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.customListPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.customListPost.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9190,8 +10805,20 @@ $util.toJson($GetRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9215,189 +10842,443 @@ $util.toJson($GetRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.customListPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.customListPost.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.getAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getAuthor.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getAuthor.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getComment.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getComment.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getEmail.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getEmail.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getEmail.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getEntity.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getEntity.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getEntity.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getEntity.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getPost.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getPost.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getRequire.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" +} ) +#if( $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues } ) -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getRequire.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getRequire.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getUser.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getUser.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.listAuthors.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listAuthors.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9407,8 +11288,20 @@ $util.toJson($GetRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9432,13 +11325,19 @@ $util.toJson($GetRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listAuthors.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listAuthors.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listComments.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listComments.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9448,8 +11347,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9473,13 +11384,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listComments.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listComments.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listEmails.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listEmails.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9489,8 +11406,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9514,13 +11443,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listEmails.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listEmails.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listPosts.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listPosts.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9530,8 +11465,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9555,13 +11502,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listPosts.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listPosts.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listRequires.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listRequires.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9571,8 +11524,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9596,13 +11561,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listRequires.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listRequires.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listTests.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9612,8 +11583,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9637,13 +11620,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listUsers.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listUsers.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -9653,8 +11642,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -9678,19 +11679,46 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listUsers.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listUsers.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.syncPosts.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.syncPosts.req.vtl": "## [Start] Sync Request template. ** +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end +#end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Sync\\", - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -9699,13 +11727,328 @@ null \\"nextToken\\": $util.toJson($util.defaultIfNull($ctx.args.nextToken, null)) } ## [End] Sync Request template. **", - "Query.syncPosts.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.syncPosts.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type, $ctx.result) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateEmail.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateEmail.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateEmail.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateRequire.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateRequire.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateRequire.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -9810,7 +12153,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -9873,25 +12216,6 @@ type Subscription { exports[`ModelTransformer: should have timestamps as nullable fields when the type makes it non-nullable 2`] = ` "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -10092,25 +12416,6 @@ $util.toJson($UpdateItem) exports[`ModelTransformer: should not add default primary key when ID is defined 1`] = ` "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -10385,7 +12690,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -10448,25 +12753,6 @@ type Subscription { exports[`ModelTransformer: should not to auto generate createdAt and updatedAt when the type in schema is not AWSDateTime 2`] = ` "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -10764,7 +13050,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -10819,25 +13105,6 @@ type Subscription { exports[`ModelTransformer: should not to include createdAt and updatedAt field when timestamps is set to null 2`] = ` "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -11137,7 +13404,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -11192,25 +13459,6 @@ type Subscription { exports[`ModelTransformer: should support timestamp parameters when generating pipelineFunctions and output schema 2`] = ` "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** diff --git a/packages/amplify-graphql-model-transformer/src/__tests__/model-transformer.test.ts b/packages/amplify-graphql-model-transformer/src/__tests__/model-transformer.test.ts index 557b0565fb5..420ae5e4e38 100644 --- a/packages/amplify-graphql-model-transformer/src/__tests__/model-transformer.test.ts +++ b/packages/amplify-graphql-model-transformer/src/__tests__/model-transformer.test.ts @@ -881,7 +881,7 @@ describe('ModelTransformer: ', () => { validateModelSchema(parse(out.schema)); }); - it('should generate sync resolver with ConflictHandlerType.AUTOMERGE', () => { + it('should generate sync resolver with ConflictHandlerType.Automerge', () => { const validSchema = ` type Post @model { id: ID! @@ -950,7 +950,7 @@ describe('ModelTransformer: ', () => { validateModelSchema(parse(definition)); }); - it('should generate sync resolver with ConflictHandlerType.OPTIMISTIC', () => { + it('should generate sync resolver with ConflictHandlerType.Optimistic', () => { const validSchema = ` type Post @model { id: ID! diff --git a/packages/amplify-graphql-model-transformer/src/definitions.ts b/packages/amplify-graphql-model-transformer/src/definitions.ts index e1aafe51b0e..77d4eb31c58 100644 --- a/packages/amplify-graphql-model-transformer/src/definitions.ts +++ b/packages/amplify-graphql-model-transformer/src/definitions.ts @@ -13,4 +13,4 @@ export const BOOLEAN_FUNCTIONS = new Set(['attributeExists', 'attributeT export const ATTRIBUTE_TYPES = ['binary', 'binarySet', 'bool', 'list', 'map', 'number', 'numberSet', 'string', 'stringSet', '_null']; - +export const OPERATION_KEY = '__operation'; diff --git a/packages/amplify-graphql-model-transformer/src/graphql-model-transformer.ts b/packages/amplify-graphql-model-transformer/src/graphql-model-transformer.ts index 544ccc833f8..0d67a92cd98 100644 --- a/packages/amplify-graphql-model-transformer/src/graphql-model-transformer.ts +++ b/packages/amplify-graphql-model-transformer/src/graphql-model-transformer.ts @@ -54,6 +54,7 @@ import { makeUpdateInputField, } from './graphql-types'; import { + generateAuthExpressionForSandboxMode, generateCreateInitSlotTemplate, generateCreateRequestTemplate, generateDefaultResponseMappingTemplate, @@ -64,7 +65,12 @@ import { generateUpdateInitSlotTemplate, generateUpdateRequestTemplate, } from './resolvers'; -import { generateGetRequestTemplate, generateListRequestTemplate, generateSyncRequestTemplate } from './resolvers/query'; +import { + generateGetRequestTemplate, + generateGetResponseTemplate, + generateListRequestTemplate, + generateSyncRequestTemplate, +} from './resolvers/query'; import { DirectiveWrapper, FieldWrapper, @@ -88,17 +94,17 @@ export type ModelDirectiveConfiguration = { list: OptionalAndNullable; sync: OptionalAndNullable; }>; - mutations: { + mutations: OptionalAndNullable<{ create: OptionalAndNullable; update: OptionalAndNullable; delete: OptionalAndNullable; - } | null; - subscriptions: { + }>; + subscriptions: OptionalAndNullable<{ onCreate: OptionalAndNullable[]; onUpdate: OptionalAndNullable[]; onDelete: OptionalAndNullable[]; - level: Partial; - } | null; + level: SubscriptionLevel; + }>; timestamps: OptionalAndNullable<{ createdAt: OptionalAndNullable; updatedAt: OptionalAndNullable; @@ -169,12 +175,13 @@ export class ModelTransformer extends TransformerModelBase implements Transforme `'${definition.name.value}' is a reserved type name and currently in use within the default schema element.`, ); } - // todo: get model configuration with default values and store it in the map const typeName = definition.name.value; + if (ctx.isProjectUsingDataStore()) { SyncUtils.validateResolverConfigForType(ctx, typeName); } + const directiveWrapped: DirectiveWrapper = new DirectiveWrapper(directive); const options = directiveWrapped.getArguments({ queries: { @@ -188,7 +195,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme delete: toCamelCase(['delete', typeName]), }, subscriptions: { - level: SubscriptionLevel.public, + level: SubscriptionLevel.on, onCreate: [this.ensureValidSubscriptionName(toCamelCase(['onCreate', typeName]))], onDelete: [this.ensureValidSubscriptionName(toCamelCase(['onDelete', typeName]))], onUpdate: [this.ensureValidSubscriptionName(toCamelCase(['onUpdate', typeName]))], @@ -241,8 +248,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme generateResolvers = (context: TransformerContextProvider): void => { for (let type of this.typesWithModelDirective) { - const def = context.output.getObject(type); - + const def = context.output.getObject(type)!; // This name is used by the mock functionality. Changing this can break mock. const tableLogicalName = `${def!.name.value}Table`; const stack = context.stackManager.getStackFor(tableLogicalName, def!.name.value); @@ -265,7 +271,13 @@ export class ModelTransformer extends TransformerModelBase implements Transforme default: throw new Error('Unknown query field type'); } - + resolver.addToSlot( + 'postAuth', + MappingTemplate.s3MappingTemplateFromString( + generateAuthExpressionForSandboxMode(context), + `${query.typeName}.${query.fieldName}.{slotName}.{slotIndex}.req.vtl`, + ), + ); resolver.mapToStack(stack); context.resolvers.addResolver(query.typeName, query.fieldName, resolver); } @@ -284,11 +296,49 @@ export class ModelTransformer extends TransformerModelBase implements Transforme resolver = this.generateUpdateResolver(context, def!, mutation.typeName, mutation.fieldName); break; default: - throw new Error('Unknown query field type'); + throw new Error('Unknown mutation field type'); } + resolver.addToSlot( + 'postAuth', + MappingTemplate.s3MappingTemplateFromString( + generateAuthExpressionForSandboxMode(context), + `${mutation.typeName}.${mutation.fieldName}.{slotName}.{slotIndex}.req.vtl`, + ), + ); resolver.mapToStack(stack); context.resolvers.addResolver(mutation.typeName, mutation.fieldName, resolver); } + + const subscriptionLevel = this.modelDirectiveConfig.get(def.name.value)?.subscriptions?.level; + // in order to create subscription resolvers the level needs to be on + if (subscriptionLevel === SubscriptionLevel.on) { + const subscriptionFields = this.getSubscriptionFieldNames(context, def!); + for (let subscription of subscriptionFields.values()) { + let resolver; + switch (subscription.type) { + case SubscriptionFieldType.ON_CREATE: + resolver = this.generateOnCreateResolver(context, def, subscription.typeName, subscription.fieldName); + break; + case SubscriptionFieldType.ON_UPDATE: + resolver = this.generateOnUpdateResolver(context, def, subscription.typeName, subscription.fieldName); + break; + case SubscriptionFieldType.ON_DELETE: + resolver = this.generateOnDeleteResolver(context, def, subscription.typeName, subscription.fieldName); + break; + default: + throw new Error('Unknown subscription field type'); + } + resolver.addToSlot( + 'postAuth', + MappingTemplate.s3MappingTemplateFromString( + generateAuthExpressionForSandboxMode(context), + `${subscription.typeName}.${subscription.fieldName}.{slotName}.{slotIndex}.req.vtl`, + ), + ); + resolver.mapToStack(stack); + context.resolvers.addResolver(subscription.typeName, subscription.fieldName, resolver); + } + } } }; @@ -307,10 +357,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme fieldName, dataSource, MappingTemplate.s3MappingTemplateFromString(generateGetRequestTemplate(), `${typeName}.${fieldName}.req.vtl`), - MappingTemplate.s3MappingTemplateFromString( - generateDefaultResponseMappingTemplate(isSyncEnabled), - `${typeName}.${fieldName}.res.vtl`, - ), + MappingTemplate.s3MappingTemplateFromString(generateGetResponseTemplate(isSyncEnabled), `${typeName}.${fieldName}.res.vtl`), ); } return this.resolverMap[resolverKey]; @@ -359,7 +406,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme `${typeName}.${fieldName}.req.vtl`, ), MappingTemplate.s3MappingTemplateFromString( - generateDefaultResponseMappingTemplate(isSyncEnabled), + generateDefaultResponseMappingTemplate(isSyncEnabled, true), `${typeName}.${fieldName}.res.vtl`, ), ); @@ -391,7 +438,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme dataSource, MappingTemplate.s3MappingTemplateFromString(generateDeleteRequestTemplate(isSyncEnabled), `${typeName}.${fieldName}.req.vtl`), MappingTemplate.s3MappingTemplateFromString( - generateDefaultResponseMappingTemplate(isSyncEnabled), + generateDefaultResponseMappingTemplate(isSyncEnabled, true), `${typeName}.${fieldName}.res.vtl`, ), ); @@ -701,7 +748,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme dataSource, MappingTemplate.s3MappingTemplateFromString(generateCreateRequestTemplate(type.name.value), `${typeName}.${fieldName}.req.vtl`), MappingTemplate.s3MappingTemplateFromString( - generateDefaultResponseMappingTemplate(isSyncEnabled), + generateDefaultResponseMappingTemplate(isSyncEnabled, true), `${typeName}.${fieldName}.res.vtl`, ), ); @@ -1164,7 +1211,6 @@ export class ModelTransformer extends TransformerModelBase implements Transforme SyncUtils.createSyncLambdaIAMPolicy(stack, syncConfig.LambdaConflictHandler.name, syncConfig.LambdaConflictHandler.region), ); } - return role; } diff --git a/packages/amplify-graphql-model-transformer/src/graphql-types/query.ts b/packages/amplify-graphql-model-transformer/src/graphql-types/query.ts index 0633eaf8e82..453f9924137 100644 --- a/packages/amplify-graphql-model-transformer/src/graphql-types/query.ts +++ b/packages/amplify-graphql-model-transformer/src/graphql-types/query.ts @@ -13,7 +13,7 @@ export const makeListQueryFilterInput = ( export const makeListQueryModel = (type: ObjectTypeDefinitionNode, modelName: string, isSyncEnabled: boolean): ObjectTypeDefinitionNode => { const outputType = ObjectDefinitionWrapper.create(modelName); - outputType.addField(FieldWrapper.create('items', type.name.value, true, true)); + outputType.addField(FieldWrapper.create('items', type.name.value, false, true)); outputType.addField(FieldWrapper.create('nextToken', 'String', true, false)); if (isSyncEnabled) { diff --git a/packages/amplify-graphql-model-transformer/src/index.ts b/packages/amplify-graphql-model-transformer/src/index.ts index d419f3ed214..10267d40d30 100644 --- a/packages/amplify-graphql-model-transformer/src/index.ts +++ b/packages/amplify-graphql-model-transformer/src/index.ts @@ -1,3 +1,4 @@ -export { ModelTransformer } from './graphql-model-transformer'; +export { ModelTransformer, ModelDirectiveConfiguration, SubscriptionLevel } from './graphql-model-transformer'; +export { OPERATION_KEY } from './definitions'; export * from './graphql-types'; export * from './resolvers'; diff --git a/packages/amplify-graphql-model-transformer/src/resolvers/common.ts b/packages/amplify-graphql-model-transformer/src/resolvers/common.ts index ed2fcd07e41..01c9059ac81 100644 --- a/packages/amplify-graphql-model-transformer/src/resolvers/common.ts +++ b/packages/amplify-graphql-model-transformer/src/resolvers/common.ts @@ -15,7 +15,13 @@ import { ifElse, printBlock, toJson, + qref, + str, + not, } from 'graphql-mapping-template'; +import { OPERATION_KEY } from '../definitions'; + +const API_KEY = 'API Key Authorization'; /** * Helper method to generate code that converts DynamoDB condition object to condition @@ -57,9 +63,11 @@ export const generateConditionSlot = (inputConditionObjectName: string, conditio /** * Generate common response template used by most of the resolvers. + * Append operation if response is coming from a mutation, this is to protect field resolver for subscriptions */ -export const generateDefaultResponseMappingTemplate = (isSyncEnabled: boolean): string => { +export const generateDefaultResponseMappingTemplate = (isSyncEnabled: boolean, mutation = false): string => { const statements: Expression[] = []; + if (mutation) statements.push(qref(methodCall(ref('ctx.result.put'), str(OPERATION_KEY), str('Mutation')))); if (isSyncEnabled) { statements.push( ifElse( @@ -74,14 +82,30 @@ export const generateDefaultResponseMappingTemplate = (isSyncEnabled: boolean): ); } - return printBlock('Get ResponseTemplate')(compoundExpression(statements)); + return printBlock('ResponseTemplate')(compoundExpression(statements)); }; /** - * Util function to gernate resolver key used to keep track of all the resolvers in memory + * Util function to generate resolver key used to keep track of all the resolvers in memory * @param typeName Name of the type * @param fieldName Name of the field */ export const generateResolverKey = (typeName: string, fieldName: string): string => { return `${typeName}.${fieldName}`; }; + +/** + * Util function to generate sandbox mode expression + * @param ctx context to get sandbox mode + */ +export const generateAuthExpressionForSandboxMode = (ctx: any): string => { + const enabled = ctx.resourceHelper.api.sandboxModeEnabled; + let exp; + + if (enabled) exp = iff(notEquals(methodCall(ref('util.authType')), str(API_KEY)), methodCall(ref('util.unauthorized'))); + else exp = methodCall(ref('util.unauthorized')); + + return printBlock(`Sandbox Mode ${enabled ? 'Enabled' : 'Disabled'}`)( + compoundExpression([iff(not(ref('ctx.stash.get("hasAuth")')), exp), toJson(obj({}))]), + ); +}; diff --git a/packages/amplify-graphql-model-transformer/src/resolvers/mutation.ts b/packages/amplify-graphql-model-transformer/src/resolvers/mutation.ts index 3aa5f732ba1..240b3899497 100644 --- a/packages/amplify-graphql-model-transformer/src/resolvers/mutation.ts +++ b/packages/amplify-graphql-model-transformer/src/resolvers/mutation.ts @@ -152,8 +152,6 @@ export const generateUpdateRequestTemplate = (modelName: string, isSyncEnabled: */ export const generateCreateRequestTemplate = (modelName: string): string => { const statements: Expression[] = [ - // set key the condition - ...generateKeyConditionTemplate(false), // Generate conditions comment('Set the default values to put request'), set(ref('mergedValues'), methodCall(ref('util.defaultIfNull'), ref('ctx.stash.defaultValues'), obj({}))), @@ -174,7 +172,6 @@ export const generateCreateRequestTemplate = (modelName: string): string => { ), // add conditions - iff(ref('context.args.condition'), qref(methodCall(ref('ctx.stash.conditions.add'), ref('context.args.condition')))), // key conditions ...generateKeyConditionTemplate(false), diff --git a/packages/amplify-graphql-model-transformer/src/resolvers/query.ts b/packages/amplify-graphql-model-transformer/src/resolvers/query.ts index d73bfb587be..ac92ee42a72 100644 --- a/packages/amplify-graphql-model-transformer/src/resolvers/query.ts +++ b/packages/amplify-graphql-model-transformer/src/resolvers/query.ts @@ -16,27 +16,86 @@ import { equals, bool, and, + isNullOrEmpty, + list, + forEach, nul, } from 'graphql-mapping-template'; import { ResourceConstants } from 'graphql-transformer-common'; +const authFilter = ref('ctx.stash.authFilter'); + /** * Generate get query resolver template */ export const generateGetRequestTemplate = (): string => { const statements: Expression[] = [ - set(ref('GetRequest'), obj({ version: str('2018-05-29'), operation: str('GetItem') })), + set(ref('GetRequest'), obj({ version: str('2018-05-29'), operation: str('Query') })), ifElse( ref('ctx.stash.metadata.modelObjectKey'), - set(ref('key'), ref('ctx.stash.metadata.modelObjectKey')), - compoundExpression([set(ref('key'), obj({ id: methodCall(ref('util.dynamodb.toDynamoDB'), ref('ctx.args.id')) }))]), + compoundExpression([ + set(ref('expression'), str('')), + set(ref('expressionNames'), obj({})), + set(ref('expressionValues'), obj({})), + forEach(ref('item'), ref('ctx.stash.metadata.modelObjectKey.entrySet()'), [ + set(ref('expression'), str('$expression#keyCount$velocityCount = :valueCount$velocityCount AND ')), + qref(methodCall(ref('expressionNames.put'), str('#keyCount$velocityCount'), ref('item.key'))), + qref(methodCall(ref('expressionValues.put'), str(':valueCount$velocityCount'), ref('item.value'))), + ]), + set(ref('expression'), methodCall(ref('expression.replaceAll'), str('AND $'), str(''))), + set( + ref('query'), + obj({ expression: ref('expression'), expressionNames: ref('expressionNames'), expressionValues: ref('expressionValues') }), + ), + ]), + set( + ref('query'), + obj({ + expression: str('id = :id'), + expressionValues: obj({ + ':id': methodCall(ref('util.parseJson'), methodCall(ref('util.dynamodb.toDynamoDBJson'), ref('ctx.args.id'))), + }), + }), + ), + ), + qref(methodCall(ref('GetRequest.put'), str('query'), ref('query'))), + iff( + not(isNullOrEmpty(authFilter)), + qref( + methodCall( + ref('GetRequest.put'), + str('filter'), + methodCall(ref('util.parseJson'), methodCall(ref('util.transform.toDynamoDBFilterExpression'), authFilter)), + ), + ), ), - qref(methodCall(ref('GetRequest.put'), str('key'), ref('key'))), toJson(ref('GetRequest')), ]; return printBlock('Get Request template')(compoundExpression(statements)); }; +export const generateGetResponseTemplate = (isSyncEnabled: boolean): string => { + const statements = new Array(); + if (isSyncEnabled) { + statements.push( + iff(ref('ctx.error'), methodCall(ref('util.error'), ref('ctx.error.message'), ref('ctx.error.type'), ref('ctx.result'))), + ); + } else { + statements.push(iff(ref('ctx.error'), methodCall(ref('util.error'), ref('ctx.error.message'), ref('ctx.error.type')))); + } + statements.push( + ifElse( + and([not(ref('ctx.result.items.isEmpty()')), equals(ref('ctx.result.scannedCount'), int(1))]), + toJson(ref('ctx.result.items[0]')), + compoundExpression([ + iff(and([ref('ctx.result.items.isEmpty()'), equals(ref('ctx.result.scannedCount'), int(1))]), ref('util.unauthorized()')), + toJson(nul()), + ]), + ), + ); + return printBlock('Get Response template')(compoundExpression(statements)); +}; + export const generateListRequestTemplate = (): string => { const requestVariable = 'ListRequest'; const modelQueryObj = 'ctx.stash.modelQueryExpression'; @@ -51,12 +110,20 @@ export const generateListRequestTemplate = (): string => { }), ), iff(ref('context.args.nextToken'), set(ref(`${requestVariable}.nextToken`), ref('context.args.nextToken'))), + ifElse( + not(isNullOrEmpty(authFilter)), + compoundExpression([ + set(ref('filter'), authFilter), + iff(not(isNullOrEmpty(ref('ctx.args.filter'))), set(ref('filter'), obj({ and: list([ref('filter'), ref('ctx.args.filter')]) }))), + ]), + iff(not(isNullOrEmpty(ref('ctx.args.filter'))), set(ref('filter'), ref('ctx.args.filter'))), + ), iff( - ref('context.args.filter'), + not(isNullOrEmpty(ref('filter'))), compoundExpression([ set( ref(`filterExpression`), - methodCall(ref('util.parseJson'), methodCall(ref('util.transform.toDynamoDBFilterExpression'), ref('ctx.args.filter'))), + methodCall(ref('util.parseJson'), methodCall(ref('util.transform.toDynamoDBFilterExpression'), ref('filter'))), ), iff( not(methodCall(ref('util.isNullOrBlank'), ref('filterExpression.expression'))), @@ -95,10 +162,37 @@ export const generateListRequestTemplate = (): string => { export const generateSyncRequestTemplate = (): string => { return printBlock('Sync Request template')( compoundExpression([ + ifElse( + not(isNullOrEmpty(authFilter)), + compoundExpression([ + set(ref('filter'), authFilter), + iff(not(isNullOrEmpty(ref('ctx.args.filter'))), set(ref('filter'), obj({ and: list([ref('filter'), ref('ctx.args.filter')]) }))), + ]), + iff(not(isNullOrEmpty(ref('ctx.args.filter'))), set(ref('filter'), ref('ctx.args.filter'))), + ), + iff( + not(isNullOrEmpty(ref('filter'))), + compoundExpression([ + set( + ref(`filterExpression`), + methodCall(ref('util.parseJson'), methodCall(ref('util.transform.toDynamoDBFilterExpression'), ref('filter'))), + ), + iff( + not(methodCall(ref('util.isNullOrBlank'), ref('filterExpression.expression'))), + compoundExpression([ + iff( + equals(methodCall(ref('filterEpression.expressionValues.size')), int(0)), + qref(methodCall(ref('filterEpression.remove'), str('expressionValues'))), + ), + set(ref('filter'), ref('filterExpression')), + ]), + ), + ]), + ), obj({ version: str('2018-05-29'), operation: str('Sync'), - filter: ifElse(ref('context.args.filter'), ref('util.transform.toDynamoDBFilterExpression($ctx.args.filter)'), nul()), + filter: ifElse(ref('filter'), ref('util.toJson($filter)'), nul()), limit: ref(`util.defaultIfNull($ctx.args.limit, ${ResourceConstants.DEFAULT_SYNC_QUERY_PAGE_LIMIT})`), lastSync: ref('util.toJson($util.defaultIfNull($ctx.args.lastSync, null))'), nextToken: ref('util.toJson($util.defaultIfNull($ctx.args.nextToken, null))'), diff --git a/packages/amplify-graphql-model-transformer/src/wrappers/object-definition-wrapper.ts b/packages/amplify-graphql-model-transformer/src/wrappers/object-definition-wrapper.ts index a0de181099a..9604f4c7eb3 100644 --- a/packages/amplify-graphql-model-transformer/src/wrappers/object-definition-wrapper.ts +++ b/packages/amplify-graphql-model-transformer/src/wrappers/object-definition-wrapper.ts @@ -295,7 +295,7 @@ export class FieldWrapper extends GenericFieldWrapper { field.makeNonNullable(); } if (isList) { - field.wrapListType(); + field.wrapListType().makeNonNullable(); } return field; }; diff --git a/packages/amplify-graphql-predictions-transformer/src/graphql-predictions-transformer.ts b/packages/amplify-graphql-predictions-transformer/src/graphql-predictions-transformer.ts index bd3b5eb2885..db57b7e0e75 100644 --- a/packages/amplify-graphql-predictions-transformer/src/graphql-predictions-transformer.ts +++ b/packages/amplify-graphql-predictions-transformer/src/graphql-predictions-transformer.ts @@ -1,5 +1,12 @@ import * as path from 'path'; -import { DirectiveWrapper, InvalidDirectiveError, MappingTemplate, TransformerPluginBase } from '@aws-amplify/graphql-transformer-core'; +import { + DirectiveWrapper, + IAM_AUTH_ROLE_PARAMETER, + IAM_UNAUTH_ROLE_PARAMETER, + InvalidDirectiveError, + MappingTemplate, + TransformerPluginBase, +} from '@aws-amplify/graphql-transformer-core'; import { TransformerContextProvider, TransformerSchemaVisitStepContextProvider, @@ -50,6 +57,7 @@ import { translateTextAmzTarget, PREDICTIONS_DIRECTIVE_STACK, } from './utils/constants'; +import { AuthorizationType } from '@aws-cdk/aws-appsync'; type PredictionsDirectiveConfiguration = { actions: string[] | undefined; @@ -91,7 +99,7 @@ export class PredictionsTransformer extends TransformerPluginBase { } as PredictionsDirectiveConfiguration); if (!Array.isArray(args.actions)) { - args.actions = [(args.actions as unknown) as string]; + args.actions = [args.actions as unknown as string]; } validateActions(args.actions); @@ -318,23 +326,50 @@ function createResolver( if (referencesEnv(bucketName)) { const env = context.stackManager.getParameter(ResourceConstants.PARAMETERS.Env) as cdk.CfnParameter; - substitutions.env = (env as unknown) as string; + substitutions.env = env as unknown as string; + } + const requestTemplate = [ + cdk.Fn.conditionIf( + ResourceConstants.CONDITIONS.HasEnvironmentParameter, + cdk.Fn.sub(`$util.qr($ctx.stash.put("s3Bucket", "${bucketName}"))`, substitutions), + cdk.Fn.sub(`$util.qr($ctx.stash.put("s3Bucket", "${removeEnvReference(bucketName)}"))`, { + hash: cdk.Fn.select(3, cdk.Fn.split('-', cdk.Fn.ref('AWS::StackName'))), + }), + ) as unknown as string, + print(compoundExpression([qref('$ctx.stash.put("isList", false)')])), + ]; + // TODO: predictions should use resolver manager + const authModes = [context.authConfig.defaultAuthentication, ...(context.authConfig.additionalAuthenticationProviders || [])].map( + mode => mode?.authenticationType, + ); + if (authModes.includes(AuthorizationType.IAM)) { + const authRoleParameter = (context.stackManager.getParameter(IAM_AUTH_ROLE_PARAMETER) as cdk.CfnParameter).valueAsString; + const unauthRoleParameter = (context.stackManager.getParameter(IAM_UNAUTH_ROLE_PARAMETER) as cdk.CfnParameter).valueAsString; + requestTemplate.push( + `$util.qr($ctx.stash.put("authRole", "arn:aws:sts::${ + cdk.Stack.of(context.stackManager.rootStack).account + }:assumed-role/${authRoleParameter}/CognitoIdentityCredentials"))`, + `$util.qr($ctx.stash.put("unauthRole", "arn:aws:sts::${ + cdk.Stack.of(context.stackManager.rootStack).account + }:assumed-role/${unauthRoleParameter}/CognitoIdentityCredentials"))`, + ); } + requestTemplate.push(print(obj({}))); return context.api.host.addResolver( config.resolverTypeName, config.resolverFieldName, MappingTemplate.inlineTemplateFromString( - (cdk.Fn.join('\n', [ - (cdk.Fn.conditionIf( + cdk.Fn.join('\n', [ + cdk.Fn.conditionIf( ResourceConstants.CONDITIONS.HasEnvironmentParameter, cdk.Fn.sub(`$util.qr($ctx.stash.put("s3Bucket", "${bucketName}"))`, substitutions), cdk.Fn.sub(`$util.qr($ctx.stash.put("s3Bucket", "${removeEnvReference(bucketName)}"))`, { hash: cdk.Fn.select(3, cdk.Fn.split('-', cdk.Fn.ref('AWS::StackName'))), }), - ) as unknown) as string, + ) as unknown as string, print(compoundExpression([qref('$ctx.stash.put("isList", false)'), obj({})])), - ]) as unknown) as string, + ]) as unknown as string, ), MappingTemplate.inlineTemplateFromString( print( @@ -398,11 +433,11 @@ function removeEnvReference(value: string): string { function joinWithEnv(context: TransformerContextProvider, separator: string, listToJoin: any[]): string { const env = context.stackManager.getParameter(ResourceConstants.PARAMETERS.Env) as cdk.CfnParameter; - return (cdk.Fn.conditionIf( + return cdk.Fn.conditionIf( ResourceConstants.CONDITIONS.HasEnvironmentParameter, cdk.Fn.join(separator, [...listToJoin, env]), cdk.Fn.join(separator, listToJoin), - ) as unknown) as string; + ) as unknown as string; } function needsList(action: string, isCurrentlyList: boolean): boolean { @@ -500,14 +535,14 @@ function getStorageArn(context: TransformerContextProvider, bucketName: string): if (referencesEnv(bucketName)) { const env = context.stackManager.getParameter(ResourceConstants.PARAMETERS.Env) as cdk.CfnParameter; - substitutions.env = (env as unknown) as string; + substitutions.env = env as unknown as string; } - return (cdk.Fn.conditionIf( + return cdk.Fn.conditionIf( ResourceConstants.CONDITIONS.HasEnvironmentParameter, cdk.Fn.sub(s3ArnKey(bucketName), substitutions), cdk.Fn.sub(s3ArnKey(removeEnvReference(bucketName)), { hash: cdk.Fn.select(3, cdk.Fn.split('-', cdk.Fn.ref('AWS::StackName'))) }), - ) as unknown) as string; + ) as unknown as string; } function createActionFunction(context: TransformerContextProvider, stack: cdk.Stack, action: string, datasourceName: string) { diff --git a/packages/amplify-graphql-relational-transformer/package.json b/packages/amplify-graphql-relational-transformer/package.json index 57c9fbbb2c9..8a74a1a8447 100644 --- a/packages/amplify-graphql-relational-transformer/package.json +++ b/packages/amplify-graphql-relational-transformer/package.json @@ -27,6 +27,7 @@ "test": "jest" }, "dependencies": { + "@aws-amplify/graphql-auth-transformer": "0.1.0", "@aws-amplify/graphql-index-transformer": "0.4.0", "@aws-amplify/graphql-model-transformer": "0.6.4", "@aws-amplify/graphql-transformer-core": "0.9.2", diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap index d06564b1739..6bfcb117372 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap @@ -3277,7 +3277,7 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 1964, + "end": 1966, "start": 1951, }, "name": Object { @@ -3289,24 +3289,38 @@ Object { "value": "items", }, "type": Object { - "kind": "ListType", + "kind": "NonNullType", "loc": Object { - "end": 1964, + "end": 1966, "start": 1958, }, "type": Object { - "kind": "NamedType", + "kind": "ListType", "loc": Object { - "end": 1963, - "start": 1959, + "end": 1965, + "start": 1958, }, - "name": Object { - "kind": "Name", + "type": Object { + "kind": "NonNullType", "loc": Object { - "end": 1963, + "end": 1964, "start": 1959, }, - "value": "Post", + "type": Object { + "kind": "NamedType", + "loc": Object { + "end": 1963, + "start": 1959, + }, + "name": Object { + "kind": "Name", + "loc": Object { + "end": 1963, + "start": 1959, + }, + "value": "Post", + }, + }, }, }, }, @@ -3317,28 +3331,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 1984, - "start": 1967, + "end": 1986, + "start": 1969, }, "name": Object { "kind": "Name", "loc": Object { - "end": 1976, - "start": 1967, + "end": 1978, + "start": 1969, }, "value": "nextToken", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 1984, - "start": 1978, + "end": 1986, + "start": 1980, }, "name": Object { "kind": "Name", "loc": Object { - "end": 1984, - "start": 1978, + "end": 1986, + "start": 1980, }, "value": "String", }, @@ -3348,7 +3362,7 @@ Object { "interfaces": Array [], "kind": "ObjectTypeDefinition", "loc": Object { - "end": 1986, + "end": 1988, "start": 1922, }, "name": Object { @@ -3370,28 +3384,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2035, - "start": 2019, + "end": 2037, + "start": 2021, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2021, - "start": 2019, + "end": 2023, + "start": 2021, }, "value": "id", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2035, - "start": 2023, + "end": 2037, + "start": 2025, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2035, - "start": 2023, + "end": 2037, + "start": 2025, }, "value": "ModelIDInput", }, @@ -3403,28 +3417,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2061, - "start": 2038, + "end": 2063, + "start": 2040, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2043, - "start": 2038, + "end": 2045, + "start": 2040, }, "value": "title", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2061, - "start": 2045, + "end": 2063, + "start": 2047, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2061, - "start": 2045, + "end": 2063, + "start": 2047, }, "value": "ModelStringInput", }, @@ -3436,34 +3450,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2091, - "start": 2064, + "end": 2093, + "start": 2066, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2067, - "start": 2064, + "end": 2069, + "start": 2066, }, "value": "and", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 2091, - "start": 2069, + "end": 2093, + "start": 2071, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2090, - "start": 2070, + "end": 2092, + "start": 2072, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2090, - "start": 2070, + "end": 2092, + "start": 2072, }, "value": "ModelPostFilterInput", }, @@ -3476,34 +3490,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2120, - "start": 2094, + "end": 2122, + "start": 2096, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2096, - "start": 2094, + "end": 2098, + "start": 2096, }, "value": "or", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 2120, - "start": 2098, + "end": 2122, + "start": 2100, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2119, - "start": 2099, + "end": 2121, + "start": 2101, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2119, - "start": 2099, + "end": 2121, + "start": 2101, }, "value": "ModelPostFilterInput", }, @@ -3516,28 +3530,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2148, - "start": 2123, + "end": 2150, + "start": 2125, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2126, - "start": 2123, + "end": 2128, + "start": 2125, }, "value": "not", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2148, - "start": 2128, + "end": 2150, + "start": 2130, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2148, - "start": 2128, + "end": 2150, + "start": 2130, }, "value": "ModelPostFilterInput", }, @@ -3546,14 +3560,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 2150, - "start": 1988, + "end": 2152, + "start": 1990, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2014, - "start": 1994, + "end": 2016, + "start": 1996, }, "value": "ModelPostFilterInput", }, @@ -3570,34 +3584,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2182, - "start": 2175, + "end": 2184, + "start": 2177, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2177, - "start": 2175, + "end": 2179, + "start": 2177, }, "value": "id", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 2182, - "start": 2179, + "end": 2184, + "start": 2181, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2181, - "start": 2179, + "end": 2183, + "start": 2181, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2181, - "start": 2179, + "end": 2183, + "start": 2181, }, "value": "ID", }, @@ -3609,28 +3623,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 2189, - "start": 2167, + "end": 2191, + "start": 2169, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2174, - "start": 2167, + "end": 2176, + "start": 2169, }, "value": "getPost", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2189, - "start": 2185, + "end": 2191, + "start": 2187, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2189, - "start": 2185, + "end": 2191, + "start": 2187, }, "value": "Post", }, @@ -3644,28 +3658,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2230, - "start": 2202, + "end": 2232, + "start": 2204, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2208, - "start": 2202, + "end": 2210, + "start": 2204, }, "value": "filter", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2230, - "start": 2210, + "end": 2232, + "start": 2212, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2230, - "start": 2210, + "end": 2232, + "start": 2212, }, "value": "ModelPostFilterInput", }, @@ -3677,28 +3691,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2242, - "start": 2232, + "end": 2244, + "start": 2234, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2237, - "start": 2232, + "end": 2239, + "start": 2234, }, "value": "limit", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2242, - "start": 2239, + "end": 2244, + "start": 2241, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2242, - "start": 2239, + "end": 2244, + "start": 2241, }, "value": "Int", }, @@ -3710,28 +3724,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2261, - "start": 2244, + "end": 2263, + "start": 2246, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2253, - "start": 2244, + "end": 2255, + "start": 2246, }, "value": "nextToken", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2261, - "start": 2255, + "end": 2263, + "start": 2257, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2261, - "start": 2255, + "end": 2263, + "start": 2257, }, "value": "String", }, @@ -3742,28 +3756,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 2283, - "start": 2192, + "end": 2285, + "start": 2194, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2201, - "start": 2192, + "end": 2203, + "start": 2194, }, "value": "listPosts", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2283, - "start": 2264, + "end": 2285, + "start": 2266, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2283, - "start": 2264, + "end": 2285, + "start": 2266, }, "value": "ModelPostConnection", }, @@ -3777,34 +3791,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2301, - "start": 2294, + "end": 2303, + "start": 2296, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2296, - "start": 2294, + "end": 2298, + "start": 2296, }, "value": "id", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 2301, - "start": 2298, + "end": 2303, + "start": 2300, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2300, - "start": 2298, + "end": 2302, + "start": 2300, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2300, - "start": 2298, + "end": 2302, + "start": 2300, }, "value": "ID", }, @@ -3816,28 +3830,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 2308, - "start": 2286, + "end": 2310, + "start": 2288, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2293, - "start": 2286, + "end": 2295, + "start": 2288, }, "value": "getUser", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2308, - "start": 2304, + "end": 2310, + "start": 2306, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2308, - "start": 2304, + "end": 2310, + "start": 2306, }, "value": "User", }, @@ -3851,28 +3865,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2349, - "start": 2321, + "end": 2351, + "start": 2323, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2327, - "start": 2321, + "end": 2329, + "start": 2323, }, "value": "filter", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2349, - "start": 2329, + "end": 2351, + "start": 2331, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2349, - "start": 2329, + "end": 2351, + "start": 2331, }, "value": "ModelUserFilterInput", }, @@ -3884,28 +3898,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2361, - "start": 2351, + "end": 2363, + "start": 2353, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2356, - "start": 2351, + "end": 2358, + "start": 2353, }, "value": "limit", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2361, - "start": 2358, + "end": 2363, + "start": 2360, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2361, - "start": 2358, + "end": 2363, + "start": 2360, }, "value": "Int", }, @@ -3917,28 +3931,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2380, - "start": 2363, + "end": 2382, + "start": 2365, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2372, - "start": 2363, + "end": 2374, + "start": 2365, }, "value": "nextToken", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2380, - "start": 2374, + "end": 2382, + "start": 2376, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2380, - "start": 2374, + "end": 2382, + "start": 2376, }, "value": "String", }, @@ -3949,28 +3963,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 2402, - "start": 2311, + "end": 2404, + "start": 2313, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2320, - "start": 2311, + "end": 2322, + "start": 2313, }, "value": "listUsers", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2402, - "start": 2383, + "end": 2404, + "start": 2385, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2402, - "start": 2383, + "end": 2404, + "start": 2385, }, "value": "ModelUserConnection", }, @@ -3980,14 +3994,14 @@ Object { "interfaces": Array [], "kind": "ObjectTypeDefinition", "loc": Object { - "end": 2404, - "start": 2152, + "end": 2406, + "start": 2154, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2162, - "start": 2157, + "end": 2164, + "start": 2159, }, "value": "Query", }, @@ -4002,28 +4016,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2463, - "start": 2440, + "end": 2465, + "start": 2442, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2445, - "start": 2440, + "end": 2447, + "start": 2442, }, "value": "title", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2463, - "start": 2447, + "end": 2465, + "start": 2449, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2463, - "start": 2447, + "end": 2465, + "start": 2449, }, "value": "ModelStringInput", }, @@ -4035,34 +4049,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2496, - "start": 2466, + "end": 2498, + "start": 2468, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2469, - "start": 2466, + "end": 2471, + "start": 2468, }, "value": "and", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 2496, - "start": 2471, + "end": 2498, + "start": 2473, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2495, - "start": 2472, + "end": 2497, + "start": 2474, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2495, - "start": 2472, + "end": 2497, + "start": 2474, }, "value": "ModelPostConditionInput", }, @@ -4075,34 +4089,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2528, - "start": 2499, + "end": 2530, + "start": 2501, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2501, - "start": 2499, + "end": 2503, + "start": 2501, }, "value": "or", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 2528, - "start": 2503, + "end": 2530, + "start": 2505, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2527, - "start": 2504, + "end": 2529, + "start": 2506, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2527, - "start": 2504, + "end": 2529, + "start": 2506, }, "value": "ModelPostConditionInput", }, @@ -4115,28 +4129,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2559, - "start": 2531, + "end": 2561, + "start": 2533, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2534, - "start": 2531, + "end": 2536, + "start": 2533, }, "value": "not", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2559, - "start": 2536, + "end": 2561, + "start": 2538, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2559, - "start": 2536, + "end": 2561, + "start": 2538, }, "value": "ModelPostConditionInput", }, @@ -4145,14 +4159,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 2561, - "start": 2406, + "end": 2563, + "start": 2408, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2435, - "start": 2412, + "end": 2437, + "start": 2414, }, "value": "ModelPostConditionInput", }, @@ -4167,28 +4181,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2595, - "start": 2589, + "end": 2597, + "start": 2591, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2591, - "start": 2589, + "end": 2593, + "start": 2591, }, "value": "id", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2595, - "start": 2593, + "end": 2597, + "start": 2595, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2595, - "start": 2593, + "end": 2597, + "start": 2595, }, "value": "ID", }, @@ -4200,34 +4214,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2612, - "start": 2598, + "end": 2614, + "start": 2600, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2603, - "start": 2598, + "end": 2605, + "start": 2600, }, "value": "title", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 2612, - "start": 2605, + "end": 2614, + "start": 2607, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2611, - "start": 2605, + "end": 2613, + "start": 2607, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2611, - "start": 2605, + "end": 2613, + "start": 2607, }, "value": "String", }, @@ -4237,14 +4251,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 2614, - "start": 2563, + "end": 2616, + "start": 2565, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2584, - "start": 2569, + "end": 2586, + "start": 2571, }, "value": "CreatePostInput", }, @@ -4259,34 +4273,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2649, - "start": 2642, + "end": 2651, + "start": 2644, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2644, - "start": 2642, + "end": 2646, + "start": 2644, }, "value": "id", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 2649, - "start": 2646, + "end": 2651, + "start": 2648, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2648, - "start": 2646, + "end": 2650, + "start": 2648, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2648, - "start": 2646, + "end": 2650, + "start": 2648, }, "value": "ID", }, @@ -4299,28 +4313,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2665, - "start": 2652, + "end": 2667, + "start": 2654, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2657, - "start": 2652, + "end": 2659, + "start": 2654, }, "value": "title", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2665, - "start": 2659, + "end": 2667, + "start": 2661, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2665, - "start": 2659, + "end": 2667, + "start": 2661, }, "value": "String", }, @@ -4329,14 +4343,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 2667, - "start": 2616, + "end": 2669, + "start": 2618, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2637, - "start": 2622, + "end": 2639, + "start": 2624, }, "value": "UpdatePostInput", }, @@ -4351,34 +4365,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2702, - "start": 2695, + "end": 2704, + "start": 2697, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2697, - "start": 2695, + "end": 2699, + "start": 2697, }, "value": "id", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 2702, - "start": 2699, + "end": 2704, + "start": 2701, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2701, - "start": 2699, + "end": 2703, + "start": 2701, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2701, - "start": 2699, + "end": 2703, + "start": 2701, }, "value": "ID", }, @@ -4388,14 +4402,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 2704, - "start": 2669, + "end": 2706, + "start": 2671, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2690, - "start": 2675, + "end": 2692, + "start": 2677, }, "value": "DeletePostInput", }, @@ -4412,34 +4426,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2758, - "start": 2735, + "end": 2760, + "start": 2737, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2740, - "start": 2735, + "end": 2742, + "start": 2737, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 2758, - "start": 2742, + "end": 2760, + "start": 2744, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2757, - "start": 2742, + "end": 2759, + "start": 2744, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2757, - "start": 2742, + "end": 2759, + "start": 2744, }, "value": "CreatePostInput", }, @@ -4452,28 +4466,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2794, - "start": 2760, + "end": 2796, + "start": 2762, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2769, - "start": 2760, + "end": 2771, + "start": 2762, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2794, - "start": 2771, + "end": 2796, + "start": 2773, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2794, - "start": 2771, + "end": 2796, + "start": 2773, }, "value": "ModelPostConditionInput", }, @@ -4484,28 +4498,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 2801, - "start": 2724, + "end": 2803, + "start": 2726, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2734, - "start": 2724, + "end": 2736, + "start": 2726, }, "value": "createPost", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2801, - "start": 2797, + "end": 2803, + "start": 2799, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2801, - "start": 2797, + "end": 2803, + "start": 2799, }, "value": "Post", }, @@ -4519,34 +4533,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2838, - "start": 2815, + "end": 2840, + "start": 2817, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2820, - "start": 2815, + "end": 2822, + "start": 2817, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 2838, - "start": 2822, + "end": 2840, + "start": 2824, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2837, - "start": 2822, + "end": 2839, + "start": 2824, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2837, - "start": 2822, + "end": 2839, + "start": 2824, }, "value": "UpdatePostInput", }, @@ -4559,28 +4573,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2874, - "start": 2840, + "end": 2876, + "start": 2842, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2849, - "start": 2840, + "end": 2851, + "start": 2842, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2874, - "start": 2851, + "end": 2876, + "start": 2853, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2874, - "start": 2851, + "end": 2876, + "start": 2853, }, "value": "ModelPostConditionInput", }, @@ -4591,28 +4605,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 2881, - "start": 2804, + "end": 2883, + "start": 2806, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2814, - "start": 2804, + "end": 2816, + "start": 2806, }, "value": "updatePost", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2881, - "start": 2877, + "end": 2883, + "start": 2879, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2881, - "start": 2877, + "end": 2883, + "start": 2879, }, "value": "Post", }, @@ -4626,34 +4640,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2918, - "start": 2895, + "end": 2920, + "start": 2897, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2900, - "start": 2895, + "end": 2902, + "start": 2897, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 2918, - "start": 2902, + "end": 2920, + "start": 2904, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2917, - "start": 2902, + "end": 2919, + "start": 2904, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2917, - "start": 2902, + "end": 2919, + "start": 2904, }, "value": "DeletePostInput", }, @@ -4666,28 +4680,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 2954, - "start": 2920, + "end": 2956, + "start": 2922, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2929, - "start": 2920, + "end": 2931, + "start": 2922, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2954, - "start": 2931, + "end": 2956, + "start": 2933, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2954, - "start": 2931, + "end": 2956, + "start": 2933, }, "value": "ModelPostConditionInput", }, @@ -4698,28 +4712,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 2961, - "start": 2884, + "end": 2963, + "start": 2886, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2894, - "start": 2884, + "end": 2896, + "start": 2886, }, "value": "deletePost", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 2961, - "start": 2957, + "end": 2963, + "start": 2959, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2961, - "start": 2957, + "end": 2963, + "start": 2959, }, "value": "Post", }, @@ -4733,34 +4747,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3010, - "start": 2981, + "end": 3012, + "start": 2983, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2986, - "start": 2981, + "end": 2988, + "start": 2983, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 3010, - "start": 2988, + "end": 3012, + "start": 2990, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3009, - "start": 2988, + "end": 3011, + "start": 2990, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3009, - "start": 2988, + "end": 3011, + "start": 2990, }, "value": "CreatePostEditorInput", }, @@ -4773,28 +4787,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3052, - "start": 3012, + "end": 3054, + "start": 3014, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3021, - "start": 3012, + "end": 3023, + "start": 3014, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3052, - "start": 3023, + "end": 3054, + "start": 3025, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3052, - "start": 3023, + "end": 3054, + "start": 3025, }, "value": "ModelPostEditorConditionInput", }, @@ -4805,28 +4819,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 3065, - "start": 2964, + "end": 3067, + "start": 2966, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2980, - "start": 2964, + "end": 2982, + "start": 2966, }, "value": "createPostEditor", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3065, - "start": 3055, + "end": 3067, + "start": 3057, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3065, - "start": 3055, + "end": 3067, + "start": 3057, }, "value": "PostEditor", }, @@ -4840,34 +4854,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3114, - "start": 3085, + "end": 3116, + "start": 3087, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3090, - "start": 3085, + "end": 3092, + "start": 3087, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 3114, - "start": 3092, + "end": 3116, + "start": 3094, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3113, - "start": 3092, + "end": 3115, + "start": 3094, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3113, - "start": 3092, + "end": 3115, + "start": 3094, }, "value": "UpdatePostEditorInput", }, @@ -4880,28 +4894,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3156, - "start": 3116, + "end": 3158, + "start": 3118, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3125, - "start": 3116, + "end": 3127, + "start": 3118, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3156, - "start": 3127, + "end": 3158, + "start": 3129, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3156, - "start": 3127, + "end": 3158, + "start": 3129, }, "value": "ModelPostEditorConditionInput", }, @@ -4912,28 +4926,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 3169, - "start": 3068, + "end": 3171, + "start": 3070, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3084, - "start": 3068, + "end": 3086, + "start": 3070, }, "value": "updatePostEditor", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3169, - "start": 3159, + "end": 3171, + "start": 3161, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3169, - "start": 3159, + "end": 3171, + "start": 3161, }, "value": "PostEditor", }, @@ -4947,34 +4961,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3218, - "start": 3189, + "end": 3220, + "start": 3191, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3194, - "start": 3189, + "end": 3196, + "start": 3191, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 3218, - "start": 3196, + "end": 3220, + "start": 3198, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3217, - "start": 3196, + "end": 3219, + "start": 3198, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3217, - "start": 3196, + "end": 3219, + "start": 3198, }, "value": "DeletePostEditorInput", }, @@ -4987,28 +5001,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3260, - "start": 3220, + "end": 3262, + "start": 3222, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3229, - "start": 3220, + "end": 3231, + "start": 3222, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3260, - "start": 3231, + "end": 3262, + "start": 3233, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3260, - "start": 3231, + "end": 3262, + "start": 3233, }, "value": "ModelPostEditorConditionInput", }, @@ -5019,28 +5033,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 3273, - "start": 3172, + "end": 3275, + "start": 3174, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3188, - "start": 3172, + "end": 3190, + "start": 3174, }, "value": "deletePostEditor", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3273, - "start": 3263, + "end": 3275, + "start": 3265, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3273, - "start": 3263, + "end": 3275, + "start": 3265, }, "value": "PostEditor", }, @@ -5054,34 +5068,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3310, - "start": 3287, + "end": 3312, + "start": 3289, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3292, - "start": 3287, + "end": 3294, + "start": 3289, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 3310, - "start": 3294, + "end": 3312, + "start": 3296, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3309, - "start": 3294, + "end": 3311, + "start": 3296, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3309, - "start": 3294, + "end": 3311, + "start": 3296, }, "value": "CreateUserInput", }, @@ -5094,28 +5108,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3346, - "start": 3312, + "end": 3348, + "start": 3314, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3321, - "start": 3312, + "end": 3323, + "start": 3314, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3346, - "start": 3323, + "end": 3348, + "start": 3325, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3346, - "start": 3323, + "end": 3348, + "start": 3325, }, "value": "ModelUserConditionInput", }, @@ -5126,28 +5140,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 3353, - "start": 3276, + "end": 3355, + "start": 3278, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3286, - "start": 3276, + "end": 3288, + "start": 3278, }, "value": "createUser", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3353, - "start": 3349, + "end": 3355, + "start": 3351, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3353, - "start": 3349, + "end": 3355, + "start": 3351, }, "value": "User", }, @@ -5161,34 +5175,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3390, - "start": 3367, + "end": 3392, + "start": 3369, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3372, - "start": 3367, + "end": 3374, + "start": 3369, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 3390, - "start": 3374, + "end": 3392, + "start": 3376, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3389, - "start": 3374, + "end": 3391, + "start": 3376, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3389, - "start": 3374, + "end": 3391, + "start": 3376, }, "value": "UpdateUserInput", }, @@ -5201,28 +5215,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3426, - "start": 3392, + "end": 3428, + "start": 3394, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3401, - "start": 3392, + "end": 3403, + "start": 3394, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3426, - "start": 3403, + "end": 3428, + "start": 3405, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3426, - "start": 3403, + "end": 3428, + "start": 3405, }, "value": "ModelUserConditionInput", }, @@ -5233,28 +5247,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 3433, - "start": 3356, + "end": 3435, + "start": 3358, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3366, - "start": 3356, + "end": 3368, + "start": 3358, }, "value": "updateUser", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3433, - "start": 3429, + "end": 3435, + "start": 3431, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3433, - "start": 3429, + "end": 3435, + "start": 3431, }, "value": "User", }, @@ -5268,34 +5282,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3470, - "start": 3447, + "end": 3472, + "start": 3449, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3452, - "start": 3447, + "end": 3454, + "start": 3449, }, "value": "input", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 3470, - "start": 3454, + "end": 3472, + "start": 3456, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3469, - "start": 3454, + "end": 3471, + "start": 3456, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3469, - "start": 3454, + "end": 3471, + "start": 3456, }, "value": "DeleteUserInput", }, @@ -5308,28 +5322,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 3506, - "start": 3472, + "end": 3508, + "start": 3474, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3481, - "start": 3472, + "end": 3483, + "start": 3474, }, "value": "condition", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3506, - "start": 3483, + "end": 3508, + "start": 3485, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3506, - "start": 3483, + "end": 3508, + "start": 3485, }, "value": "ModelUserConditionInput", }, @@ -5340,28 +5354,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 3513, - "start": 3436, + "end": 3515, + "start": 3438, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3446, - "start": 3436, + "end": 3448, + "start": 3438, }, "value": "deleteUser", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3513, - "start": 3509, + "end": 3515, + "start": 3511, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3513, - "start": 3509, + "end": 3515, + "start": 3511, }, "value": "User", }, @@ -5371,14 +5385,14 @@ Object { "interfaces": Array [], "kind": "ObjectTypeDefinition", "loc": Object { - "end": 3515, - "start": 2706, + "end": 3517, + "start": 2708, }, "name": Object { "kind": "Name", "loc": Object { - "end": 2719, - "start": 2711, + "end": 2721, + "start": 2713, }, "value": "Mutation", }, @@ -5396,30 +5410,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 3598, - "start": 3573, + "end": 3600, + "start": 3575, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3582, - "start": 3573, + "end": 3584, + "start": 3575, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 3598, - "start": 3584, + "end": 3600, + "start": 3586, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 3597, - "start": 3585, + "end": 3599, + "start": 3587, }, "value": "createPost", }, @@ -5429,14 +5443,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 3599, - "start": 3558, + "end": 3601, + "start": 3560, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3572, - "start": 3559, + "end": 3574, + "start": 3561, }, "value": "aws_subscribe", }, @@ -5444,28 +5458,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 3599, - "start": 3539, + "end": 3601, + "start": 3541, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3551, - "start": 3539, + "end": 3553, + "start": 3541, }, "value": "onCreatePost", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3557, - "start": 3553, + "end": 3559, + "start": 3555, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3557, - "start": 3553, + "end": 3559, + "start": 3555, }, "value": "Post", }, @@ -5480,30 +5494,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 3661, - "start": 3636, + "end": 3663, + "start": 3638, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3645, - "start": 3636, + "end": 3647, + "start": 3638, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 3661, - "start": 3647, + "end": 3663, + "start": 3649, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 3660, - "start": 3648, + "end": 3662, + "start": 3650, }, "value": "updatePost", }, @@ -5513,14 +5527,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 3662, - "start": 3621, + "end": 3664, + "start": 3623, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3635, - "start": 3622, + "end": 3637, + "start": 3624, }, "value": "aws_subscribe", }, @@ -5528,28 +5542,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 3662, - "start": 3602, + "end": 3664, + "start": 3604, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3614, - "start": 3602, + "end": 3616, + "start": 3604, }, "value": "onUpdatePost", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3620, - "start": 3616, + "end": 3622, + "start": 3618, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3620, - "start": 3616, + "end": 3622, + "start": 3618, }, "value": "Post", }, @@ -5564,30 +5578,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 3724, - "start": 3699, + "end": 3726, + "start": 3701, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3708, - "start": 3699, + "end": 3710, + "start": 3701, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 3724, - "start": 3710, + "end": 3726, + "start": 3712, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 3723, - "start": 3711, + "end": 3725, + "start": 3713, }, "value": "deletePost", }, @@ -5597,14 +5611,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 3725, - "start": 3684, + "end": 3727, + "start": 3686, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3698, - "start": 3685, + "end": 3700, + "start": 3687, }, "value": "aws_subscribe", }, @@ -5612,28 +5626,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 3725, - "start": 3665, + "end": 3727, + "start": 3667, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3677, - "start": 3665, + "end": 3679, + "start": 3667, }, "value": "onDeletePost", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3683, - "start": 3679, + "end": 3685, + "start": 3681, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3683, - "start": 3679, + "end": 3685, + "start": 3681, }, "value": "Post", }, @@ -5648,30 +5662,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 3805, - "start": 3774, + "end": 3807, + "start": 3776, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3783, - "start": 3774, + "end": 3785, + "start": 3776, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 3805, - "start": 3785, + "end": 3807, + "start": 3787, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 3804, - "start": 3786, + "end": 3806, + "start": 3788, }, "value": "createPostEditor", }, @@ -5681,14 +5695,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 3806, - "start": 3759, + "end": 3808, + "start": 3761, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3773, - "start": 3760, + "end": 3775, + "start": 3762, }, "value": "aws_subscribe", }, @@ -5696,28 +5710,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 3806, - "start": 3728, + "end": 3808, + "start": 3730, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3746, - "start": 3728, + "end": 3748, + "start": 3730, }, "value": "onCreatePostEditor", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3758, - "start": 3748, + "end": 3760, + "start": 3750, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3758, - "start": 3748, + "end": 3760, + "start": 3750, }, "value": "PostEditor", }, @@ -5732,30 +5746,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 3886, - "start": 3855, + "end": 3888, + "start": 3857, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3864, - "start": 3855, + "end": 3866, + "start": 3857, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 3886, - "start": 3866, + "end": 3888, + "start": 3868, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 3885, - "start": 3867, + "end": 3887, + "start": 3869, }, "value": "updatePostEditor", }, @@ -5765,14 +5779,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 3887, - "start": 3840, + "end": 3889, + "start": 3842, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3854, - "start": 3841, + "end": 3856, + "start": 3843, }, "value": "aws_subscribe", }, @@ -5780,28 +5794,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 3887, - "start": 3809, + "end": 3889, + "start": 3811, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3827, - "start": 3809, + "end": 3829, + "start": 3811, }, "value": "onUpdatePostEditor", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3839, - "start": 3829, + "end": 3841, + "start": 3831, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3839, - "start": 3829, + "end": 3841, + "start": 3831, }, "value": "PostEditor", }, @@ -5816,30 +5830,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 3967, - "start": 3936, + "end": 3969, + "start": 3938, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3945, - "start": 3936, + "end": 3947, + "start": 3938, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 3967, - "start": 3947, + "end": 3969, + "start": 3949, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 3966, - "start": 3948, + "end": 3968, + "start": 3950, }, "value": "deletePostEditor", }, @@ -5849,14 +5863,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 3968, - "start": 3921, + "end": 3970, + "start": 3923, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3935, - "start": 3922, + "end": 3937, + "start": 3924, }, "value": "aws_subscribe", }, @@ -5864,28 +5878,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 3968, - "start": 3890, + "end": 3970, + "start": 3892, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3908, - "start": 3890, + "end": 3910, + "start": 3892, }, "value": "onDeletePostEditor", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3920, - "start": 3910, + "end": 3922, + "start": 3912, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3920, - "start": 3910, + "end": 3922, + "start": 3912, }, "value": "PostEditor", }, @@ -5900,30 +5914,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 4030, - "start": 4005, + "end": 4032, + "start": 4007, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4014, - "start": 4005, + "end": 4016, + "start": 4007, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 4030, - "start": 4016, + "end": 4032, + "start": 4018, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 4029, - "start": 4017, + "end": 4031, + "start": 4019, }, "value": "createUser", }, @@ -5933,14 +5947,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 4031, - "start": 3990, + "end": 4033, + "start": 3992, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4004, - "start": 3991, + "end": 4006, + "start": 3993, }, "value": "aws_subscribe", }, @@ -5948,28 +5962,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 4031, - "start": 3971, + "end": 4033, + "start": 3973, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3983, - "start": 3971, + "end": 3985, + "start": 3973, }, "value": "onCreateUser", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 3989, - "start": 3985, + "end": 3991, + "start": 3987, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3989, - "start": 3985, + "end": 3991, + "start": 3987, }, "value": "User", }, @@ -5984,30 +5998,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 4093, - "start": 4068, + "end": 4095, + "start": 4070, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4077, - "start": 4068, + "end": 4079, + "start": 4070, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 4093, - "start": 4079, + "end": 4095, + "start": 4081, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 4092, - "start": 4080, + "end": 4094, + "start": 4082, }, "value": "updateUser", }, @@ -6017,14 +6031,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 4094, - "start": 4053, + "end": 4096, + "start": 4055, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4067, - "start": 4054, + "end": 4069, + "start": 4056, }, "value": "aws_subscribe", }, @@ -6032,28 +6046,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 4094, - "start": 4034, + "end": 4096, + "start": 4036, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4046, - "start": 4034, + "end": 4048, + "start": 4036, }, "value": "onUpdateUser", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4052, - "start": 4048, + "end": 4054, + "start": 4050, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4052, - "start": 4048, + "end": 4054, + "start": 4050, }, "value": "User", }, @@ -6068,30 +6082,30 @@ Object { Object { "kind": "Argument", "loc": Object { - "end": 4156, - "start": 4131, + "end": 4158, + "start": 4133, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4140, - "start": 4131, + "end": 4142, + "start": 4133, }, "value": "mutations", }, "value": Object { "kind": "ListValue", "loc": Object { - "end": 4156, - "start": 4142, + "end": 4158, + "start": 4144, }, "values": Array [ Object { "block": false, "kind": "StringValue", "loc": Object { - "end": 4155, - "start": 4143, + "end": 4157, + "start": 4145, }, "value": "deleteUser", }, @@ -6101,14 +6115,14 @@ Object { ], "kind": "Directive", "loc": Object { - "end": 4157, - "start": 4116, + "end": 4159, + "start": 4118, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4130, - "start": 4117, + "end": 4132, + "start": 4119, }, "value": "aws_subscribe", }, @@ -6116,28 +6130,28 @@ Object { ], "kind": "FieldDefinition", "loc": Object { - "end": 4157, - "start": 4097, + "end": 4159, + "start": 4099, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4109, - "start": 4097, + "end": 4111, + "start": 4099, }, "value": "onDeleteUser", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4115, - "start": 4111, + "end": 4117, + "start": 4113, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4115, - "start": 4111, + "end": 4117, + "start": 4113, }, "value": "User", }, @@ -6147,14 +6161,14 @@ Object { "interfaces": Array [], "kind": "ObjectTypeDefinition", "loc": Object { - "end": 4159, - "start": 3517, + "end": 4161, + "start": 3519, }, "name": Object { "kind": "Name", "loc": Object { - "end": 3534, - "start": 3522, + "end": 3536, + "start": 3524, }, "value": "Subscription", }, @@ -6169,28 +6183,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4221, - "start": 4201, + "end": 4223, + "start": 4203, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4207, - "start": 4201, + "end": 4209, + "start": 4203, }, "value": "postID", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4221, - "start": 4209, + "end": 4223, + "start": 4211, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4221, - "start": 4209, + "end": 4223, + "start": 4211, }, "value": "ModelIDInput", }, @@ -6202,28 +6216,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4246, - "start": 4224, + "end": 4248, + "start": 4226, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4232, - "start": 4224, + "end": 4234, + "start": 4226, }, "value": "editorID", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4246, - "start": 4234, + "end": 4248, + "start": 4236, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4246, - "start": 4234, + "end": 4248, + "start": 4236, }, "value": "ModelIDInput", }, @@ -6235,34 +6249,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4285, - "start": 4249, + "end": 4287, + "start": 4251, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4252, - "start": 4249, + "end": 4254, + "start": 4251, }, "value": "and", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 4285, - "start": 4254, + "end": 4287, + "start": 4256, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4284, - "start": 4255, + "end": 4286, + "start": 4257, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4284, - "start": 4255, + "end": 4286, + "start": 4257, }, "value": "ModelPostEditorConditionInput", }, @@ -6275,34 +6289,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4323, - "start": 4288, + "end": 4325, + "start": 4290, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4290, - "start": 4288, + "end": 4292, + "start": 4290, }, "value": "or", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 4323, - "start": 4292, + "end": 4325, + "start": 4294, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4322, - "start": 4293, + "end": 4324, + "start": 4295, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4322, - "start": 4293, + "end": 4324, + "start": 4295, }, "value": "ModelPostEditorConditionInput", }, @@ -6315,28 +6329,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4360, - "start": 4326, + "end": 4362, + "start": 4328, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4329, - "start": 4326, + "end": 4331, + "start": 4328, }, "value": "not", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4360, - "start": 4331, + "end": 4362, + "start": 4333, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4360, - "start": 4331, + "end": 4362, + "start": 4333, }, "value": "ModelPostEditorConditionInput", }, @@ -6345,14 +6359,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 4362, - "start": 4161, + "end": 4364, + "start": 4163, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4196, - "start": 4167, + "end": 4198, + "start": 4169, }, "value": "ModelPostEditorConditionInput", }, @@ -6367,28 +6381,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4402, - "start": 4396, + "end": 4404, + "start": 4398, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4398, - "start": 4396, + "end": 4400, + "start": 4398, }, "value": "id", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4402, - "start": 4400, + "end": 4404, + "start": 4402, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4402, - "start": 4400, + "end": 4404, + "start": 4402, }, "value": "ID", }, @@ -6400,34 +6414,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4416, - "start": 4405, + "end": 4418, + "start": 4407, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4411, - "start": 4405, + "end": 4413, + "start": 4407, }, "value": "postID", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 4416, - "start": 4413, + "end": 4418, + "start": 4415, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4415, - "start": 4413, + "end": 4417, + "start": 4415, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4415, - "start": 4413, + "end": 4417, + "start": 4415, }, "value": "ID", }, @@ -6440,34 +6454,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4432, - "start": 4419, + "end": 4434, + "start": 4421, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4427, - "start": 4419, + "end": 4429, + "start": 4421, }, "value": "editorID", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 4432, - "start": 4429, + "end": 4434, + "start": 4431, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4431, - "start": 4429, + "end": 4433, + "start": 4431, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4431, - "start": 4429, + "end": 4433, + "start": 4431, }, "value": "ID", }, @@ -6477,14 +6491,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 4434, - "start": 4364, + "end": 4436, + "start": 4366, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4391, - "start": 4370, + "end": 4393, + "start": 4372, }, "value": "CreatePostEditorInput", }, @@ -6499,34 +6513,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4475, - "start": 4468, + "end": 4477, + "start": 4470, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4470, - "start": 4468, + "end": 4472, + "start": 4470, }, "value": "id", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 4475, - "start": 4472, + "end": 4477, + "start": 4474, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4474, - "start": 4472, + "end": 4476, + "start": 4474, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4474, - "start": 4472, + "end": 4476, + "start": 4474, }, "value": "ID", }, @@ -6539,28 +6553,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4488, - "start": 4478, + "end": 4490, + "start": 4480, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4484, - "start": 4478, + "end": 4486, + "start": 4480, }, "value": "postID", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4488, - "start": 4486, + "end": 4490, + "start": 4488, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4488, - "start": 4486, + "end": 4490, + "start": 4488, }, "value": "ID", }, @@ -6572,28 +6586,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4503, - "start": 4491, + "end": 4505, + "start": 4493, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4499, - "start": 4491, + "end": 4501, + "start": 4493, }, "value": "editorID", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4503, - "start": 4501, + "end": 4505, + "start": 4503, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4503, - "start": 4501, + "end": 4505, + "start": 4503, }, "value": "ID", }, @@ -6602,14 +6616,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 4505, - "start": 4436, + "end": 4507, + "start": 4438, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4463, - "start": 4442, + "end": 4465, + "start": 4444, }, "value": "UpdatePostEditorInput", }, @@ -6624,34 +6638,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4546, - "start": 4539, + "end": 4548, + "start": 4541, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4541, - "start": 4539, + "end": 4543, + "start": 4541, }, "value": "id", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 4546, - "start": 4543, + "end": 4548, + "start": 4545, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4545, - "start": 4543, + "end": 4547, + "start": 4545, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4545, - "start": 4543, + "end": 4547, + "start": 4545, }, "value": "ID", }, @@ -6661,14 +6675,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 4548, - "start": 4507, + "end": 4550, + "start": 4509, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4534, - "start": 4513, + "end": 4536, + "start": 4515, }, "value": "DeletePostEditorInput", }, @@ -6683,36 +6697,50 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 4592, - "start": 4579, + "end": 4596, + "start": 4581, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4584, - "start": 4579, + "end": 4586, + "start": 4581, }, "value": "items", }, "type": Object { - "kind": "ListType", + "kind": "NonNullType", "loc": Object { - "end": 4592, - "start": 4586, + "end": 4596, + "start": 4588, }, "type": Object { - "kind": "NamedType", + "kind": "ListType", "loc": Object { - "end": 4591, - "start": 4587, + "end": 4595, + "start": 4588, }, - "name": Object { - "kind": "Name", + "type": Object { + "kind": "NonNullType", "loc": Object { - "end": 4591, - "start": 4587, + "end": 4594, + "start": 4589, + }, + "type": Object { + "kind": "NamedType", + "loc": Object { + "end": 4593, + "start": 4589, + }, + "name": Object { + "kind": "Name", + "loc": Object { + "end": 4593, + "start": 4589, + }, + "value": "User", + }, }, - "value": "User", }, }, }, @@ -6723,28 +6751,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 4612, - "start": 4595, + "end": 4616, + "start": 4599, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4604, - "start": 4595, + "end": 4608, + "start": 4599, }, "value": "nextToken", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4612, - "start": 4606, + "end": 4616, + "start": 4610, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4612, - "start": 4606, + "end": 4616, + "start": 4610, }, "value": "String", }, @@ -6754,14 +6782,14 @@ Object { "interfaces": Array [], "kind": "ObjectTypeDefinition", "loc": Object { - "end": 4614, - "start": 4550, + "end": 4618, + "start": 4552, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4574, - "start": 4555, + "end": 4576, + "start": 4557, }, "value": "ModelUserConnection", }, @@ -6776,28 +6804,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4663, - "start": 4647, + "end": 4667, + "start": 4651, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4649, - "start": 4647, + "end": 4653, + "start": 4651, }, "value": "id", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4663, - "start": 4651, + "end": 4667, + "start": 4655, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4663, - "start": 4651, + "end": 4667, + "start": 4655, }, "value": "ModelIDInput", }, @@ -6809,28 +6837,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4692, - "start": 4666, + "end": 4696, + "start": 4670, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4674, - "start": 4666, + "end": 4678, + "start": 4670, }, "value": "username", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4692, - "start": 4676, + "end": 4696, + "start": 4680, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4692, - "start": 4676, + "end": 4696, + "start": 4680, }, "value": "ModelStringInput", }, @@ -6842,34 +6870,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4722, - "start": 4695, + "end": 4726, + "start": 4699, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4698, - "start": 4695, + "end": 4702, + "start": 4699, }, "value": "and", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 4722, - "start": 4700, + "end": 4726, + "start": 4704, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4721, - "start": 4701, + "end": 4725, + "start": 4705, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4721, - "start": 4701, + "end": 4725, + "start": 4705, }, "value": "ModelUserFilterInput", }, @@ -6882,34 +6910,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4751, - "start": 4725, + "end": 4755, + "start": 4729, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4727, - "start": 4725, + "end": 4731, + "start": 4729, }, "value": "or", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 4751, - "start": 4729, + "end": 4755, + "start": 4733, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4750, - "start": 4730, + "end": 4754, + "start": 4734, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4750, - "start": 4730, + "end": 4754, + "start": 4734, }, "value": "ModelUserFilterInput", }, @@ -6922,28 +6950,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4779, - "start": 4754, + "end": 4783, + "start": 4758, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4757, - "start": 4754, + "end": 4761, + "start": 4758, }, "value": "not", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4779, - "start": 4759, + "end": 4783, + "start": 4763, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4779, - "start": 4759, + "end": 4783, + "start": 4763, }, "value": "ModelUserFilterInput", }, @@ -6952,14 +6980,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 4781, - "start": 4616, + "end": 4785, + "start": 4620, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4642, - "start": 4622, + "end": 4646, + "start": 4626, }, "value": "ModelUserFilterInput", }, @@ -6974,28 +7002,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4843, - "start": 4817, + "end": 4847, + "start": 4821, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4825, - "start": 4817, + "end": 4829, + "start": 4821, }, "value": "username", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4843, - "start": 4827, + "end": 4847, + "start": 4831, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4843, - "start": 4827, + "end": 4847, + "start": 4831, }, "value": "ModelStringInput", }, @@ -7007,34 +7035,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4876, - "start": 4846, + "end": 4880, + "start": 4850, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4849, - "start": 4846, + "end": 4853, + "start": 4850, }, "value": "and", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 4876, - "start": 4851, + "end": 4880, + "start": 4855, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4875, - "start": 4852, + "end": 4879, + "start": 4856, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4875, - "start": 4852, + "end": 4879, + "start": 4856, }, "value": "ModelUserConditionInput", }, @@ -7047,34 +7075,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4908, - "start": 4879, + "end": 4912, + "start": 4883, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4881, - "start": 4879, + "end": 4885, + "start": 4883, }, "value": "or", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 4908, - "start": 4883, + "end": 4912, + "start": 4887, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4907, - "start": 4884, + "end": 4911, + "start": 4888, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4907, - "start": 4884, + "end": 4911, + "start": 4888, }, "value": "ModelUserConditionInput", }, @@ -7087,28 +7115,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4939, - "start": 4911, + "end": 4943, + "start": 4915, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4914, - "start": 4911, + "end": 4918, + "start": 4915, }, "value": "not", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4939, - "start": 4916, + "end": 4943, + "start": 4920, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4939, - "start": 4916, + "end": 4943, + "start": 4920, }, "value": "ModelUserConditionInput", }, @@ -7117,14 +7145,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 4941, - "start": 4783, + "end": 4945, + "start": 4787, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4812, - "start": 4789, + "end": 4816, + "start": 4793, }, "value": "ModelUserConditionInput", }, @@ -7139,28 +7167,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4975, - "start": 4969, + "end": 4979, + "start": 4973, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4971, - "start": 4969, + "end": 4975, + "start": 4973, }, "value": "id", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4975, - "start": 4973, + "end": 4979, + "start": 4977, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4975, - "start": 4973, + "end": 4979, + "start": 4977, }, "value": "ID", }, @@ -7172,34 +7200,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 4995, - "start": 4978, + "end": 4999, + "start": 4982, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4986, - "start": 4978, + "end": 4990, + "start": 4982, }, "value": "username", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 4995, - "start": 4988, + "end": 4999, + "start": 4992, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 4994, - "start": 4988, + "end": 4998, + "start": 4992, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4994, - "start": 4988, + "end": 4998, + "start": 4992, }, "value": "String", }, @@ -7209,14 +7237,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 4997, - "start": 4943, + "end": 5001, + "start": 4947, }, "name": Object { "kind": "Name", "loc": Object { - "end": 4964, - "start": 4949, + "end": 4968, + "start": 4953, }, "value": "CreateUserInput", }, @@ -7231,34 +7259,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5032, - "start": 5025, + "end": 5036, + "start": 5029, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5027, - "start": 5025, + "end": 5031, + "start": 5029, }, "value": "id", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 5032, - "start": 5029, + "end": 5036, + "start": 5033, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5031, - "start": 5029, + "end": 5035, + "start": 5033, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5031, - "start": 5029, + "end": 5035, + "start": 5033, }, "value": "ID", }, @@ -7271,28 +7299,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5051, - "start": 5035, + "end": 5055, + "start": 5039, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5043, - "start": 5035, + "end": 5047, + "start": 5039, }, "value": "username", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5051, - "start": 5045, + "end": 5055, + "start": 5049, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5051, - "start": 5045, + "end": 5055, + "start": 5049, }, "value": "String", }, @@ -7301,14 +7329,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 5053, - "start": 4999, + "end": 5057, + "start": 5003, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5020, - "start": 5005, + "end": 5024, + "start": 5009, }, "value": "UpdateUserInput", }, @@ -7323,34 +7351,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5088, - "start": 5081, + "end": 5092, + "start": 5085, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5083, - "start": 5081, + "end": 5087, + "start": 5085, }, "value": "id", }, "type": Object { "kind": "NonNullType", "loc": Object { - "end": 5088, - "start": 5085, + "end": 5092, + "start": 5089, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5087, - "start": 5085, + "end": 5091, + "start": 5089, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5087, - "start": 5085, + "end": 5091, + "start": 5089, }, "value": "ID", }, @@ -7360,14 +7388,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 5090, - "start": 5055, + "end": 5094, + "start": 5059, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5076, - "start": 5061, + "end": 5080, + "start": 5065, }, "value": "DeleteUserInput", }, @@ -7382,28 +7410,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5133, - "start": 5127, + "end": 5137, + "start": 5131, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5129, - "start": 5127, + "end": 5133, + "start": 5131, }, "value": "eq", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5133, - "start": 5131, + "end": 5137, + "start": 5135, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5133, - "start": 5131, + "end": 5137, + "start": 5135, }, "value": "ID", }, @@ -7415,28 +7443,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5142, - "start": 5136, + "end": 5146, + "start": 5140, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5138, - "start": 5136, + "end": 5142, + "start": 5140, }, "value": "le", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5142, - "start": 5140, + "end": 5146, + "start": 5144, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5142, - "start": 5140, + "end": 5146, + "start": 5144, }, "value": "ID", }, @@ -7448,28 +7476,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5151, - "start": 5145, + "end": 5155, + "start": 5149, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5147, - "start": 5145, + "end": 5151, + "start": 5149, }, "value": "lt", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5151, - "start": 5149, + "end": 5155, + "start": 5153, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5151, - "start": 5149, + "end": 5155, + "start": 5153, }, "value": "ID", }, @@ -7481,28 +7509,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5160, - "start": 5154, + "end": 5164, + "start": 5158, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5156, - "start": 5154, + "end": 5160, + "start": 5158, }, "value": "ge", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5160, - "start": 5158, + "end": 5164, + "start": 5162, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5160, - "start": 5158, + "end": 5164, + "start": 5162, }, "value": "ID", }, @@ -7514,28 +7542,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5169, - "start": 5163, + "end": 5173, + "start": 5167, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5165, - "start": 5163, + "end": 5169, + "start": 5167, }, "value": "gt", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5169, - "start": 5167, + "end": 5173, + "start": 5171, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5169, - "start": 5167, + "end": 5173, + "start": 5171, }, "value": "ID", }, @@ -7547,34 +7575,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5185, - "start": 5172, + "end": 5189, + "start": 5176, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5179, - "start": 5172, + "end": 5183, + "start": 5176, }, "value": "between", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 5185, - "start": 5181, + "end": 5189, + "start": 5185, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5184, - "start": 5182, + "end": 5188, + "start": 5186, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5184, - "start": 5182, + "end": 5188, + "start": 5186, }, "value": "ID", }, @@ -7587,28 +7615,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5202, - "start": 5188, + "end": 5206, + "start": 5192, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5198, - "start": 5188, + "end": 5202, + "start": 5192, }, "value": "beginsWith", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5202, - "start": 5200, + "end": 5206, + "start": 5204, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5202, - "start": 5200, + "end": 5206, + "start": 5204, }, "value": "ID", }, @@ -7617,14 +7645,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 5204, - "start": 5092, + "end": 5208, + "start": 5096, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5122, - "start": 5098, + "end": 5126, + "start": 5102, }, "value": "ModelIDKeyConditionInput", }, @@ -7639,36 +7667,50 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 5275, - "start": 5241, + "end": 5266, + "start": 5245, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5246, - "start": 5241, + "end": 5250, + "start": 5245, }, "value": "items", }, "type": Object { - "kind": "ListType", + "kind": "NonNullType", "loc": Object { - "end": 5275, - "start": 5248, + "end": 5266, + "start": 5252, }, "type": Object { - "kind": "NamedType", + "kind": "ListType", "loc": Object { - "end": 5274, - "start": 5249, + "end": 5265, + "start": 5252, }, - "name": Object { - "kind": "Name", + "type": Object { + "kind": "NonNullType", "loc": Object { - "end": 5274, - "start": 5249, + "end": 5264, + "start": 5253, + }, + "type": Object { + "kind": "NamedType", + "loc": Object { + "end": 5263, + "start": 5253, + }, + "name": Object { + "kind": "Name", + "loc": Object { + "end": 5263, + "start": 5253, + }, + "value": "PostEditor", + }, }, - "value": "ModelPostEditorConnection", }, }, }, @@ -7679,28 +7721,28 @@ Object { "directives": Array [], "kind": "FieldDefinition", "loc": Object { - "end": 5295, - "start": 5278, + "end": 5286, + "start": 5269, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5287, - "start": 5278, + "end": 5278, + "start": 5269, }, "value": "nextToken", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5295, - "start": 5289, + "end": 5286, + "start": 5280, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5295, - "start": 5289, + "end": 5286, + "start": 5280, }, "value": "String", }, @@ -7710,14 +7752,14 @@ Object { "interfaces": Array [], "kind": "ObjectTypeDefinition", "loc": Object { - "end": 5297, - "start": 5206, + "end": 5288, + "start": 5210, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5236, - "start": 5211, + "end": 5240, + "start": 5215, }, "value": "ModelPostEditorConnection", }, @@ -7732,28 +7774,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5352, - "start": 5336, + "end": 5343, + "start": 5327, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5338, - "start": 5336, + "end": 5329, + "start": 5327, }, "value": "id", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5352, - "start": 5340, + "end": 5343, + "start": 5331, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5352, - "start": 5340, + "end": 5343, + "start": 5331, }, "value": "ModelIDInput", }, @@ -7765,28 +7807,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5375, - "start": 5355, + "end": 5366, + "start": 5346, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5361, - "start": 5355, + "end": 5352, + "start": 5346, }, "value": "postID", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5375, - "start": 5363, + "end": 5366, + "start": 5354, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5375, - "start": 5363, + "end": 5366, + "start": 5354, }, "value": "ModelIDInput", }, @@ -7798,28 +7840,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5400, - "start": 5378, + "end": 5391, + "start": 5369, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5386, - "start": 5378, + "end": 5377, + "start": 5369, }, "value": "editorID", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5400, - "start": 5388, + "end": 5391, + "start": 5379, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5400, - "start": 5388, + "end": 5391, + "start": 5379, }, "value": "ModelIDInput", }, @@ -7831,34 +7873,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5436, - "start": 5403, + "end": 5427, + "start": 5394, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5406, - "start": 5403, + "end": 5397, + "start": 5394, }, "value": "and", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 5436, - "start": 5408, + "end": 5427, + "start": 5399, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5435, - "start": 5409, + "end": 5426, + "start": 5400, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5435, - "start": 5409, + "end": 5426, + "start": 5400, }, "value": "ModelPostEditorFilterInput", }, @@ -7871,34 +7913,34 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5471, - "start": 5439, + "end": 5462, + "start": 5430, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5441, - "start": 5439, + "end": 5432, + "start": 5430, }, "value": "or", }, "type": Object { "kind": "ListType", "loc": Object { - "end": 5471, - "start": 5443, + "end": 5462, + "start": 5434, }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5470, - "start": 5444, + "end": 5461, + "start": 5435, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5470, - "start": 5444, + "end": 5461, + "start": 5435, }, "value": "ModelPostEditorFilterInput", }, @@ -7911,28 +7953,28 @@ Object { "directives": Array [], "kind": "InputValueDefinition", "loc": Object { - "end": 5505, - "start": 5474, + "end": 5496, + "start": 5465, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5477, - "start": 5474, + "end": 5468, + "start": 5465, }, "value": "not", }, "type": Object { "kind": "NamedType", "loc": Object { - "end": 5505, - "start": 5479, + "end": 5496, + "start": 5470, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5505, - "start": 5479, + "end": 5496, + "start": 5470, }, "value": "ModelPostEditorFilterInput", }, @@ -7941,14 +7983,14 @@ Object { ], "kind": "InputObjectTypeDefinition", "loc": Object { - "end": 5507, - "start": 5299, + "end": 5498, + "start": 5290, }, "name": Object { "kind": "Name", "loc": Object { - "end": 5331, - "start": 5305, + "end": 5322, + "start": 5296, }, "value": "ModelPostEditorFilterInput", }, @@ -7956,7 +7998,7 @@ Object { ], "kind": "Document", "loc": Object { - "end": 5509, + "end": 5500, "start": 0, }, } @@ -7964,7 +8006,10 @@ Object { exports[`validates VTL of a complex schema 1`] = ` Object { - "Child.parents.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "Child.parents.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -8018,6 +8063,27 @@ Object { $util.qr($query.expressionValues.put(\\":sortKey\\", { \\"S\\": \\"$ctx.args.childName.ge\\" })) #end ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -8031,8 +8097,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -8053,7 +8119,10 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "Friendship.friend.req.vtl": "#if( $util.isNull($ctx.source.friendID) ) + "Friendship.friend.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.friendID) ) #set( $result = { \\"items\\": [] } ) @@ -8130,6 +8199,27 @@ $util.error($ctx.error.message, $ctx.error.type) ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -8143,8 +8233,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -8175,7 +8265,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createChild.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createChild.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createChild.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -8188,25 +8284,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createChild.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8271,13 +8348,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Child\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createChild.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createChild.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createComment.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8289,26 +8367,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createComment.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createComment.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8373,13 +8438,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Comment\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createFriendship.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8391,26 +8457,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createFriendship.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createFriendship.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createFriendship.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8475,13 +8528,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Friendship\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createFriendship.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createFriendship.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createParent.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8493,26 +8547,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createParent.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createParent.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createParent.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8577,13 +8618,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Parent\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createParent.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createParent.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createPost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8595,7 +8637,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createPost.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createPost.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -8607,25 +8655,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { {}", "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8690,13 +8719,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Post\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createPostAuthor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8708,26 +8738,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createPostAuthor.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createPostAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createPostAuthor.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8792,13 +8809,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"PostAuthor\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPostAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPostAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createPostEditor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8810,26 +8828,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createPostEditor.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createPostEditor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createPostEditor.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8894,13 +8899,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"PostEditor\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPostEditor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPostEditor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createPostModel.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -8912,26 +8918,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.createPostModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.createPostModel.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -8996,13 +8989,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"PostModel\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPostModel.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPostModel.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -9014,26 +9008,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -9098,13 +9079,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createTest1.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -9116,7 +9098,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createTest1.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createTest1.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createTest1.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -9136,25 +9124,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { $util.qr($ctx.args.input.put(\\"email#name\\",\\"\${mergedValues.email}#\${mergedValues.name}\\")) {}", "Mutation.createTest1.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -9219,13 +9188,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Test1\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createTest1.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createTest1.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createUser.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -9237,7 +9207,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createUser.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createUser.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -9257,25 +9233,6 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { $util.qr($ctx.args.input.put(\\"name#surname\\",\\"\${mergedValues.name}#\${mergedValues.surname}\\")) {}", "Mutation.createUser.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -9340,13 +9297,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"User\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createUserModel.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -9358,7 +9316,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createUserModel.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createUserModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createUserModel.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -9370,7 +9334,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { ## [End] Set the primary key. ** {}", - "Mutation.createUserModel.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.createUserModel.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -9398,25 +9362,6 @@ $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) #end {}", "Mutation.createUserModel.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) -#end -## End - key condition ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -9481,14 +9426,21 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"UserModel\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createUserModel.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createUserModel.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteChild.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteChild.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteChild.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -9556,13 +9508,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteChild.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteChild.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteComment.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -9620,13 +9579,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteFriendship.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteFriendship.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -9684,13 +9650,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteFriendship.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteFriendship.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteParent.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteParent.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -9748,14 +9721,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteParent.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteParent.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deletePost.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deletePost.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -9822,13 +9802,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deletePostAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deletePostAuthor.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -9886,13 +9873,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePostAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePostAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deletePostEditor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deletePostEditor.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -9950,13 +9944,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePostEditor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePostEditor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deletePostModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deletePostModel.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -10014,13 +10015,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePostModel.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePostModel.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteTest.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -10078,14 +10086,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteTest1.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteTest1.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteTest1.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -10153,14 +10168,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteTest1.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteTest1.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteUser.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteUser.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -10228,14 +10250,21 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Mutation.deleteUserModel.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** +## [End] ResponseTemplate. **", + "Mutation.deleteUserModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.deleteUserModel.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -10246,7 +10275,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { })) ## [End] Set the primary key. ** {}", - "Mutation.deleteUserModel.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.deleteUserModel.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -10316,13 +10345,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteUserModel.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteUserModel.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateChild.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -10332,7 +10362,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateChild.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateChild.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateChild.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -10473,13 +10509,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateChild.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateChild.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateComment.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -10489,6 +10526,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateComment.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -10618,13 +10661,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateComment.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateFriendship.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -10634,6 +10678,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateFriendship.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateFriendship.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -10763,13 +10813,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateFriendship.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateFriendship.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateParent.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -10779,6 +10830,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateParent.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateParent.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -10908,13 +10965,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateParent.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateParent.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -10924,7 +10982,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updatePost.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updatePost.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -11064,13 +11128,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePostAuthor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -11080,6 +11145,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updatePostAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updatePostAuthor.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -11209,13 +11280,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePostAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePostAuthor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePostEditor.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -11225,6 +11297,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updatePostEditor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updatePostEditor.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -11354,13 +11432,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePostEditor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePostEditor.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePostModel.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -11370,6 +11449,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updatePostModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updatePostModel.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -11499,13 +11584,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePostModel.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePostModel.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -11515,6 +11601,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateTest.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -11644,13 +11736,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateTest1.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -11660,7 +11753,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateTest1.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateTest1.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateTest1.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -11808,13 +11907,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateTest1.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateTest1.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateUser.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -11824,7 +11924,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateUser.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateUser.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -11972,13 +12078,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateUser.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateUserModel.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -11988,7 +12095,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.updateUserModel.postAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateUserModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.updateUserModel.preAuth.1.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -12000,7 +12113,7 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { ## [End] Set the primary key. ** {}", - "Mutation.updateUserModel.postAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** + "Mutation.updateUserModel.preAuth.2.req.vtl": "## [Start] Merge default values and inputs. ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) $util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) ## [End] Merge default values and inputs. ** @@ -12156,47 +12269,92 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateUserModel.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateUserModel.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Parent.child.req.vtl": "#if( $util.isNull($ctx.source.childID) || $util.isNull($ctx.source.childName) ) +## [End] ResponseTemplate. **", + "Parent.child.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.childID) || $util.isNull($ctx.source.childName) ) #return #else -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\", - \\"key\\": { - \\"id\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.childID, \\"___xamznone____\\")), - \\"name\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.childName, \\"___xamznone____\\")) - } + #set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"Query\\" +} ) + $util.qr($GetRequest.put(\\"query\\", { + \\"expression\\": \\"#partitionKey = :partitionValue AND #sortKeyName = :sortKeyName\\", + \\"expressionNames\\": { + \\"#partitionKey\\": \\"id\\", + \\"#sortKeyName\\": \\"name\\" + }, + \\"expressionValues\\": { + \\":partitionValue\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.childID, \\"___xamznone____\\"))), + \\":sortKeyName\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.childName, \\"___xamznone____\\"))) } +})) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) + #end + $util.toJson($GetRequest) #end", "Parent.child.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else -$util.toJson($ctx.result) + #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) + #else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) + #end #end", - "Post.author.req.vtl": "#if( $util.isNull($ctx.source.owner) ) + "Post.author.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.owner) ) #return #else -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\", - \\"key\\": { - \\"id\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.owner, \\"___xamznone____\\")) - } + #set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"Query\\" +} ) + $util.qr($GetRequest.put(\\"query\\", { + \\"expression\\": \\"#partitionKey = :partitionValue\\", + \\"expressionNames\\": { + \\"#partitionKey\\": \\"id\\" + }, + \\"expressionValues\\": { + \\":partitionValue\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.owner, \\"___xamznone____\\"))) } +})) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) + #end + $util.toJson($GetRequest) #end", "Post.author.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else -$util.toJson($ctx.result) + #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) + #else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) + #end #end", - "Post.authors.req.vtl": "#if( $util.isNull($ctx.source.authorID) ) + "Post.authors.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.authorID) ) #set( $result = { \\"items\\": [] } ) @@ -12273,6 +12431,27 @@ $util.toJson($ctx.result) ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -12286,8 +12465,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -12307,7 +12486,10 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "Post.comments.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "Post.comments.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -12323,6 +12505,27 @@ $util.error($ctx.error.message, $ctx.error.type) \\":partitionKey\\": $util.dynamodb.toDynamoDB($context.source.id) } } ) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -12336,8 +12539,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -12358,7 +12561,10 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "Post.editors.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "Post.editors.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -12412,6 +12618,27 @@ $util.error($ctx.error.message, $ctx.error.type) $util.qr($query.expressionValues.put(\\":sortKey\\", { \\"S\\": \\"$ctx.args.editorID.ge\\" })) #end ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -12425,8 +12652,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -12447,55 +12674,118 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "PostAuthor.post.req.vtl": "#if( $util.isNull($ctx.source.postID) ) + "PostAuthor.post.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.postID) ) #return #else -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\", - \\"key\\": { - \\"id\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.postID, \\"___xamznone____\\")) - } + #set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"Query\\" +} ) + $util.qr($GetRequest.put(\\"query\\", { + \\"expression\\": \\"#partitionKey = :partitionValue\\", + \\"expressionNames\\": { + \\"#partitionKey\\": \\"id\\" + }, + \\"expressionValues\\": { + \\":partitionValue\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.postID, \\"___xamznone____\\"))) } +})) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) + #end + $util.toJson($GetRequest) #end", "PostAuthor.post.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else -$util.toJson($ctx.result) + #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) + #else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) + #end #end", - "PostEditor.editor.req.vtl": "#if( $util.isNull($ctx.source.editorID) ) + "PostEditor.editor.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.editorID) ) #return #else -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\", - \\"key\\": { - \\"id\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.editorID, \\"___xamznone____\\")) - } + #set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"Query\\" +} ) + $util.qr($GetRequest.put(\\"query\\", { + \\"expression\\": \\"#partitionKey = :partitionValue\\", + \\"expressionNames\\": { + \\"#partitionKey\\": \\"id\\" + }, + \\"expressionValues\\": { + \\":partitionValue\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.editorID, \\"___xamznone____\\"))) } +})) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) + #end + $util.toJson($GetRequest) #end", "PostEditor.editor.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else -$util.toJson($ctx.result) + #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) + #else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) + #end #end", - "PostEditor.post.req.vtl": "#if( $util.isNull($ctx.source.postID) ) + "PostEditor.post.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.postID) ) #return #else -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\", - \\"key\\": { - \\"id\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.postID, \\"___xamznone____\\")) - } + #set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"Query\\" +} ) + $util.qr($GetRequest.put(\\"query\\", { + \\"expression\\": \\"#partitionKey = :partitionValue\\", + \\"expressionNames\\": { + \\"#partitionKey\\": \\"id\\" + }, + \\"expressionValues\\": { + \\":partitionValue\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.postID, \\"___xamznone____\\"))) } +})) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) + #end + $util.toJson($GetRequest) #end", "PostEditor.post.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else -$util.toJson($ctx.result) + #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) + #else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) + #end #end", - "PostModel.authors.req.vtl": "#if( $util.isNull($ctx.source.authorID) ) + "PostModel.authors.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.authorID) ) #set( $result = { \\"items\\": [] } ) @@ -12513,6 +12803,27 @@ $util.toJson($ctx.result) \\":sortKey\\": $util.dynamodb.toDynamoDB(\\"\${context.source.authorName}#\${context.source.authorSurname}\\") } } ) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -12526,8 +12837,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -12548,24 +12859,51 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "PostModel.singleAuthor.req.vtl": "#if( $util.isNull($ctx.source.authorID) || $util.isNull($ctx.source.authorName) || $util.isNull($ctx.source.authorSurname) ) + "PostModel.singleAuthor.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.authorID) || $util.isNull($ctx.source.authorName) || $util.isNull($ctx.source.authorSurname) ) #return #else -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\", - \\"key\\": { - \\"id\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.authorID, \\"___xamznone____\\")), - \\"name#surname\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank(\\"\${ctx.source.authorName}#\${ctx.source.authorSurname}\\", \\"___xamznone____\\")) - } + #set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"Query\\" +} ) + $util.qr($GetRequest.put(\\"query\\", { + \\"expression\\": \\"#partitionKey = :partitionValue AND #sortKeyName = :sortKeyName\\", + \\"expressionNames\\": { + \\"#partitionKey\\": \\"id\\", + \\"#sortKeyName\\": \\"name#surname\\" + }, + \\"expressionValues\\": { + \\":partitionValue\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.authorID, \\"___xamznone____\\"))), + \\":sortKeyName\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank(\\"\${ctx.source.authorName}#\${ctx.source.authorSurname}\\", \\"___xamznone____\\"))) } +})) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) + #end + $util.toJson($GetRequest) #end", "PostModel.singleAuthor.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else -$util.toJson($ctx.result) + #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) + #else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) + #end #end", - "Query.getChild.postAuth.1.req.vtl": "## [Start] Set the primary key. ** + "Query.getChild.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getChild.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id), \\"name\\": $util.dynamodb.toDynamoDB($ctx.args.name) @@ -12575,92 +12913,216 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getChild.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getChild.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getChild.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getComment.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getComment.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getComment.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) -#else - $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) +#else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getFriendship.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getFriendship.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getFriendship.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getFriendship.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getParent.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getParent.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getParent.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getParent.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.getPost.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getPost.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"title\\": $util.dynamodb.toDynamoDB($ctx.args.title) })) @@ -12669,92 +13131,216 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getPost.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getPost.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getPostAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getPostAuthor.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getPostAuthor.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getPostAuthor.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getPostModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getPostModel.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getPostModel.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getPostModel.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getTest.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getTest1.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.getTest1.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getTest1.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id), \\"email#name\\": $util.dynamodb.toDynamoDB(\\"\${ctx.args.email}#\${ctx.args.name}\\") @@ -12764,26 +13350,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getTest1.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getTest1.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getTest1.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.getUser.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getUser.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id), \\"name#surname\\": $util.dynamodb.toDynamoDB(\\"\${ctx.args.name}#\${ctx.args.surname}\\") @@ -12793,26 +13410,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getUser.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getUser.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getUser.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.getUserModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", - "Query.getUserModel.postAuth.1.req.vtl": "## [Start] Set the primary key. ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.getUserModel.preAuth.1.req.vtl": "## [Start] Set the primary key. ** $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id), \\"rollNumber\\": $util.dynamodb.toDynamoDB($ctx.args.rollNumber) @@ -12822,26 +13470,57 @@ $util.qr($ctx.stash.metadata.put(\\"modelObjectKey\\", { "Query.getUserModel.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getUserModel.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getUserModel.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", - "Query.listChildren.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] Get Response template. **", + "Query.listChildren.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listChildren.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.id) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'id'.\\", \\"InvalidArgumentsError\\") #end @@ -12912,8 +13591,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -12937,13 +13628,19 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listChildren.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listChildren.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listComments.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listComments.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -12953,8 +13650,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -12978,13 +13687,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listComments.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listComments.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listFriendships.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listFriendships.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -12994,8 +13709,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13019,13 +13746,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listFriendships.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listFriendships.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listParents.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listParents.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -13035,8 +13768,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13060,13 +13805,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listParents.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listParents.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listPostAuthors.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listPostAuthors.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -13076,8 +13827,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13101,13 +13864,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listPostAuthors.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listPostAuthors.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listPostModels.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listPostModels.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -13117,8 +13886,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13142,14 +13923,20 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listPostModels.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listPostModels.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.listPosts.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] ResponseTemplate. **", + "Query.listPosts.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listPosts.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"sortDirection is not supported for List operations without a Sort key defined.\\", \\"InvalidArgumentsError\\") #end @@ -13177,8 +13964,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13202,14 +14001,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listPosts.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listPosts.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.listTest1s.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] ResponseTemplate. **", + "Query.listTest1s.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listTest1s.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.id) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'id'.\\", \\"InvalidArgumentsError\\") #end @@ -13326,8 +14131,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13351,13 +14168,19 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTest1s.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTest1s.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listTests.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listTests.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -13367,8 +14190,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13392,14 +14227,20 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listTests.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listTests.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.listUserModels.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] ResponseTemplate. **", + "Query.listUserModels.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listUserModels.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.id) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'id'.\\", \\"InvalidArgumentsError\\") #end @@ -13470,8 +14311,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13495,14 +14348,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listUserModels.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listUserModels.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Query.listUsers.postAuth.1.req.vtl": "## [Start] Set query expression for key ** +## [End] ResponseTemplate. **", + "Query.listUsers.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Query.listUsers.preAuth.1.req.vtl": "## [Start] Set query expression for key ** #if( $util.isNull($ctx.args.id) && !$util.isNull($ctx.args.sortDirection) ) $util.error(\\"When providing argument 'sortDirection' you must also provide argument 'id'.\\", \\"InvalidArgumentsError\\") #end @@ -13619,8 +14478,20 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -13644,14 +14515,557 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listUsers.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listUsers.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", - "Test.otherParts.req.vtl": "#if( $util.isNull($ctx.source.id) ) +## [End] ResponseTemplate. **", + "Subscription.onCreateChild.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateChild.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateChild.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateFriendship.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateFriendship.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateFriendship.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateParent.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateParent.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateParent.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreatePostAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePostAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePostAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreatePostEditor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePostEditor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePostEditor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreatePostModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePostModel.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePostModel.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateTest1.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateTest1.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateTest1.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateUserModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateUserModel.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateUserModel.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteChild.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteChild.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteChild.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteFriendship.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteFriendship.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteFriendship.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteParent.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteParent.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteParent.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePostAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePostAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePostAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePostEditor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePostEditor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePostEditor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePostModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePostModel.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePostModel.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteTest1.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteTest1.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteTest1.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteUserModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteUserModel.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteUserModel.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateChild.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateChild.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateChild.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateComment.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateComment.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateComment.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateFriendship.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateFriendship.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateFriendship.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateParent.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateParent.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateParent.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePostAuthor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePostAuthor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePostAuthor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePostEditor.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePostEditor.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePostEditor.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePostModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePostModel.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePostModel.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateTest1.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateTest1.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateTest1.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateUser.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateUser.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateUser.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateUserModel.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateUserModel.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateUserModel.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Test.otherParts.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -13728,6 +15142,27 @@ $util.toJson($ListRequest) ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -13741,8 +15176,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -13762,7 +15197,10 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "Test.testObj.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "Test.testObj.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -13839,6 +15277,27 @@ $util.error($ctx.error.message, $ctx.error.type) ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -13852,8 +15311,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -13874,7 +15333,10 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "User.friendships.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "User.friendships.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -13928,6 +15390,27 @@ $util.error($ctx.error.message, $ctx.error.type) $util.qr($query.expressionValues.put(\\":sortKey\\", { \\"S\\": \\"$ctx.args.friendID.ge\\" })) #end ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -13941,8 +15424,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -13963,7 +15446,10 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "User.posts.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "User.posts.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -14017,6 +15503,27 @@ $util.error($ctx.error.message, $ctx.error.type) $util.qr($query.expressionValues.put(\\":sortKey\\", { \\"S\\": \\"$ctx.args.postID.ge\\" })) #end ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -14030,8 +15537,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -14052,7 +15559,10 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "UserModel.authorPosts.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "UserModel.authorPosts.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -14106,6 +15616,27 @@ $util.error($ctx.error.message, $ctx.error.type) $util.qr($query.expressionValues.put(\\":sortKey\\", { \\"S\\": \\"$ctx.args.postID.ge\\" })) #end ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -14119,8 +15650,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-many-to-many-transformer.test.ts.snap b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-many-to-many-transformer.test.ts.snap index 3e58971d8e4..1ec7f89b791 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-many-to-many-transformer.test.ts.snap +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-many-to-many-transformer.test.ts.snap @@ -1,5 +1,445 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`join table inherits auth from both tables 1`] = ` +"## [Start] Authorization Steps. ** +$util.qr($ctx.stash.put(\\"hasAuth\\", true)) +#set( $isAuthorized = false ) +#set( $primaryFieldMap = {} ) +#if( $util.authType() == \\"API Key Authorization\\" ) + #set( $isAuthorized = true ) +#end +#if( $util.authType() == \\"IAM Authorization\\" ) + #if( !$isAuthorized ) + #if( $ctx.identity.userArn == $ctx.stash.unauthRole ) + #set( $isAuthorized = true ) + #end + #end +#end +#if( !$isAuthorized && $util.isNull($ctx.stash.authFilter) && $primaryFieldMap.isEmpty() ) +$util.unauthorized() +#end +$util.toJson({\\"version\\":\\"2018-05-29\\",\\"payload\\":{}}) +## [End] Authorization Steps. **" +`; + +exports[`join table inherits auth from both tables 2`] = ` +"## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **" +`; + +exports[`join table inherits auth from both tables 3`] = ` +"## [Start] Get Response template. ** +#if( $ctx.error ) + $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) +#else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **" +`; + +exports[`join table inherits auth from both tables 4`] = ` +"## [Start] Authorization Steps. ** +$util.qr($ctx.stash.put(\\"hasAuth\\", true)) +#set( $isAuthorized = false ) +#set( $primaryFieldMap = {} ) +#if( $util.authType() == \\"API Key Authorization\\" ) + #set( $isAuthorized = true ) +#end +#if( $util.authType() == \\"IAM Authorization\\" ) + #if( !$isAuthorized ) + #if( $ctx.identity.userArn == $ctx.stash.unauthRole ) + #set( $isAuthorized = true ) + #end + #end +#end +#if( !$isAuthorized && $util.isNull($ctx.stash.authFilter) && $primaryFieldMap.isEmpty() ) +$util.unauthorized() +#end +$util.toJson({\\"version\\":\\"2018-05-29\\",\\"payload\\":{}}) +## [End] Authorization Steps. **" +`; + +exports[`join table inherits auth from both tables 5`] = ` +"## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **" +`; + +exports[`join table inherits auth from both tables 6`] = ` +"## [Start] Authorization Steps. ** +$util.qr($ctx.stash.put(\\"hasAuth\\", true)) +#set( $inputFields = $util.parseJson($util.toJson($ctx.args.input.keySet())) ) +#set( $isAuthorized = false ) +#set( $allowedFields = [] ) +#if( $util.authType() == \\"API Key Authorization\\" ) + #set( $isAuthorized = true ) +#end +#if( $util.authType() == \\"IAM Authorization\\" ) + #if( $ctx.identity.userArn == $ctx.stash.unauthRole ) + #set( $isAuthorized = true ) + #end +#end +#if( !$isAuthorized && $allowedFields.isEmpty() ) +$util.unauthorized() +#end +#if( !$isAuthorized ) + #set( $deniedFields = $util.list.copyAndRemoveAll($inputFields, $allowedFields) ) + #if( $deniedFields.size() > 0 ) + $util.error(\\"Unauthorized on \${deniedFields}\\", \\"Unauthorized\\") + #end +#end +$util.toJson({\\"version\\":\\"2018-05-29\\",\\"payload\\":{}}) +## [End] Authorization Steps. **" +`; + +exports[`join table inherits auth from both tables 7`] = ` +"## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **" +`; + +exports[`join table inherits auth from both tables 8`] = ` +"## [Start] Get Request template. ** +#set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"GetItem\\" +} ) +#if( $ctx.stash.metadata.modelObjectKey ) + #set( $key = $ctx.stash.metadata.modelObjectKey ) +#else + #set( $key = { + \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.input.id) +} ) +#end +$util.qr($GetRequest.put(\\"key\\", $key)) +$util.toJson($GetRequest) +## [End] Get Request template. **" +`; + +exports[`join table inherits auth from both tables 9`] = ` +"## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **" +`; + +exports[`join table inherits auth from both tables 10`] = ` +"## [Start] Authorization Steps. ** +$util.qr($ctx.stash.put(\\"hasAuth\\", true)) +#set( $isAuthorized = false ) +#if( $util.authType() == \\"API Key Authorization\\" ) + #set( $isAuthorized = true ) +#end +#if( $util.authType() == \\"IAM Authorization\\" ) + #if( $ctx.identity.userArn == $ctx.stash.unauthRole ) + #set( $isAuthorized = true ) + #end +#end +#if( !$isAuthorized ) +$util.unauthorized() +#end +$util.toJson({\\"version\\":\\"2018-05-29\\",\\"payload\\":{}}) +## [End] Authorization Steps. **" +`; + +exports[`join table inherits auth from both tables 11`] = ` +"## [Start] Delete Request template. ** +#set( $DeleteRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"DeleteItem\\" +} ) +#if( $ctx.stash.metadata.modelObjectKey ) + #set( $Key = $ctx.stash.metadata.modelObjectKey ) +#else + #set( $Key = { + \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.input.id) +} ) +#end +$util.qr($DeleteRequest.put(\\"key\\", $Key)) +## Begin - key condition ** +#if( $ctx.stash.metadata.modelObjectKey ) + #set( $keyConditionExpr = {} ) + #set( $keyConditionExprNames = {} ) + #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) + $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { + \\"attributeExists\\": true +})) + $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) + #end + $util.qr($ctx.stash.conditions.add($keyConditionExpr)) +#else + $util.qr($ctx.stash.conditions.add({ + \\"id\\": { + \\"attributeExists\\": true + } +})) +#end +## End - key condition ** +#if( $context.args.condition ) + $util.qr($ctx.stash.conditions.add($context.args.condition)) +#end +## Start condition block ** +#if( $ctx.stash.conditions && $ctx.stash.conditions.size() != 0 ) + #set( $mergedConditions = { + \\"and\\": $ctx.stash.conditions +} ) + #set( $Conditions = $util.parseJson($util.transform.toDynamoDBConditionExpression($mergedConditions)) ) + #if( $Conditions.expressionValues && $Conditions.expressionValues.size() == 0 ) + #set( $Conditions = { + \\"expression\\": $Conditions.expression, + \\"expressionNames\\": $Conditions.expressionNames +} ) + #end + ## End condition block ** +#end +#if( $Conditions ) + #if( $keyConditionExprNames ) + $util.qr($Conditions.expressionNames.putAll($keyConditionExprNames)) + #end + $util.qr($DeleteRequest.put(\\"condition\\", $Conditions)) +#end +$util.toJson($DeleteRequest) +## [End] Delete Request template. **" +`; + +exports[`join table inherits auth from both tables 12`] = ` +"## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) +#if( $ctx.error ) + $util.error($ctx.error.message, $ctx.error.type) +#else + $util.toJson($ctx.result) +#end +## [End] ResponseTemplate. **" +`; + +exports[`join table inherits auth from both tables 13`] = ` +"## [Start] Get Request template. ** +#set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"GetItem\\" +} ) +#if( $ctx.stash.metadata.modelObjectKey ) + #set( $key = $ctx.stash.metadata.modelObjectKey ) +#else + #set( $key = { + \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.input.id) +} ) +#end +$util.qr($GetRequest.put(\\"key\\", $key)) +$util.toJson($GetRequest) +## [End] Get Request template. **" +`; + +exports[`join table inherits auth from both tables 14`] = ` +"## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **" +`; + +exports[`join table inherits auth from both tables 15`] = ` +"## [Start] Authorization Steps. ** +$util.qr($ctx.stash.put(\\"hasAuth\\", true)) +#if( $ctx.error ) + $util.error($ctx.error.message, $ctx.error.type) +#end +#set( $inputFields = $util.parseJson($util.toJson($ctx.args.input.keySet())) ) +#set( $isAuthorized = false ) +#set( $allowedFields = [] ) +#set( $nullAllowedFields = [] ) +#set( $deniedFields = {} ) +#if( $util.authType() == \\"API Key Authorization\\" ) + #set( $isAuthorized = true ) +#end +#if( $util.authType() == \\"IAM Authorization\\" ) + #if( $ctx.identity.userArn == $ctx.stash.unauthRole ) + #set( $isAuthorized = true ) + #end +#end +#if( !$isAuthorized && $allowedFields.isEmpty() && $nullAllowedFields.isEmpty() ) +$util.unauthorized() +#end +#if( !$isAuthorized ) + #foreach( $entry in $util.map.copyAndRetainAllKeys($ctx.args.input, $inputFields).entrySet() ) + #if( $util.isNull($entry.value) && !$nullAllowedFields.contains($entry.key) ) + $util.qr($deniedFields.put($entry.key, \\"\\")) + #end + #end + #foreach( $deniedField in $util.list.copyAndRemoveAll($inputFields, $allowedFields) ) + $util.qr($deniedFields.put($deniedField, \\"\\")) + #end +#end +#if( $deniedFields.keySet().size() > 0 ) + $util.error(\\"Unauthorized on \${deniedFields.keySet()}\\", \\"Unauthorized\\") +#end +$util.toJson({}) +## [End] Authorization Steps. **" +`; + +exports[`join table inherits auth from both tables 16`] = ` +"## [Start] Mutation Update resolver. ** +## Set the default values to put request ** +#set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) +## copy the values from input ** +$util.qr($mergedValues.putAll($util.defaultIfNull($ctx.args.input, {}))) +## set the typename ** +## Initialize the vars for creating ddb expression ** +#set( $expNames = {} ) +#set( $expValues = {} ) +#set( $expSet = {} ) +#set( $expAdd = {} ) +#set( $expRemove = [] ) +#if( $ctx.stash.metadata.modelObjectKey ) + #set( $Key = $ctx.stash.metadata.modelObjectKey ) +#else + #set( $Key = { + \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.input.id) +} ) +#end +## Model key ** +#if( $ctx.stash.metadata.modelObjectKey ) + #set( $keyFields = [] ) + #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) + $util.qr($keyFields.add(\\"$entry.key\\")) + #end +#else + #set( $keyFields = [\\"id\\"] ) +#end +#foreach( $entry in $util.map.copyAndRemoveAllKeys($mergedValues, $keyFields).entrySet() ) + #if( !$util.isNull($ctx.stash.metadata.dynamodbNameOverrideMap) && $ctx.stash.metadata.dynamodbNameOverrideMap.containsKey(\\"$entry.key\\") ) + #set( $entryKeyAttributeName = $ctx.stash.metadata.dynamodbNameOverrideMap.get(\\"$entry.key\\") ) + #else + #set( $entryKeyAttributeName = $entry.key ) + #end + #if( $util.isNull($entry.value) ) + #set( $discard = $expRemove.add(\\"#$entryKeyAttributeName\\") ) + $util.qr($expNames.put(\\"#$entryKeyAttributeName\\", \\"$entry.key\\")) + #else + $util.qr($expSet.put(\\"#$entryKeyAttributeName\\", \\":$entryKeyAttributeName\\")) + $util.qr($expNames.put(\\"#$entryKeyAttributeName\\", \\"$entry.key\\")) + $util.qr($expValues.put(\\":$entryKeyAttributeName\\", $util.dynamodb.toDynamoDB($entry.value))) + #end +#end +#set( $expression = \\"\\" ) +#if( !$expSet.isEmpty() ) + #set( $expression = \\"SET\\" ) + #foreach( $entry in $expSet.entrySet() ) + #set( $expression = \\"$expression $entry.key = $entry.value\\" ) + #if( $foreach.hasNext() ) + #set( $expression = \\"$expression,\\" ) + #end + #end +#end +#if( !$expAdd.isEmpty() ) + #set( $expression = \\"$expression ADD\\" ) + #foreach( $entry in $expAdd.entrySet() ) + #set( $expression = \\"$expression $entry.key $entry.value\\" ) + #if( $foreach.hasNext() ) + #set( $expression = \\"$expression,\\" ) + #end + #end +#end +#if( !$expRemove.isEmpty() ) + #set( $expression = \\"$expression REMOVE\\" ) + #foreach( $entry in $expRemove ) + #set( $expression = \\"$expression $entry\\" ) + #if( $foreach.hasNext() ) + #set( $expression = \\"$expression,\\" ) + #end + #end +#end +#set( $update = {} ) +$util.qr($update.put(\\"expression\\", \\"$expression\\")) +#if( !$expNames.isEmpty() ) + $util.qr($update.put(\\"expressionNames\\", $expNames)) +#end +#if( !$expValues.isEmpty() ) + $util.qr($update.put(\\"expressionValues\\", $expValues)) +#end +## Begin - key condition ** +#if( $ctx.stash.metadata.modelObjectKey ) + #set( $keyConditionExpr = {} ) + #set( $keyConditionExprNames = {} ) + #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) + $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { + \\"attributeExists\\": true +})) + $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) + #end + $util.qr($ctx.stash.conditions.add($keyConditionExpr)) +#else + $util.qr($ctx.stash.conditions.add({ + \\"id\\": { + \\"attributeExists\\": true + } +})) +#end +## End - key condition ** +#if( $context.args.condition ) + $util.qr($ctx.stash.conditions.add($context.args.condition)) +#end +## Start condition block ** +#if( $ctx.stash.conditions && $ctx.stash.conditions.size() != 0 ) + #set( $mergedConditions = { + \\"and\\": $ctx.stash.conditions +} ) + #set( $Conditions = $util.parseJson($util.transform.toDynamoDBConditionExpression($mergedConditions)) ) + #if( $Conditions.expressionValues && $Conditions.expressionValues.size() == 0 ) + #set( $Conditions = { + \\"expression\\": $Conditions.expression, + \\"expressionNames\\": $Conditions.expressionNames +} ) + #end + ## End condition block ** +#end +#set( $UpdateItem = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"UpdateItem\\", + \\"key\\": $Key, + \\"update\\": $update +} ) +#if( $Conditions ) + #if( $keyConditionExprNames ) + $util.qr($Conditions.expressionNames.putAll($keyConditionExprNames)) + #end + $util.qr($UpdateItem.put(\\"condition\\", $Conditions)) +#end +$util.toJson($UpdateItem) +## [End] Mutation Update resolver. **" +`; + +exports[`join table inherits auth from both tables 17`] = ` +"## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) +#if( $ctx.error ) + $util.error($ctx.error.message, $ctx.error.type) +#else + $util.toJson($ctx.result) +#end +## [End] ResponseTemplate. **" +`; + exports[`valid schema 1`] = ` " type Foo { @@ -118,7 +558,7 @@ enum ModelSortDirection { } type ModelFooConnection { - items: [Foo] + items: [Foo!]! nextToken: String } @@ -181,7 +621,7 @@ type Subscription { } type ModelBarConnection { - items: [Bar] + items: [Bar!]! nextToken: String } @@ -211,7 +651,7 @@ input DeleteBarInput { } type ModelFooBarConnection { - items: [FooBar] + items: [FooBar!]! nextToken: String } @@ -263,7 +703,10 @@ input ModelIDKeyConditionInput { exports[`valid schema 2`] = ` Object { - "Bar.foos.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "Bar.foos.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -317,6 +760,27 @@ Object { $util.qr($query.expressionValues.put(\\":sortKey\\", { \\"S\\": \\"$ctx.args.fooID.ge\\" })) #end ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -330,8 +794,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -352,7 +816,10 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "Foo.bars.req.vtl": "#if( $util.isNull($ctx.source.id) ) + "Foo.bars.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.id) ) #set( $result = { \\"items\\": [] } ) @@ -406,6 +873,27 @@ $util.error($ctx.error.message, $ctx.error.type) $util.qr($query.expressionValues.put(\\":sortKey\\", { \\"S\\": \\"$ctx.args.barID.ge\\" })) #end ## [End] Applying Key Condition ** + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end + #else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end + #end + #if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) + #if( !$util.isNullOrBlank($filterExpression.expression) ) + #if( $filterEpression.expressionValues.size() == 0 ) + $util.qr($filterEpression.remove(\\"expressionValues\\")) + #end + #set( $filter = $filterExpression ) + #end + #end { \\"version\\": \\"2018-05-29\\", \\"operation\\": \\"Query\\", @@ -419,8 +907,8 @@ false #else true #end, - \\"filter\\": #if( $context.args.filter ) -$util.transform.toDynamoDBFilterExpression($ctx.args.filter) + \\"filter\\": #if( $filter ) +$util.toJson($filter) #else null #end, @@ -441,37 +929,77 @@ $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($result) #end", - "FooBar.bar.req.vtl": "#if( $util.isNull($ctx.source.barID) ) + "FooBar.bar.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.barID) ) #return #else -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\", - \\"key\\": { - \\"id\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.barID, \\"___xamznone____\\")) - } + #set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"Query\\" +} ) + $util.qr($GetRequest.put(\\"query\\", { + \\"expression\\": \\"#partitionKey = :partitionValue\\", + \\"expressionNames\\": { + \\"#partitionKey\\": \\"id\\" + }, + \\"expressionValues\\": { + \\":partitionValue\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.barID, \\"___xamznone____\\"))) } +})) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) + #end + $util.toJson($GetRequest) #end", "FooBar.bar.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else -$util.toJson($ctx.result) + #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) + #else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) + #end #end", - "FooBar.foo.req.vtl": "#if( $util.isNull($ctx.source.fooID) ) + "FooBar.foo.req.vtl": "#if( $ctx.source.deniedField ) + #return($util.toJson(null)) +#end +#if( $util.isNull($ctx.source.fooID) ) #return #else -{ - \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\", - \\"key\\": { - \\"id\\": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.fooID, \\"___xamznone____\\")) - } + #set( $GetRequest = { + \\"version\\": \\"2018-05-29\\", + \\"operation\\": \\"Query\\" +} ) + $util.qr($GetRequest.put(\\"query\\", { + \\"expression\\": \\"#partitionKey = :partitionValue\\", + \\"expressionNames\\": { + \\"#partitionKey\\": \\"id\\" + }, + \\"expressionValues\\": { + \\":partitionValue\\": $util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.fooID, \\"___xamznone____\\"))) } +})) + #if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) + #end + $util.toJson($GetRequest) #end", "FooBar.foo.res.vtl": "#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else -$util.toJson($ctx.result) + #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) + #else + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) + #end #end", "Mutation.createBar.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) @@ -484,26 +1012,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createBar.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createBar.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -568,13 +1083,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Bar\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createBar.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createBar.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createFoo.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -586,26 +1102,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createFoo.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createFoo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createFoo.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -670,13 +1173,14 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Foo\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createFoo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createFoo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.createFooBar.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $createdAt = $util.time.nowISO8601() ) @@ -688,26 +1192,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createFooBar.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createFooBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createFooBar.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -772,13 +1263,20 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"FooBar\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createFooBar.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createFooBar.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteBar.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -836,13 +1334,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteBar.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteBar.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteFoo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteFoo.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -900,13 +1405,20 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteFoo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteFoo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deleteFooBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deleteFooBar.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -964,13 +1476,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deleteFooBar.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deleteFooBar.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateBar.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -980,6 +1493,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateBar.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -1109,13 +1628,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateBar.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateBar.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateFoo.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -1125,6 +1645,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateFoo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateFoo.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -1254,13 +1780,14 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateFoo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateFoo.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updateFooBar.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -1270,6 +1797,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updateFooBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updateFooBar.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -1399,79 +1932,179 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updateFooBar.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updateFooBar.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.getBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getBar.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getBar.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getBar.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getFoo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getFoo.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getFoo.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getFoo.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.getFooBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getFooBar.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getFooBar.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getFooBar.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) +#end +## [End] Get Response template. **", + "Query.listBars.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## [End] Get ResponseTemplate. **", +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listBars.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -1481,8 +2114,20 @@ $util.toJson($GetRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -1506,13 +2151,19 @@ $util.toJson($GetRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listBars.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listBars.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listFooBars.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listFooBars.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -1522,8 +2173,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -1547,13 +2210,19 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listFooBars.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listFooBars.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.listFoos.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listFoos.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -1563,8 +2232,20 @@ $util.toJson($ListRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -1588,12 +2269,147 @@ $util.toJson($ListRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listFoos.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listFoos.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Subscription.onCreateBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateBar.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateBar.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateFoo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateFoo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateFoo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onCreateFooBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreateFooBar.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreateFooBar.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteBar.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteBar.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteFoo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteFoo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteFoo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeleteFooBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeleteFooBar.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeleteFooBar.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateBar.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateBar.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateFoo.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateFoo.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateFoo.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdateFooBar.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdateFooBar.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdateFooBar.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-many-to-many-transformer.test.ts b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-many-to-many-transformer.test.ts index df6e7f04535..d91541dc2d1 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-many-to-many-transformer.test.ts +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-many-to-many-transformer.test.ts @@ -1,6 +1,8 @@ +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; import { IndexTransformer } from '@aws-amplify/graphql-index-transformer'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { GraphQLTransform, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; import { parse } from 'graphql'; import { HasOneTransformer, ManyToManyTransformer } from '..'; @@ -172,16 +174,142 @@ test('valid schema', () => { expect(out.pipelineFunctions).toMatchSnapshot(); }); +test('join table inherits auth from first table', () => { + const inputSchema = ` + type Foo @model @auth(rules: [{ allow: public, provider: apiKey }]) { + id: ID! + bars: [Bar] @manyToMany(relationName: "FooBar") + } + type Bar @model { + id: ID! + foos: [Foo] @manyToMany(relationName: "FooBar") + }`; + const transformer = createTransformer(); + const out = transformer.transform(inputSchema); + expect(out).toBeDefined(); + const schema = parse(out.schema); + validateModelSchema(schema); + + expect(out.pipelineFunctions['Query.getFooBar.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Query.getFoo.auth.1.req.vtl']); + expect(out.pipelineFunctions['Query.getFooBar.postAuth.1.req.vtl']).toEqual(out.pipelineFunctions['Query.getFoo.postAuth.1.req.vtl']); + expect(out.pipelineFunctions['Query.getFooBar.res.vtl']).toEqual(out.pipelineFunctions['Query.getFoo.res.vtl']); + expect(out.pipelineFunctions['Query.listFooBars.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Query.listFoos.auth.1.req.vtl']); + expect(out.pipelineFunctions['Query.listFooBars.postAuth.1.req.vtl']).toEqual(out.pipelineFunctions['Query.listFoos.postAuth.1.req.vtl']); + expect(out.pipelineFunctions['Mutation.createFooBar.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Mutation.createFoo.auth.1.req.vtl']); + expect(out.pipelineFunctions['Mutation.createFooBar.postAuth.1.req.vtl']).toEqual( + out.pipelineFunctions['Mutation.createFoo.postAuth.1.req.vtl'], + ); + expect(out.pipelineFunctions['Mutation.deleteFooBar.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Mutation.deleteFoo.auth.1.req.vtl']); + expect(out.pipelineFunctions['Mutation.deleteFooBar.postAuth.1.req.vtl']).toEqual( + out.pipelineFunctions['Mutation.deleteFoo.postAuth.1.req.vtl'], + ); + expect(out.pipelineFunctions['Mutation.deleteFooBar.auth.1.res.vtl']).toEqual(out.pipelineFunctions['Mutation.deleteFoo.auth.1.res.vtl']); + expect(out.pipelineFunctions['Mutation.deleteFooBar.req.vtl']).toEqual(out.pipelineFunctions['Mutation.deleteFoo.req.vtl']); + expect(out.pipelineFunctions['Mutation.deleteFooBar.res.vtl']).toEqual(out.pipelineFunctions['Mutation.deleteFoo.res.vtl']); + expect(out.pipelineFunctions['Mutation.updateFooBar.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Mutation.updateFoo.auth.1.req.vtl']); + expect(out.pipelineFunctions['Mutation.updateFooBar.postAuth.1.req.vtl']).toEqual( + out.pipelineFunctions['Mutation.updateFoo.postAuth.1.req.vtl'], + ); + expect(out.pipelineFunctions['Mutation.updateFooBar.auth.1.res.vtl']).toEqual(out.pipelineFunctions['Mutation.updateFoo.auth.1.res.vtl']); + expect(out.pipelineFunctions['Mutation.updateFooBar.req.vtl']).toEqual(out.pipelineFunctions['Mutation.updateFoo.req.vtl']); + expect(out.pipelineFunctions['Mutation.updateFooBar.res.vtl']).toEqual(out.pipelineFunctions['Mutation.updateFoo.res.vtl']); +}); + +test('join table inherits auth from second table', () => { + const inputSchema = ` + type Foo @model { + id: ID! + bars: [Bar] @manyToMany(relationName: "FooBar") + } + type Bar @model @auth(rules: [{ allow: public, provider: apiKey }]) { + id: ID! + foos: [Foo] @manyToMany(relationName: "FooBar") + }`; + const transformer = createTransformer(); + const out = transformer.transform(inputSchema); + expect(out).toBeDefined(); + const schema = parse(out.schema); + validateModelSchema(schema); + + expect(out.pipelineFunctions['Query.getFooBar.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Query.getBar.auth.1.req.vtl']); + expect(out.pipelineFunctions['Query.getFooBar.postAuth.1.req.vtl']).toEqual(out.pipelineFunctions['Query.getBar.postAuth.1.req.vtl']); + expect(out.pipelineFunctions['Query.getFooBar.res.vtl']).toEqual(out.pipelineFunctions['Query.getBar.res.vtl']); + expect(out.pipelineFunctions['Query.listFooBars.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Query.listBars.auth.1.req.vtl']); + expect(out.pipelineFunctions['Query.listFooBars.postAuth.1.req.vtl']).toEqual(out.pipelineFunctions['Query.listBars.postAuth.1.req.vtl']); + expect(out.pipelineFunctions['Mutation.createFooBar.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Mutation.createBar.auth.1.req.vtl']); + expect(out.pipelineFunctions['Mutation.createFooBar.postAuth.1.req.vtl']).toEqual( + out.pipelineFunctions['Mutation.createBar.postAuth.1.req.vtl'], + ); + expect(out.pipelineFunctions['Mutation.deleteFooBar.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Mutation.deleteBar.auth.1.req.vtl']); + expect(out.pipelineFunctions['Mutation.deleteFooBar.postAuth.1.req.vtl']).toEqual( + out.pipelineFunctions['Mutation.deleteBar.postAuth.1.req.vtl'], + ); + expect(out.pipelineFunctions['Mutation.deleteFooBar.auth.1.res.vtl']).toEqual(out.pipelineFunctions['Mutation.deleteBar.auth.1.res.vtl']); + expect(out.pipelineFunctions['Mutation.deleteFooBar.req.vtl']).toEqual(out.pipelineFunctions['Mutation.deleteBar.req.vtl']); + expect(out.pipelineFunctions['Mutation.deleteFooBar.res.vtl']).toEqual(out.pipelineFunctions['Mutation.deleteBar.res.vtl']); + expect(out.pipelineFunctions['Mutation.updateFooBar.auth.1.req.vtl']).toEqual(out.pipelineFunctions['Mutation.updateBar.auth.1.req.vtl']); + expect(out.pipelineFunctions['Mutation.updateFooBar.postAuth.1.req.vtl']).toEqual( + out.pipelineFunctions['Mutation.updateBar.postAuth.1.req.vtl'], + ); + expect(out.pipelineFunctions['Mutation.updateFooBar.auth.1.res.vtl']).toEqual(out.pipelineFunctions['Mutation.updateBar.auth.1.res.vtl']); + expect(out.pipelineFunctions['Mutation.updateFooBar.req.vtl']).toEqual(out.pipelineFunctions['Mutation.updateBar.req.vtl']); + expect(out.pipelineFunctions['Mutation.updateFooBar.res.vtl']).toEqual(out.pipelineFunctions['Mutation.updateBar.res.vtl']); +}); + +test('join table inherits auth from both tables', () => { + const inputSchema = ` + type Foo @model @auth(rules: [{ allow: public, provider: iam }]) { + id: ID! + bars: [Bar] @manyToMany(relationName: "FooBar") + } + type Bar @model @auth(rules: [{ allow: public, provider: apiKey }]) { + id: ID! + foos: [Foo] @manyToMany(relationName: "FooBar") + }`; + const transformer = createTransformer(); + const out = transformer.transform(inputSchema); + expect(out).toBeDefined(); + const schema = parse(out.schema); + validateModelSchema(schema); + + expect(out.pipelineFunctions['Query.getFooBar.auth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Query.getFooBar.postAuth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Query.getFooBar.res.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Query.listFooBars.auth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Query.listFooBars.postAuth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.createFooBar.auth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.createFooBar.postAuth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.deleteFooBar.auth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.deleteFooBar.postAuth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.deleteFooBar.auth.1.res.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.deleteFooBar.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.deleteFooBar.res.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.updateFooBar.auth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.updateFooBar.postAuth.1.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.updateFooBar.auth.1.res.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.updateFooBar.req.vtl']).toMatchSnapshot(); + expect(out.pipelineFunctions['Mutation.updateFooBar.res.vtl']).toMatchSnapshot(); +}); + function createTransformer() { + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'API_KEY', + }, + additionalAuthenticationProviders: [{ authenticationType: 'AWS_IAM' }], + }; + const authTransformer = new AuthTransformer({ authConfig, addAwsIamAuthInOutputSchema: false }); const modelTransformer = new ModelTransformer(); const indexTransformer = new IndexTransformer(); const hasOneTransformer = new HasOneTransformer(); const transformer = new GraphQLTransform({ + authConfig, transformers: [ modelTransformer, indexTransformer, hasOneTransformer, - new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer), + new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer), + authTransformer, ], }); diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/relational-auth.test.ts b/packages/amplify-graphql-relational-transformer/src/__tests__/relational-auth.test.ts new file mode 100644 index 00000000000..7523c593ad5 --- /dev/null +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/relational-auth.test.ts @@ -0,0 +1,192 @@ +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { IndexTransformer } from '@aws-amplify/graphql-index-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { AppSyncAuthConfiguration, AppSyncAuthMode } from '@aws-amplify/graphql-transformer-interfaces'; +import { DocumentNode, ObjectTypeDefinitionNode, Kind, FieldDefinitionNode, parse, InputValueDefinitionNode } from 'graphql'; +import { HasManyTransformer, BelongsToTransformer } from '..'; + +const iamDefaultConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AWS_IAM', + }, + additionalAuthenticationProviders: [], +}; + +const apiKeyDefaultConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'API_KEY', + }, + additionalAuthenticationProviders: [], +}; + +test('per-field auth on relational field', () => { + const validSchema = ` + type Post @model @auth(rules: [ { allow: groups, groups: ["admin"] }, { allow: groups, groups: ["viewer"], operations: [read] } ]){ + id: ID! + title: String! + comments: [Comment] @hasMany @auth(rules: [ { allow: groups, groups: ["admin"] } ]) + } + + type Comment @model { + id: ID! + content: String + }`; + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [{ authenticationType: 'AWS_IAM' }], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new HasManyTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + + expect(out.pipelineFunctions['Post.comments.auth.1.req.vtl']).toContain( + '#set( $staticGroupRoles = [{"claim":"cognito:groups","entity":"admin"}] )', + ); +}); + +test(`ModelXConnection type is getting the directives added, when a field has @hasMany but one fo the types has no queries defined`, () => { + const validSchema = ` + type User @model + @auth(rules: [ + { allow: private, provider: iam, operations: [read] } + { allow: groups, groups: ["group"], operations: [read, update, delete] }, + ]) { + id: ID! + posts: [Post!] @hasMany(indexName: "byUser", fields: ["id"]) + } + type Post @model(queries: null) + @auth(rules: [ + { allow: private, provider: iam, operations: [read] }, + { allow: groups, groups: ["group"], operations: [read, update, delete] } + ]) { + id: ID! + postUserId: ID! @index(name: "byUser") + message: String + }`; + const transformer = getTransformer(withAuthModes(iamDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + const out = transformer.transform(validSchema); + const schemaDoc = parse(out.schema); + const queryType = getObjectType(schemaDoc, 'Query'); + const mutationType = getObjectType(schemaDoc, 'Mutation'); + + expectTwo(getField(queryType, 'getUser'), ['aws_iam', 'aws_cognito_user_pools']); + expectTwo(getField(queryType, 'listUsers'), ['aws_iam', 'aws_cognito_user_pools']); + + expectNone(getField(mutationType, 'createUser')); + expectOne(getField(mutationType, 'updateUser'), 'aws_cognito_user_pools'); + expectOne(getField(mutationType, 'deleteUser'), 'aws_cognito_user_pools'); + + const userType = getObjectType(schemaDoc, 'User'); + expectTwo(userType, ['aws_iam', 'aws_cognito_user_pools']); + expectNone(getField(userType, 'posts')); + + const modelPostConnectionType = getObjectType(schemaDoc, 'ModelPostConnection'); + expect(modelPostConnectionType).toBeDefined(); + expectTwo(modelPostConnectionType, ['aws_iam', 'aws_cognito_user_pools']); +}); + +test(`ModelXConnection type is getting the directives added, when a field has @connection but one of the types has no queries defined. Many to Many`, () => { + const schema = ` + type Post @model @auth(rules: [{ allow: owner }]) { + id: ID! + title: String! + editors: [PostEditor] @hasMany(indexName: "byPost", fields: ["id"]) + } + # Create a join model and disable queries as you don't need them + # and can query through Post.editors and User.posts + type PostEditor + @model(queries: null) + @auth(rules: [{ allow: owner }]) { + id: ID! + postID: ID! @index(name: "byPost", sortKeyFields: ["editorID"]) + editorID: ID! @index(name: "byEditor", sortKeyFields: ["postID"]) + post: Post! @belongsTo(fields: ["postID"]) + editor: User! @belongsTo(fields: ["editorID"]) + } + type User @model @auth(rules: [{ allow: owner }]) { + id: ID! + username: String! + posts: [PostEditor] @hasMany(indexName: "byEditor", fields: ["id"]) + }`; + + const transformer = getTransformer(withAuthModes(apiKeyDefaultConfig, ['AMAZON_COGNITO_USER_POOLS'])); + const out = transformer.transform(schema); + const schemaDoc = parse(out.schema); + + const modelPostEditorConnectionType = getObjectType(schemaDoc, 'ModelPostEditorConnection'); + expect(modelPostEditorConnectionType).toBeDefined(); + // since we have resolver level auth to deny providers the default is added here to ensure the access is granted if the default type is not applied on the parent + // therefore we just need to make sure that the access is at least granted on the schema level + expect((modelPostEditorConnectionType as any).directives.some((dir: any) => dir.name.value === 'aws_cognito_user_pools')).toBe(true); +}); + +const getTransformer = (authConfig: AppSyncAuthConfiguration) => { + return new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new IndexTransformer(), + new HasManyTransformer(), + new BelongsToTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); +}; + +const withAuthModes = (authConfig: AppSyncAuthConfiguration, authModes: AppSyncAuthMode[]): AppSyncAuthConfiguration => { + const newAuthConfig = { + defaultAuthentication: { + authenticationType: authConfig.defaultAuthentication.authenticationType, + }, + additionalAuthenticationProviders: [], + }; + + for (const authMode of authModes) { + const provider = { authenticationType: authMode }; + + newAuthConfig.additionalAuthenticationProviders.push(provider as never); + } + + return newAuthConfig; +}; + +const getObjectType = (doc: DocumentNode, type: string): ObjectTypeDefinitionNode | undefined => { + return doc.definitions.find(def => def.kind === Kind.OBJECT_TYPE_DEFINITION && def.name.value === type) as + | ObjectTypeDefinitionNode + | undefined; +}; + +const getField = (type: any, name: string) => type.fields.find((f: any) => f.name.value === name); + +const expectNone = (fieldOrType: any) => { + expect(fieldOrType.directives.length === 0); +}; + +const expectOne = (fieldOrType: any, directiveName: string) => { + expect(fieldOrType.directives.length).toBe(1); + expect(fieldOrType.directives.find((d: any) => d.name.value === directiveName)).toBeDefined(); +}; + +const expectTwo = (fieldOrType: any, directiveNames: string[]) => { + expect(directiveNames).toBeDefined(); + expect(directiveNames).toHaveLength(2); + expect(fieldOrType.directives.length === 2); + expect(fieldOrType.directives.find((d: any) => d.name.value === directiveNames[0])).toBeDefined(); + expect(fieldOrType.directives.find((d: any) => d.name.value === directiveNames[1])).toBeDefined(); +}; diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts index 0fcc2ddfbfb..ef3c04399fd 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts @@ -6,7 +6,7 @@ import { TransformerTransformSchemaStepContextProvider, TransformerValidationStepContextProvider, } from '@aws-amplify/graphql-transformer-interfaces'; -import { DirectiveNode, FieldDefinitionNode, InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode } from 'graphql'; +import { DirectiveNode, FieldDefinitionNode, InterfaceTypeDefinitionNode, Kind, ObjectTypeDefinitionNode } from 'graphql'; import { blankObject, getBaseType, @@ -24,6 +24,7 @@ import { ManyToManyDirectiveConfiguration, ManyToManyRelation } from './types'; import { validateModelDirective } from './utils'; import { makeQueryConnectionWithKeyResolver, updateTableForConnection } from './resolvers'; import { ensureHasManyConnectionField, extendTypeWithConnection, getPartitionKeyField } from './schema'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { IndexTransformer } from '@aws-amplify/graphql-index-transformer'; import { HasOneTransformer } from './graphql-has-one-transformer'; @@ -40,12 +41,19 @@ export class ManyToManyTransformer extends TransformerPluginBase { private modelTransformer: ModelTransformer; private indexTransformer: IndexTransformer; private hasOneTransformer: HasOneTransformer; - - constructor(modelTransformer: ModelTransformer, indexTransformer: IndexTransformer, hasOneTransformer: HasOneTransformer) { + private authTransformer: AuthTransformer; + + constructor( + modelTransformer: ModelTransformer, + indexTransformer: IndexTransformer, + hasOneTransformer: HasOneTransformer, + authTransformer: AuthTransformer, + ) { super('amplify-many-to-many-transformer', directiveDefinition); this.modelTransformer = modelTransformer; this.indexTransformer = indexTransformer; this.hasOneTransformer = hasOneTransformer; + this.authTransformer = authTransformer; } field = ( @@ -136,10 +144,17 @@ export class ManyToManyTransformer extends TransformerPluginBase { const d2RelatedField = makeField(d2FieldNameId, [], wrapNonNull(makeNamedType(getBaseType(d2PartitionKey.type))), [d2IndexDirective]); const d1Field = makeField(d1FieldName, [], wrapNonNull(makeNamedType(d1TypeName)), [d1HasOneDirective]); const d2Field = makeField(d2FieldName, [], wrapNonNull(makeNamedType(d2TypeName)), [d2HasOneDirective]); + const joinTableDirectives = [joinModelDirective]; + const joinTableAuthDirective = createJoinTableAuthDirective(directive1.object, directive2.object); + + if (joinTableAuthDirective) { + joinTableDirectives.push(joinTableAuthDirective); + } + const joinType = { ...blankObject(name), fields: [makeField('id', [], wrapNonNull(makeNamedType('ID'))), d1RelatedField, d2RelatedField, d1Field, d2Field], - directives: [joinModelDirective], + directives: joinTableDirectives, }; ctx.output.addObject(joinType); @@ -160,6 +175,11 @@ export class ManyToManyTransformer extends TransformerPluginBase { this.indexTransformer.field(joinType, d2RelatedField, d2IndexDirective, context); this.hasOneTransformer.field(joinType, d1Field, d1HasOneDirective, context); this.hasOneTransformer.field(joinType, d2Field, d2HasOneDirective, context); + + if (joinTableAuthDirective) { + this.authTransformer.object(joinType, joinTableAuthDirective, context); + } + context.providerRegistry.registerDataSourceProvider(joinType, this.modelTransformer); }); }; @@ -206,3 +226,17 @@ function addDirectiveToRelationMap(map: Map, directi function getGraphqlRelationName(name: string): string { return graphqlName(toUpper(name)); } + +function createJoinTableAuthDirective(table1: ObjectTypeDefinitionNode, table2: ObjectTypeDefinitionNode) { + const t1Auth = table1.directives!.find(directive => directive.name.value === 'auth'); + const t2Auth = table2.directives!.find(directive => directive.name.value === 'auth'); + const t1Rules = ((t1Auth?.arguments ?? []).find(arg => arg.name.value === 'rules')?.value as any)?.values ?? []; + const t2Rules = ((t2Auth?.arguments ?? []).find(arg => arg.name.value === 'rules')?.value as any)?.values ?? []; + const rules = [...t1Rules, ...t2Rules]; + + if (rules.length === 0) { + return; + } + + return makeDirective('auth', [makeArgument('rules', { kind: Kind.LIST, values: rules })]); +} diff --git a/packages/amplify-graphql-relational-transformer/src/resolvers.ts b/packages/amplify-graphql-relational-transformer/src/resolvers.ts index 97da8250ec7..f7cf53a4702 100644 --- a/packages/amplify-graphql-relational-transformer/src/resolvers.ts +++ b/packages/amplify-graphql-relational-transformer/src/resolvers.ts @@ -6,6 +6,7 @@ import { Table } from '@aws-cdk/aws-dynamodb'; import * as cdk from '@aws-cdk/core'; import { ObjectTypeDefinitionNode } from 'graphql'; import { + and, bool, compoundExpression, DynamoDBMappingTemplate, @@ -13,16 +14,22 @@ import { Expression, ifElse, iff, + int, + isNullOrEmpty, list, + methodCall, + not, nul, obj, ObjectNode, or, print, + qref, raw, ref, set, str, + toJson, } from 'graphql-mapping-template'; import { applyCompositeKeyConditionExpression, @@ -36,6 +43,8 @@ import { import { HasManyDirectiveConfiguration, HasOneDirectiveConfiguration } from './types'; import { getConnectionAttributeName } from './utils'; +const authFilter = ref('ctx.stash.authFilter'); + export function makeGetItemConnectionWithKeyResolver(config: HasOneDirectiveConfiguration, ctx: TransformerContextProvider) { const { connectionFields, field, fields, object, relatedType, relatedTypeIndex } = config; assert(relatedTypeIndex.length > 0); @@ -44,8 +53,14 @@ export function makeGetItemConnectionWithKeyResolver(config: HasOneDirectiveConf const { keySchema } = table as any; const dataSource = ctx.api.host.getDataSource(`${relatedType.name.value}Table`); const partitionKeyName = keySchema[0].attributeName; - const keyObj = { - [partitionKeyName]: ref(`util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.${localFields[0]}, "${NONE_VALUE}"))`), + let totalExpressions = [`#partitionKey = :partitionValue`]; + let totalExpressionNames: Record = { + [`#partitionKey`]: str(partitionKeyName), + }; + let totalExpressionValues: Record = { + [`:partitionValue`]: ref( + `util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.${localFields[0]}, "${NONE_VALUE}")))`, + ), }; // Add a composite sort key or simple sort key if there is one. @@ -54,11 +69,18 @@ export function makeGetItemConnectionWithKeyResolver(config: HasOneDirectiveConf const sortKeyName = keySchema[1].attributeName; const condensedSortKeyValue = condenseRangeKey(rangeKeyFields.map(keyField => `\${ctx.source.${keyField}}`)); - keyObj[sortKeyName] = ref(`util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank("${condensedSortKeyValue}", "${NONE_VALUE}"))`); + totalExpressions.push(`#sortKeyName = :sortKeyName`); + totalExpressionNames['#sortKeyName'] = str(sortKeyName); + totalExpressionValues[':sortKeyName'] = ref( + `util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank("${condensedSortKeyValue}", "${NONE_VALUE}")))`, + ); } else if (relatedTypeIndex.length === 2) { const sortKeyName = keySchema[1].attributeName; - - keyObj[sortKeyName] = ref(`util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.${localFields[1]}, "${NONE_VALUE}"))`); + totalExpressions.push(`#sortKeyName = :sortKeyName`); + totalExpressionNames['#sortKeyName'] = str(sortKeyName); + totalExpressionValues[':sortKeyName'] = ref( + `util.parseJson($util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.${localFields[1]}, "${NONE_VALUE}")))`, + ); } const resolver = ctx.resolvers.generateQueryResolver( @@ -67,20 +89,55 @@ export function makeGetItemConnectionWithKeyResolver(config: HasOneDirectiveConf dataSource as any, MappingTemplate.s3MappingTemplateFromString( print( - ifElse( - or(localFields.map(f => raw(`$util.isNull($ctx.source.${f})`))), - raw('#return'), - compoundExpression([ - DynamoDBMappingTemplate.getItem({ - key: obj(keyObj), - }), - ]), - ), + compoundExpression([ + iff(ref('ctx.source.deniedField'), raw(`#return($util.toJson(null))`)), + ifElse( + or(localFields.map(f => raw(`$util.isNull($ctx.source.${f})`))), + raw('#return'), + compoundExpression([ + set(ref('GetRequest'), obj({ version: str('2018-05-29'), operation: str('Query') })), + qref( + methodCall( + ref('GetRequest.put'), + str('query'), + obj({ + expression: str(totalExpressions.join(' AND ')), + expressionNames: obj(totalExpressionNames), + expressionValues: obj(totalExpressionValues), + }), + ), + ), + iff( + not(isNullOrEmpty(authFilter)), + qref( + methodCall( + ref('GetRequest.put'), + str('filter'), + methodCall(ref('util.parseJson'), methodCall(ref('util.transform.toDynamoDBFilterExpression'), authFilter)), + ), + ), + ), + toJson(ref('GetRequest')), + ]), + ), + ]), ), `${object.name.value}.${field.name.value}.req.vtl`, ), MappingTemplate.s3MappingTemplateFromString( - print(DynamoDBMappingTemplate.dynamoDBResponse(false)), + print( + DynamoDBMappingTemplate.dynamoDBResponse( + false, + ifElse( + and([not(ref('ctx.result.items.isEmpty()')), equals(ref('ctx.result.scannedCount'), int(1))]), + toJson(ref('ctx.result.items[0]')), + compoundExpression([ + iff(and([ref('ctx.result.items.isEmpty()'), equals(ref('ctx.result.scannedCount'), int(1))]), ref('util.unauthorized()')), + toJson(nul()), + ]), + ), + ), + ), `${object.name.value}.${field.name.value}.res.vtl`, ), ); @@ -115,6 +172,36 @@ export function makeQueryConnectionWithKeyResolver(config: HasManyDirectiveConfi setup.push(applyCompositeKeyConditionExpression(sortKeyFieldNames, 'query', toCamelCase(sortKeyFieldNames), sortKeyFieldName)); } } + // add setup filter to query + setup.push( + ifElse( + not(isNullOrEmpty(authFilter)), + compoundExpression([ + set(ref('filter'), authFilter), + iff(not(isNullOrEmpty(ref('ctx.args.filter'))), set(ref('filter'), obj({ and: list([ref('filter'), ref('ctx.args.filter')]) }))), + ]), + iff(not(isNullOrEmpty(ref('ctx.args.filter'))), set(ref('filter'), ref('ctx.args.filter'))), + ), + iff( + not(isNullOrEmpty(ref('filter'))), + compoundExpression([ + set( + ref(`filterExpression`), + methodCall(ref('util.parseJson'), methodCall(ref('util.transform.toDynamoDBFilterExpression'), ref('filter'))), + ), + iff( + not(methodCall(ref('util.isNullOrBlank'), ref('filterExpression.expression'))), + compoundExpression([ + iff( + equals(methodCall(ref('filterEpression.expressionValues.size')), int(0)), + qref(methodCall(ref('filterEpression.remove'), str('expressionValues'))), + ), + set(ref('filter'), ref('filterExpression')), + ]), + ), + ]), + ), + ); const queryArguments = { query: raw('$util.toJson($query)'), @@ -123,7 +210,7 @@ export function makeQueryConnectionWithKeyResolver(config: HasManyDirectiveConfi ifElse(equals(ref('context.args.sortDirection'), str('ASC')), bool(true), bool(false)), bool(true), ), - filter: ifElse(ref('context.args.filter'), ref('util.transform.toDynamoDBFilterExpression($ctx.args.filter)'), nul()), + filter: ifElse(ref('filter'), ref('util.toJson($filter)'), nul()), limit: ref('limit'), nextToken: ifElse(ref('context.args.nextToken'), ref('util.toJson($context.args.nextToken)'), nul()), } as any; @@ -139,11 +226,14 @@ export function makeQueryConnectionWithKeyResolver(config: HasManyDirectiveConfi dataSource as any, MappingTemplate.s3MappingTemplateFromString( print( - ifElse( - raw(`$util.isNull($ctx.source.${connectionAttributes[0]})`), - compoundExpression([set(ref('result'), obj({ items: list([]) })), raw('#return($result)')]), - compoundExpression([...setup, queryObj]), - ), + compoundExpression([ + iff(ref('ctx.source.deniedField'), raw(`#return($util.toJson(null))`)), + ifElse( + raw(`$util.isNull($ctx.source.${connectionAttributes[0]})`), + compoundExpression([set(ref('result'), obj({ items: list([]) })), raw('#return($result)')]), + compoundExpression([...setup, queryObj]), + ), + ]), ), `${object.name.value}.${field.name.value}.req.vtl`, ), diff --git a/packages/amplify-graphql-relational-transformer/src/schema.ts b/packages/amplify-graphql-relational-transformer/src/schema.ts index db278a06b0a..05142f82784 100644 --- a/packages/amplify-graphql-relational-transformer/src/schema.ts +++ b/packages/amplify-graphql-relational-transformer/src/schema.ts @@ -65,7 +65,7 @@ function generateModelXConnectionType( let connectionTypeExtension = blankObjectExtension(tableXConnectionName); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [ - makeField('items', [], makeListType(makeNamedType(tableXConnectionName))), + makeField('items', [], makeNonNullType(makeListType(makeNonNullType(makeNamedType(relatedType.name.value))))), ]); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [makeField('nextToken', [], makeNamedType('String'))]); diff --git a/packages/amplify-graphql-searchable-transformer/src/__tests__/__snapshots__/amplify-graphql-searchable-transformer.tests.ts.snap b/packages/amplify-graphql-searchable-transformer/src/__tests__/__snapshots__/amplify-graphql-searchable-transformer.tests.ts.snap index 4f2f780d9da..7fec1a00e9f 100644 --- a/packages/amplify-graphql-searchable-transformer/src/__tests__/__snapshots__/amplify-graphql-searchable-transformer.tests.ts.snap +++ b/packages/amplify-graphql-searchable-transformer/src/__tests__/__snapshots__/amplify-graphql-searchable-transformer.tests.ts.snap @@ -101,12 +101,6 @@ input SearchableEmployeeSortInput { direction: SearchableSortDirection } -input SearchableEmployeeAggregationInput { - name: String! - type: SearchableAggregateType! - field: String! -} - enum SearchableAggregateType { terms avg @@ -115,11 +109,23 @@ enum SearchableAggregateType { sum } +enum SearchableEmployeeAggregateField { + id + firstName + lastName +} + +input SearchableEmployeeAggregationInput { + name: String! + type: SearchableAggregateType! + field: SearchableEmployeeAggregateField! +} + type SearchableEmployeeConnection { - items: [Employee] + items: [Employee!]! nextToken: String total: Int - aggregateItems: [SearchableAggregateResult] + aggregateItems: [SearchableAggregateResult!]! } type SearchableAggregateResult { @@ -240,7 +246,7 @@ enum ModelSortDirection { } type ModelEmployeeConnection { - items: [Employee] + items: [Employee!]! nextToken: String } @@ -396,12 +402,6 @@ input SearchablePostSortInput { direction: SearchableSortDirection } -input SearchablePostAggregationInput { - name: String! - type: SearchableAggregateType! - field: String! -} - enum SearchableAggregateType { terms avg @@ -410,11 +410,24 @@ enum SearchableAggregateType { sum } +enum SearchablePostAggregateField { + id + title + createdAt + updatedAt +} + +input SearchablePostAggregationInput { + name: String! + type: SearchableAggregateType! + field: SearchablePostAggregateField! +} + type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int - aggregateItems: [SearchableAggregateResult] + aggregateItems: [SearchableAggregateResult!]! } type SearchableAggregateResult { @@ -535,7 +548,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -604,26 +617,13 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", - "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** -## Begin - key condition ** -#if( $ctx.stash.metadata.modelObjectKey ) - #set( $keyConditionExpr = {} ) - #set( $keyConditionExprNames = {} ) - #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) - $util.qr($keyConditionExpr.put(\\"keyCondition$velocityCount\\", { - \\"attributeExists\\": false -})) - $util.qr($keyConditionExprNames.put(\\"#keyCondition$velocityCount\\", \\"$entry.key\\")) - #end - $util.qr($ctx.stash.conditions.add($keyConditionExpr)) -#else - $util.qr($ctx.stash.conditions.add({ - \\"id\\": { - \\"attributeExists\\": false - } -})) + "Mutation.createPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() #end -## End - key condition ** +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Mutation.createPost.req.vtl": "## [Start] Create Request template. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** @@ -688,13 +688,20 @@ $util.qr($mergedValues.put(\\"__typename\\", \\"Post\\")) #end $util.toJson($PutObject) ## [End] Create Request template. **", - "Mutation.createPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.createPost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Mutation.deletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.deletePost.req.vtl": "## [Start] Delete Request template. ** #set( $DeleteRequest = { \\"version\\": \\"2018-05-29\\", @@ -752,13 +759,14 @@ $util.qr($DeleteRequest.put(\\"key\\", $Key)) #end $util.toJson($DeleteRequest) ## [End] Delete Request template. **", - "Mutation.deletePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.deletePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Mutation.updatePost.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) #set( $updatedAt = $util.time.nowISO8601() ) @@ -768,6 +776,12 @@ $util.toJson({ \\"payload\\": {} }) ## [End] Initialization default values. **", + "Mutation.updatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Mutation.updatePost.req.vtl": "## [Start] Mutation Update resolver. ** ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) @@ -897,35 +911,73 @@ $util.qr($update.put(\\"expression\\", \\"$expression\\")) #end $util.toJson($UpdateItem) ## [End] Mutation Update resolver. **", - "Mutation.updatePost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Mutation.updatePost.res.vtl": "## [Start] ResponseTemplate. ** +$util.qr($ctx.result.put(\\"__operation\\", \\"Mutation\\")) #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", + "Query.getPost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.getPost.req.vtl": "## [Start] Get Request template. ** #set( $GetRequest = { \\"version\\": \\"2018-05-29\\", - \\"operation\\": \\"GetItem\\" + \\"operation\\": \\"Query\\" } ) #if( $ctx.stash.metadata.modelObjectKey ) - #set( $key = $ctx.stash.metadata.modelObjectKey ) + #set( $expression = \\"\\" ) + #set( $expressionNames = {} ) + #set( $expressionValues = {} ) + #foreach( $item in $ctx.stash.metadata.modelObjectKey.entrySet() ) + #set( $expression = \\"$expression#keyCount$velocityCount = :valueCount$velocityCount AND \\" ) + $util.qr($expressionNames.put(\\"#keyCount$velocityCount\\", $item.key)) + $util.qr($expressionValues.put(\\":valueCount$velocityCount\\", $item.value)) + #end + #set( $expression = $expression.replaceAll(\\"AND $\\", \\"\\") ) + #set( $query = { + \\"expression\\": $expression, + \\"expressionNames\\": $expressionNames, + \\"expressionValues\\": $expressionValues +} ) #else - #set( $key = { - \\"id\\": $util.dynamodb.toDynamoDB($ctx.args.id) + #set( $query = { + \\"expression\\": \\"id = :id\\", + \\"expressionValues\\": { + \\":id\\": $util.parseJson($util.dynamodb.toDynamoDBJson($ctx.args.id)) + } } ) #end -$util.qr($GetRequest.put(\\"key\\", $key)) +$util.qr($GetRequest.put(\\"query\\", $query)) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + $util.qr($GetRequest.put(\\"filter\\", $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.stash.authFilter)))) +#end $util.toJson($GetRequest) ## [End] Get Request template. **", - "Query.getPost.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.getPost.res.vtl": "## [Start] Get Response template. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) +#end +#if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) + $util.toJson($ctx.result.items[0]) #else - $util.toJson($ctx.result) + #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) +$util.unauthorized() + #end + $util.toJson(null) #end -## [End] Get ResponseTemplate. **", +## [End] Get Response template. **", + "Query.listPosts.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", "Query.listPosts.req.vtl": "## [Start] List Request. ** #set( $limit = $util.defaultIfNull($context.args.limit, 100) ) #set( $ListRequest = { @@ -935,8 +987,20 @@ $util.toJson($GetRequest) #if( $context.args.nextToken ) #set( $ListRequest.nextToken = $context.args.nextToken ) #end -#if( $context.args.filter ) - #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter)) ) +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"and\\": [$filter, $ctx.args.filter] +} ) + #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $ctx.args.filter ) + #end +#end +#if( !$util.isNullOrEmpty($filter) ) + #set( $filterExpression = $util.parseJson($util.transform.toDynamoDBFilterExpression($filter)) ) #if( !$util.isNullOrBlank($filterExpression.expression) ) #if( $filterEpression.expressionValues.size() == 0 ) $util.qr($filterEpression.remove(\\"expressionValues\\")) @@ -960,14 +1024,16 @@ $util.toJson($GetRequest) #end $util.toJson($ListRequest) ## [End] List Request. **", - "Query.listPosts.res.vtl": "## [Start] Get ResponseTemplate. ** + "Query.listPosts.res.vtl": "## [Start] ResponseTemplate. ** #if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else $util.toJson($ctx.result) #end -## [End] Get ResponseTemplate. **", +## [End] ResponseTemplate. **", "Query.searchPosts.req.vtl": "#set( $indexPath = \\"/post/doc/_search\\" ) +#set( $allowedAggFields = $util.defaultIfNull($ctx.stash.allowedAggFields, []) ) +#set( $aggFieldsFilterMap = $util.defaultIfNull($ctx.stash.aggFieldsFilterMap, {}) ) #set( $nonKeywordFields = [] ) #set( $sortValues = [] ) #set( $aggregateValues = {} ) @@ -995,16 +1061,46 @@ $util.toJson($ListRequest) #set( $sortField = $util.toJson(\\"\${sortItem.field}.keyword\\") ) #end #end - #set( $sortDirection = $util.toJson({\\"order\\": $sortItem.direction}) ) + #if( $util.isNullOrEmpty($sortItem.direction) ) + #set( $sortDirection = $util.toJson({\\"order\\": \\"desc\\"}) ) + #else + #set( $sortDirection = $util.toJson({\\"order\\": $sortItem.direction}) ) + #end $util.qr($sortValues.add(\\"{$sortField: $sortDirection}\\")) #end #end #foreach( $aggItem in $context.args.aggregates ) + #if( $allowedAggFields.contains($aggItem.field) ) + #set( $aggFilter = { \\"match_all\\": {} } ) + #elseif( $aggFieldsFilterMap.containsKey($aggItem.field) ) + #set( $aggFilter = { \\"bool\\": { \\"should\\": $aggFieldsFilterMap.get($aggItem.field) } } ) + #else + $util.error(\\"Unauthorized to run aggregation on field: \${aggItem.field}\\", \\"Unauthorized\\") + #end #if( $nonKeywordFields.contains($aggItem.field) ) - $util.qr($aggregateValues.put(\\"$aggItem.name\\", {\\"$aggItem.type\\": {\\"field\\": \\"$aggItem.field\\"}})) + $util.qr($aggregateValues.put(\\"$aggItem.name\\", { \\"filter\\": $aggFilter, \\"aggs\\": { \\"$aggItem.name\\": { \\"$aggItem.type\\": { \\"field\\": \\"$aggItem.field\\" }}} })) #else - $util.qr($aggregateValues.put(\\"$aggItem.name\\", {\\"$aggItem.type\\": {\\"field\\": \\"\${aggItem.field}.keyword\\"}})) + $util.qr($aggregateValues.put(\\"$aggItem.name\\", { \\"filter\\": $aggFilter, \\"aggs\\": { \\"$aggItem.name\\": { \\"$aggItem.type\\": { \\"field\\": \\"\${aggItem.field}.keyword\\" }}} })) + #end +#end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $filter = $ctx.stash.authFilter ) + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = { + \\"bool\\": { + \\"must\\": [$ctx.stash.authFilter, $util.parseJson($util.transform.toElasticsearchQueryDSL($ctx.args.filter))] + } +} ) #end +#else + #if( !$util.isNullOrEmpty($ctx.args.filter) ) + #set( $filter = $util.parseJson($util.transform.toElasticsearchQueryDSL($ctx.args.filter)) ) + #end +#end +#if( $util.isNullOrEmpty($filter) ) + #set( $filter = { + \\"match_all\\": {} +} ) #end { \\"version\\": \\"2018-05-29\\", @@ -1017,13 +1113,7 @@ $util.toJson($ListRequest) \\"size\\": #if( $context.args.limit ) $context.args.limit #else 100 #end, \\"sort\\": $sortValues, \\"version\\": false, - \\"query\\": #if( $context.args.filter ) -$util.transform.toElasticsearchQueryDSL($ctx.args.filter) -#else -{ - \\"match_all\\": {} - } -#end, + \\"query\\": $util.toJson($filter), \\"aggs\\": $util.toJson($aggregateValues) } } @@ -1039,15 +1129,16 @@ $util.transform.toElasticsearchQueryDSL($ctx.args.filter) #foreach( $aggItem in $context.result.aggregations.keySet() ) #set( $aggResult = {} ) #set( $aggResultValue = {} ) + #set( $currentAggItem = $ctx.result.aggregations.get($aggItem) ) $util.qr($aggResult.put(\\"name\\", $aggItem)) - #if( !$util.isNullOrEmpty($context.result.aggregations) ) - #if( !$util.isNullOrEmpty($context.result.aggregations.get($aggItem).buckets) ) + #if( !$util.isNullOrEmpty($currentAggItem) ) + #if( !$util.isNullOrEmpty($currentAggItem.get($aggItem).buckets) ) $util.qr($aggResultValue.put(\\"__typename\\", \\"SearchableAggregateBucketResult\\")) - $util.qr($aggResultValue.put(\\"buckets\\", $context.result.aggregations.get($aggItem).buckets)) + $util.qr($aggResultValue.put(\\"buckets\\", $currentAggItem.get($aggItem).buckets)) #end - #if( !$util.isNullOrEmpty($context.result.aggregations.get($aggItem).value) ) + #if( !$util.isNullOrEmpty($currentAggItem.get($aggItem).value) ) $util.qr($aggResultValue.put(\\"__typename\\", \\"SearchableAggregateScalarResult\\")) - $util.qr($aggResultValue.put(\\"value\\", $context.result.aggregations.get($aggItem).value)) + $util.qr($aggResultValue.put(\\"value\\", $currentAggItem.get($aggItem).value)) #end #end $util.qr($aggResult.put(\\"result\\", $aggResultValue)) @@ -1059,6 +1150,51 @@ $util.toJson({ \\"nextToken\\": $nextToken, \\"aggregateItems\\": $aggregateValues })", + "Subscription.onCreatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onCreatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onCreatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onDeletePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onDeletePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onDeletePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", + "Subscription.onUpdatePost.postAuth.1.req.vtl": "## [Start] Sandbox Mode Disabled. ** +#if( !$ctx.stash.get(\\"hasAuth\\") ) + $util.unauthorized() +#end +$util.toJson({}) +## [End] Sandbox Mode Disabled. **", + "Subscription.onUpdatePost.req.vtl": "## [Start] Subscription Request template. ** +$util.toJson({ + \\"version\\": \\"2018-05-29\\", + \\"payload\\": {} +}) +## [End] Subscription Request template. **", + "Subscription.onUpdatePost.res.vtl": "## [Start] Subscription Response template. ** +$util.toJson(null) +## [End] Subscription Response template. **", } `; @@ -1164,12 +1300,6 @@ input SearchablePostSortInput { direction: SearchableSortDirection } -input SearchablePostAggregationInput { - name: String! - type: SearchableAggregateType! - field: String! -} - enum SearchableAggregateType { terms avg @@ -1178,11 +1308,24 @@ enum SearchableAggregateType { sum } +enum SearchablePostAggregateField { + id + title + createdAt + updatedAt +} + +input SearchablePostAggregationInput { + name: String! + type: SearchableAggregateType! + field: SearchablePostAggregateField! +} + type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int - aggregateItems: [SearchableAggregateResult] + aggregateItems: [SearchableAggregateResult!]! } type SearchableAggregateResult { @@ -1232,17 +1375,22 @@ input SearchableUserSortInput { direction: SearchableSortDirection } +enum SearchableUserAggregateField { + id + name +} + input SearchableUserAggregationInput { name: String! type: SearchableAggregateType! - field: String! + field: SearchableUserAggregateField! } type SearchableUserConnection { - items: [User] + items: [User!]! nextToken: String total: Int - aggregateItems: [SearchableAggregateResult] + aggregateItems: [SearchableAggregateResult!]! } input ModelStringInput { @@ -1337,7 +1485,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -1397,7 +1545,7 @@ type Subscription { } type ModelUserConnection { - items: [User] + items: [User!]! nextToken: String } @@ -1528,12 +1676,6 @@ input SearchablePostSortInput { direction: SearchableSortDirection } -input SearchablePostAggregationInput { - name: String! - type: SearchableAggregateType! - field: String! -} - enum SearchableAggregateType { terms avg @@ -1542,11 +1684,24 @@ enum SearchableAggregateType { sum } +enum SearchablePostAggregateField { + id + title + createdAt + updatedAt +} + +input SearchablePostAggregationInput { + name: String! + type: SearchableAggregateType! + field: SearchablePostAggregateField! +} + type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int - aggregateItems: [SearchableAggregateResult] + aggregateItems: [SearchableAggregateResult!]! } type SearchableAggregateResult { @@ -1667,7 +1822,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -1803,12 +1958,6 @@ input SearchablePostSortInput { direction: SearchableSortDirection } -input SearchablePostAggregationInput { - name: String! - type: SearchableAggregateType! - field: String! -} - enum SearchableAggregateType { terms avg @@ -1817,11 +1966,24 @@ enum SearchableAggregateType { sum } +enum SearchablePostAggregateField { + id + title + createdAt + updatedAt +} + +input SearchablePostAggregationInput { + name: String! + type: SearchableAggregateType! + field: SearchablePostAggregateField! +} + type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int - aggregateItems: [SearchableAggregateResult] + aggregateItems: [SearchableAggregateResult!]! } type SearchableAggregateResult { @@ -1942,7 +2104,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -2093,12 +2255,6 @@ input SearchablePostSortInput { direction: SearchableSortDirection } -input SearchablePostAggregationInput { - name: String! - type: SearchableAggregateType! - field: String! -} - enum SearchableAggregateType { terms avg @@ -2107,11 +2263,24 @@ enum SearchableAggregateType { sum } +enum SearchablePostAggregateField { + id + title + createdAt + updatedAt +} + +input SearchablePostAggregationInput { + name: String! + type: SearchableAggregateType! + field: SearchablePostAggregateField! +} + type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int - aggregateItems: [SearchableAggregateResult] + aggregateItems: [SearchableAggregateResult!]! } type SearchableAggregateResult { @@ -2232,7 +2401,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } diff --git a/packages/amplify-graphql-searchable-transformer/src/definitions.ts b/packages/amplify-graphql-searchable-transformer/src/definitions.ts index 2aa3c3c5897..65042e7e54a 100644 --- a/packages/amplify-graphql-searchable-transformer/src/definitions.ts +++ b/packages/amplify-graphql-searchable-transformer/src/definitions.ts @@ -194,6 +194,28 @@ export function makeSearchableXSortableFieldsEnumObject(obj: ObjectTypeDefinitio }; } +export function makeSearchableXAggregateFieldEnumObject(obj: ObjectTypeDefinitionNode): EnumTypeDefinitionNode { + const name = graphqlName(`Searchable${obj.name.value}AggregateField`); + assert(obj.fields); + const values: EnumValueDefinitionNode[] = obj.fields + .filter((field: FieldDefinitionNode) => isScalar(field.type)) + .map((field: FieldDefinitionNode) => ({ + kind: Kind.ENUM_VALUE_DEFINITION, + name: field.name, + directives: [], + })); + + return { + kind: Kind.ENUM_TYPE_DEFINITION, + name: { + kind: 'Name', + value: name, + }, + values, + directives: [], + }; +} + export function makeSearchableXSortInputObject(obj: ObjectTypeDefinitionNode): InputObjectTypeDefinitionNode { const name = graphqlName(`Searchable${obj.name.value}SortInput`); return { @@ -278,7 +300,7 @@ export function makeSearchableXAggregationInputObject(obj: ObjectTypeDefinitionN { kind: Kind.INPUT_VALUE_DEFINITION, name: { kind: 'Name', value: 'field' }, - type: makeNonNullType(makeNamedType('String')), + type: makeNonNullType(makeNamedType(`Searchable${obj.name.value}AggregateField`)), directives: [], }, ], diff --git a/packages/amplify-graphql-searchable-transformer/src/generate-resolver-vtl.ts b/packages/amplify-graphql-searchable-transformer/src/generate-resolver-vtl.ts index ccef9dc1ca9..f9a84469383 100644 --- a/packages/amplify-graphql-searchable-transformer/src/generate-resolver-vtl.ts +++ b/packages/amplify-graphql-searchable-transformer/src/generate-resolver-vtl.ts @@ -17,13 +17,19 @@ import { Expression, bool, methodCall, + isNullOrEmpty, + not, } from 'graphql-mapping-template'; import { ResourceConstants } from 'graphql-transformer-common'; +const authFilter = ref('ctx.stash.authFilter'); + export function requestTemplate(primaryKey: string, nonKeywordFields: Expression[], includeVersion: boolean = false, type: string): string { return print( compoundExpression([ set(ref('indexPath'), str(`/${type.toLowerCase()}/doc/_search`)), + set(ref('allowedAggFields'), methodCall(ref('util.defaultIfNull'), ref('ctx.stash.allowedAggFields'), list([]))), + set(ref('aggFieldsFilterMap'), methodCall(ref('util.defaultIfNull'), ref('ctx.stash.aggFieldsFilterMap'), obj({}))), set(ref('nonKeywordFields'), list(nonKeywordFields)), set(ref('sortValues'), list([])), set(ref('aggregateValues'), obj({})), @@ -53,30 +59,66 @@ export function requestTemplate(primaryKey: string, nonKeywordFields: Expression set(ref('sortField'), ref('util.toJson("${sortItem.field}.keyword")')), ), ), - set(ref('sortDirection'), ref('util.toJson({"order": $sortItem.direction})')), + ifElse( + ref('util.isNullOrEmpty($sortItem.direction)'), + set(ref('sortDirection'), ref('util.toJson({"order": "desc"})')), + set(ref('sortDirection'), ref('util.toJson({"order": $sortItem.direction})')), + ), qref('$sortValues.add("{$sortField: $sortDirection}")'), ]), ), forEach(ref('aggItem'), ref('context.args.aggregates'), [ + raw( + '#if( $allowedAggFields.contains($aggItem.field) )\n' + + ' #set( $aggFilter = { "match_all": {} } )\n' + + ' #elseif( $aggFieldsFilterMap.containsKey($aggItem.field) )\n' + + ' #set( $aggFilter = { "bool": { "should": $aggFieldsFilterMap.get($aggItem.field) } } )\n' + + ' #else\n' + + ' $util.error("Unauthorized to run aggregation on field: ${aggItem.field}", "Unauthorized")\n' + + ' #end', + ), ifElse( ref('nonKeywordFields.contains($aggItem.field)'), - qref('$aggregateValues.put("$aggItem.name", {"$aggItem.type": {"field": "$aggItem.field"}})'), - qref('$aggregateValues.put("$aggItem.name", {"$aggItem.type": {"field": "${aggItem.field}.keyword"}})'), + qref( + '$aggregateValues.put("$aggItem.name", { "filter": $aggFilter, "aggs": { "$aggItem.name": { "$aggItem.type": { "field": "$aggItem.field" }}} })', + ), + qref( + '$aggregateValues.put("$aggItem.name", { "filter": $aggFilter, "aggs": { "$aggItem.name": { "$aggItem.type": { "field": "${aggItem.field}.keyword" }}} })', + ), ), ]), + ifElse( + not(isNullOrEmpty(authFilter)), + compoundExpression([ + set(ref('filter'), authFilter), + iff( + not(isNullOrEmpty(ref('ctx.args.filter'))), + set( + ref('filter'), + obj({ + bool: obj({ + must: list([ + ref('ctx.stash.authFilter'), + ref('util.parseJson($util.transform.toElasticsearchQueryDSL($ctx.args.filter))'), + ]), + }), + }), + ), + ), + ]), + iff( + not(isNullOrEmpty(ref('ctx.args.filter'))), + set(ref('filter'), ref('util.parseJson($util.transform.toElasticsearchQueryDSL($ctx.args.filter))')), + ), + ), + iff(isNullOrEmpty(ref('filter')), set(ref('filter'), obj({ match_all: obj({}) }))), SearchableMappingTemplate.searchTemplate({ path: str('$indexPath'), size: ifElse(ref('context.args.limit'), ref('context.args.limit'), int(ResourceConstants.DEFAULT_SEARCHABLE_PAGE_LIMIT), true), search_after: ref('util.base64Decode($context.args.nextToken)'), from: ref('context.args.from'), version: bool(includeVersion), - query: ifElse( - ref('context.args.filter'), - ref('util.transform.toElasticsearchQueryDSL($ctx.args.filter)'), - obj({ - match_all: obj({}), - }), - ), + query: methodCall(ref('util.toJson'), ref('filter')), sort: ref('sortValues'), aggs: ref('util.toJson($aggregateValues)'), }), @@ -96,22 +138,23 @@ export function responseTemplate(includeVersion = false) { forEach(ref('aggItem'), ref('context.result.aggregations.keySet()'), [ set(ref('aggResult'), obj({})), set(ref('aggResultValue'), obj({})), + set(ref('currentAggItem'), ref('ctx.result.aggregations.get($aggItem)')), qref('$aggResult.put("name", $aggItem)'), iff( - raw('!$util.isNullOrEmpty($context.result.aggregations)'), + raw('!$util.isNullOrEmpty($currentAggItem)'), compoundExpression([ iff( - raw('!$util.isNullOrEmpty($context.result.aggregations.get($aggItem).buckets)'), + raw('!$util.isNullOrEmpty($currentAggItem.get($aggItem).buckets)'), compoundExpression([ qref('$aggResultValue.put("__typename", "SearchableAggregateBucketResult")'), - qref('$aggResultValue.put("buckets", $context.result.aggregations.get($aggItem).buckets)'), + qref('$aggResultValue.put("buckets", $currentAggItem.get($aggItem).buckets)'), ]), ), iff( - raw('!$util.isNullOrEmpty($context.result.aggregations.get($aggItem).value)'), + raw('!$util.isNullOrEmpty($currentAggItem.get($aggItem).value)'), compoundExpression([ qref('$aggResultValue.put("__typename", "SearchableAggregateScalarResult")'), - qref('$aggResultValue.put("value", $context.result.aggregations.get($aggItem).value)'), + qref('$aggResultValue.put("value", $currentAggItem.get($aggItem).value)'), ]), ), ]), diff --git a/packages/amplify-graphql-searchable-transformer/src/graphql-searchable-transformer.ts b/packages/amplify-graphql-searchable-transformer/src/graphql-searchable-transformer.ts index a94a93ee74e..24162f2a57b 100644 --- a/packages/amplify-graphql-searchable-transformer/src/graphql-searchable-transformer.ts +++ b/packages/amplify-graphql-searchable-transformer/src/graphql-searchable-transformer.ts @@ -34,6 +34,7 @@ import { makeSearchableSortDirectionEnumObject, makeSearchableXFilterInputObject, makeSearchableXSortableFieldsEnumObject, + makeSearchableXAggregateFieldEnumObject, makeSearchableXSortInputObject, makeSearchableXAggregationInputObject, makeSearchableAggregateTypeEnumObject, @@ -138,7 +139,7 @@ export class SearchableModelTransformer extends TransformerPluginBase { MappingTemplate.s3MappingTemplateFromString(responseTemplate(false), `${typeName}.${def.fieldName}.res.vtl`), ); resolver.mapToStack(stack); - context.resolvers.addResolver(type, def.fieldName, resolver); + context.resolvers.addResolver(typeName, def.fieldName, resolver); } createStackOutputs(stack, domain.domainEndpoint, context.api.apiId, domain.domainArn); @@ -204,12 +205,12 @@ export class SearchableModelTransformer extends TransformerPluginBase { // Create TableXConnection type with items and nextToken let connectionTypeExtension = blankObjectExtension(searchableXConnectionName); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [ - makeField('items', [], makeListType(makeNamedType(definition.name.value))), + makeField('items', [], makeNonNullType(makeListType(makeNonNullType(makeNamedType(definition.name.value))))), ]); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [ makeField('nextToken', [], makeNamedType('String')), makeField('total', [], makeNamedType('Int')), - makeField('aggregateItems', [], makeListType(makeNamedType(`SearchableAggregateResult`))), + makeField('aggregateItems', [], makeNonNullType(makeListType(makeNonNullType(makeNamedType(`SearchableAggregateResult`))))), ]); ctx.output.addObjectExtension(connectionTypeExtension); } @@ -339,15 +340,20 @@ export class SearchableModelTransformer extends TransformerPluginBase { ctx.output.addInput(searchableXSortableInputDirection); } - if (!ctx.output.hasType(`Searchable${definition.name.value}AggregationInput`)) { - const searchableXAggregationInputDirection = makeSearchableXAggregationInputObject(definition); - ctx.output.addInput(searchableXAggregationInputDirection); - } - if (!ctx.output.hasType('SearchableAggregateType')) { const searchableAggregateTypeEnum = makeSearchableAggregateTypeEnumObject(); ctx.output.addEnum(searchableAggregateTypeEnum); } + + if (!ctx.output.hasType(`Searchable${definition.name.value}AggregateField`)) { + const searchableXAggregationField = makeSearchableXAggregateFieldEnumObject(definition); + ctx.output.addEnum(searchableXAggregationField); + } + + if (!ctx.output.hasType(`Searchable${definition.name.value}AggregationInput`)) { + const searchableXAggregationInput = makeSearchableXAggregationInputObject(definition); + ctx.output.addInput(searchableXAggregationInput); + } } } diff --git a/packages/amplify-graphql-transformer-core/src/config/index.ts b/packages/amplify-graphql-transformer-core/src/config/index.ts index 7cfa3497922..58b0911cbdb 100644 --- a/packages/amplify-graphql-transformer-core/src/config/index.ts +++ b/packages/amplify-graphql-transformer-core/src/config/index.ts @@ -1,21 +1,2 @@ export { TransformerProjectConfig } from './project-config'; -export { - TransformConfig, - ConflictDetectionType, - ConflictHandlerType, - ResolverConfig, - SyncConfig, - SyncConfigLambda, - SyncConfigOptimistic, - SyncConfigServer, - LambdaConflictHandler, - AppSyncAuthConfiguration, - AppSyncAuthConfigurationAPIKeyEntry, - AppSyncAuthConfigurationEntry, - AppSyncAuthConfigurationIAMEntry, - ApiKeyConfig, - AppSyncAuthConfigurationOIDCEntry, - AppSyncAuthConfigurationUserPoolEntry, - AppSyncAuthMode, - UserPoolConfig, -} from './transformer-config'; +export * from './transformer-config'; diff --git a/packages/amplify-graphql-transformer-core/src/config/transformer-config.ts b/packages/amplify-graphql-transformer-core/src/config/transformer-config.ts index c98cfad4f3d..1b044d1a36e 100644 --- a/packages/amplify-graphql-transformer-core/src/config/transformer-config.ts +++ b/packages/amplify-graphql-transformer-core/src/config/transformer-config.ts @@ -4,56 +4,13 @@ export interface TransformMigrationConfig { }; } -// Auth Config -export type AppSyncAuthMode = 'API_KEY' | 'AMAZON_COGNITO_USER_POOLS' | 'AWS_IAM' | 'OPENID_CONNECT'; -export type AppSyncAuthConfiguration = { - defaultAuthentication: AppSyncAuthConfigurationEntry; - additionalAuthenticationProviders: Array; -}; - -export type AppSyncAuthConfigurationEntry = - | AppSyncAuthConfigurationUserPoolEntry - | AppSyncAuthConfigurationAPIKeyEntry - | AppSyncAuthConfigurationIAMEntry - | AppSyncAuthConfigurationOIDCEntry; -export type AppSyncAuthConfigurationAPIKeyEntry = { - authenticationType: 'API_KEY'; - apiKeyConfig: ApiKeyConfig; -}; -export type AppSyncAuthConfigurationUserPoolEntry = { - authenticationType: 'AMAZON_COGNITO_USER_POOLS'; - userPoolConfig: UserPoolConfig; -}; -export type AppSyncAuthConfigurationIAMEntry = { - authenticationType: 'AWS_IAM'; -}; - -export type AppSyncAuthConfigurationOIDCEntry = { - authenticationType: 'OPENID_CONNECT'; - openIDConnectConfig: OpenIDConnectConfig; -}; - -export type ApiKeyConfig = { - description?: string; - apiKeyExpirationDays: number; -}; -export type UserPoolConfig = { - userPoolId: string; -}; -export type OpenIDConnectConfig = { - name: string; - issuerUrl: string; - clientId?: string; - iatTTL?: number; - authTTL?: number; -}; - // Sync Config export const enum ConflictHandlerType { OPTIMISTIC = 'OPTIMISTIC_CONCURRENCY', AUTOMERGE = 'AUTOMERGE', LAMBDA = 'LAMBDA', } + export type ConflictDetectionType = 'VERSION' | 'NONE'; export type SyncConfigOptimistic = { ConflictDetection: ConflictDetectionType; @@ -79,10 +36,6 @@ export type ResolverConfig = { project?: SyncConfig; models?: Record; }; -/** - * The transform config is specified in transform.conf.json within an Amplify - * API project directory. - */ export interface TransformConfig { /** * The transform library uses a "StackMapping" to determine which stack @@ -95,7 +48,9 @@ export interface TransformConfig { * overrides to get specific behavior out of the transformer. Users may * override the default stack mapping to customize behavior. */ - StackMapping?: Record; + StackMapping?: { + [resourceId: string]: string; + }; /** * Provide build time options to GraphQL Transformer constructor functions. @@ -103,9 +58,10 @@ export interface TransformConfig { * need to be set at build time. E.G. DeletionPolicies cannot depend on parameters. */ TransformerOptions?: { - [transformer: string]: Record; + [transformer: string]: { + [option: string]: any; + }; }; - /** * Object which states info about a resolver's configuration * Such as sync configuration for appsync local support diff --git a/packages/amplify-graphql-transformer-core/src/graphql-api.ts b/packages/amplify-graphql-transformer-core/src/graphql-api.ts index 71d9fa52dc8..21bcaf042b1 100644 --- a/packages/amplify-graphql-transformer-core/src/graphql-api.ts +++ b/packages/amplify-graphql-transformer-core/src/graphql-api.ts @@ -113,6 +113,7 @@ export class IamResource implements APIIAMResourceProvider { export type TransformerAPIProps = GraphqlApiProps & { readonly createApiKey?: boolean; readonly host?: TransformHostProvider; + readonly sandboxModeEnabled?: boolean; }; export class GraphQLApi extends GraphqlApiBase implements GraphQLAPIProvider { /** @@ -125,7 +126,7 @@ export class GraphQLApi extends GraphqlApiBase implements GraphQLAPIProvider { * The TransformHost object provides resource creation utilities in AWS * such as a LambdaDataSource or a DynamoDBDataSource */ - public readonly host: TransformHostProvider + public readonly host: TransformHostProvider; /** * the ARN of the API @@ -161,6 +162,11 @@ export class GraphQLApi extends GraphqlApiBase implements GraphQLAPIProvider { */ public readonly apiKey?: string; + /** + * Global Sandbox Mode for GraphQL API + */ + public readonly sandboxModeEnabled?: boolean; + private schemaResource: CfnGraphQLSchema; private api: CfnGraphQLApi; private apiKeyResource?: CfnApiKey; @@ -198,7 +204,9 @@ export class GraphQLApi extends GraphqlApiBase implements GraphQLAPIProvider { this.schema = props.schema ?? new TransformerSchema(); this.schemaResource = this.schema.bind(this); - if (props.createApiKey && modes.some(mode => mode.authorizationType === AuthorizationType.API_KEY)) { + const hasApiKey = modes.some(mode => mode.authorizationType === AuthorizationType.API_KEY); + + if (props.createApiKey && hasApiKey) { const config = modes.find((mode: AuthorizationMode) => { return mode.authorizationType === AuthorizationType.API_KEY && mode.apiKeyConfig; })?.apiKeyConfig; @@ -207,13 +215,14 @@ export class GraphQLApi extends GraphqlApiBase implements GraphQLAPIProvider { this.apiKey = this.apiKeyResource.attrApiKey; } + if (hasApiKey && props.sandboxModeEnabled) this.sandboxModeEnabled = true; + if (props.host) { this.host = props.host; this.host.setAPI(this); - } - else { + } else { this.host = new DefaultTransformHost({ - api: this + api: this, }); } } diff --git a/packages/amplify-graphql-transformer-core/src/index.ts b/packages/amplify-graphql-transformer-core/src/index.ts index 6ddfd7b974a..3ab2126bd4f 100644 --- a/packages/amplify-graphql-transformer-core/src/index.ts +++ b/packages/amplify-graphql-transformer-core/src/index.ts @@ -8,25 +8,27 @@ export { ConflictHandlerType, ResolverConfig, SyncConfig, - SyncConfigLambda, SyncConfigOptimistic, SyncConfigServer, + SyncConfigLambda, TransformConfig, TransformerProjectConfig, - AppSyncAuthConfiguration, - AppSyncAuthConfigurationAPIKeyEntry, - AppSyncAuthConfigurationEntry, - AppSyncAuthConfigurationIAMEntry, - ApiKeyConfig, - AppSyncAuthConfigurationOIDCEntry, - AppSyncAuthConfigurationUserPoolEntry, - AppSyncAuthMode, - UserPoolConfig, - LambdaConflictHandler, } from './config/index'; -export { collectDirectives, collectDirectivesByTypeNames, DirectiveWrapper } from './utils'; +export { + collectDirectives, + collectDirectivesByTypeNames, + DirectiveWrapper, + IAM_AUTH_ROLE_PARAMETER, + IAM_UNAUTH_ROLE_PARAMETER, +} from './utils'; export * from './errors'; -export { TransformerModelBase, TransformerModelEnhancerBase, TransformerPluginBase } from './transformation/transformer-plugin-base'; +export { + TransformerModelBase, + TransformerModelEnhancerBase, + TransformerPluginBase, + TransformerAuthBase, +} from './transformation/transformer-plugin-base'; +export { TransformerResolver } from './transformer-context'; /** * Returns the extra set of directives that are supported by AppSync service */ diff --git a/packages/amplify-graphql-transformer-core/src/transform-host.ts b/packages/amplify-graphql-transformer-core/src/transform-host.ts index a72f94cf87d..5ede7441a0a 100644 --- a/packages/amplify-graphql-transformer-core/src/transform-host.ts +++ b/packages/amplify-graphql-transformer-core/src/transform-host.ts @@ -26,6 +26,7 @@ export interface DefaultTransformHostOptions { export class DefaultTransformHost implements TransformHostProvider { private dataSources: Map = new Map(); + private resolvers: Map = new Map(); private api: GraphQLApi; public constructor(options: DefaultTransformHostOptions) { @@ -45,6 +46,16 @@ export class DefaultTransformHost implements TransformHostProvider { } }; + public hasResolver = (typeName: string, fieldName: string) => { + return this.resolvers.has(`${typeName}:${fieldName}`); + }; + + public getResolver = (typeName: string, fieldName: string): CfnResolver | void => { + if (this.resolvers.has(`${typeName}:${fieldName}`)) { + return this.resolvers.get(`${typeName}:${fieldName}`); + } + }; + addSearchableDataSource( name: string, awsRegion: string, @@ -125,7 +136,7 @@ export class DefaultTransformHost implements TransformHostProvider { dataSourceName?: string, pipelineConfig?: string[], stack?: Stack, - ) { + ): CfnResolver { if (dataSourceName && !Token.isUnresolved(dataSourceName) && !this.dataSources.has(dataSourceName)) { throw new Error(`DataSource ${dataSourceName} is missing in the API`); } @@ -168,6 +179,7 @@ export class DefaultTransformHost implements TransformHostProvider { }, }); this.api.addSchemaDependency(resolver); + this.resolvers.set(`${typeName}:${fieldName}`, resolver); return resolver; } else { throw new Error('Resolver needs either dataSourceName or pipelineConfig to be passed'); diff --git a/packages/amplify-graphql-transformer-core/src/transformation/sync-utils.ts b/packages/amplify-graphql-transformer-core/src/transformation/sync-utils.ts index 7d207ffa4e2..a703a99e82b 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/sync-utils.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/sync-utils.ts @@ -90,7 +90,7 @@ export function syncDataSourceConfig(): DeltaSyncConfig { export function validateResolverConfigForType(ctx: TransformerSchemaVisitStepContextProvider, typeName: string): void { const resolverConfig = ctx.getResolverConfig(); const typeResolverConfig = resolverConfig?.models?.[typeName]; - if (!typeResolverConfig || !typeResolverConfig.ConflictDetection || !typeResolverConfig.ConflictHandler) { + if (typeResolverConfig && (!typeResolverConfig.ConflictDetection || !typeResolverConfig.ConflictHandler)) { console.warn(`Invalid resolverConfig for type ${typeName}. Using the project resolverConfig instead.`); } } diff --git a/packages/amplify-graphql-transformer-core/src/transformation/transform.ts b/packages/amplify-graphql-transformer-core/src/transformation/transform.ts index 2571efec502..0ec4d164fc1 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/transform.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/transform.ts @@ -4,6 +4,7 @@ import { GraphQLAPIProvider, TransformerPluginProvider, TransformHostProvider, + AppSyncAuthConfiguration, } from '@aws-amplify/graphql-transformer-interfaces'; import { AuthorizationMode, AuthorizationType } from '@aws-cdk/aws-appsync'; import { App, Aws, CfnOutput, Fn } from '@aws-cdk/core'; @@ -23,13 +24,13 @@ import { TypeExtensionNode, UnionTypeDefinitionNode, } from 'graphql'; -import { AppSyncAuthConfiguration, TransformConfig } from '../config/transformer-config'; import { InvalidTransformerError, SchemaValidationError, UnknownDirectiveError } from '../errors'; import { GraphQLApi } from '../graphql-api'; import { TransformerContext } from '../transformer-context'; import { TransformerOutput } from '../transformer-context/output'; import { StackManager } from '../transformer-context/stack-manager'; -import { adoptAuthModes } from '../utils/authType'; +import { adoptAuthModes, IAM_AUTH_ROLE_PARAMETER, IAM_UNAUTH_ROLE_PARAMETER } from '../utils/authType'; +import { TransformConfig } from '../config'; import * as SyncUtils from './sync-utils'; import Template, { DeploymentResources } from './types'; @@ -42,7 +43,7 @@ import { matchInputFieldDirective, sortTransformerPlugins, } from './utils'; -import { validateModelSchema } from './validation'; +import { validateModelSchema, validateAuthModes } from './validation'; // eslint-disable-next-line @typescript-eslint/ban-types function isFunction(obj: any): obj is Function { @@ -70,6 +71,7 @@ export interface GraphQLTransformOptions { readonly stacks?: Record; readonly featureFlags?: FeatureFlagProvider; readonly host?: TransformHostProvider; + readonly sandboxModeEnabled?: boolean; } export type StackMapping = { [resourceId: string]: string }; export class GraphQLTransform { @@ -103,6 +105,8 @@ export class GraphQLTransform { additionalAuthenticationProviders: [], }; + validateAuthModes(this.authConfig); + this.buildParameters = options.buildParameters || {}; this.stackMappingOverrides = options.stackMapping || {}; this.transformConfig = options.transformConfig || {}; @@ -124,6 +128,7 @@ export class GraphQLTransform { this.app, parsedDocument, this.stackMappingOverrides, + this.authConfig, this.options.featureFlags, this.transformConfig.ResolverConfig, ); @@ -136,6 +141,7 @@ export class GraphQLTransform { aws_iam: true, aws_oidc: true, aws_cognito_user_pools: true, + allow_public_data_access_with_api_key: true, deprecated: true, }, ); @@ -143,6 +149,7 @@ export class GraphQLTransform { for (const transformer of this.transformers) { allModelDefinitions = allModelDefinitions.concat(...transformer.typeDefinitions, transformer.directive); } + const errors = validateModelSchema({ kind: Kind.DOCUMENT, definitions: allModelDefinitions, @@ -214,7 +221,6 @@ export class GraphQLTransform { // Synth the API and make it available to allow transformer plugins to manipulate the API const stackManager = context.stackManager as StackManager; const output: TransformerOutput = context.output as TransformerOutput; - const api = this.generateGraphQlApi(stackManager, output); // generate resolvers @@ -258,6 +264,7 @@ export class GraphQLTransform { name: `${apiName}-${envName.valueAsString}`, authorizationConfig, host: this.options.host, + sandboxModeEnabled: this.options.sandboxModeEnabled, }); const authModes = [authorizationConfig.defaultAuthorization, ...(authorizationConfig.additionalAuthorizationModes || [])].map( mode => mode?.authorizationType, @@ -286,6 +293,11 @@ export class GraphQLTransform { }); } + if (authModes.includes(AuthorizationType.IAM)) { + stackManager.addParameter(IAM_AUTH_ROLE_PARAMETER, { type: 'String' }); + stackManager.addParameter(IAM_UNAUTH_ROLE_PARAMETER, { type: 'String' }); + } + new CfnOutput(rootStack, 'GraphQLAPIIdOutput', { value: api.apiId, description: 'Your GraphQL API ID.', diff --git a/packages/amplify-graphql-transformer-core/src/transformation/transformer-plugin-base.ts b/packages/amplify-graphql-transformer-core/src/transformation/transformer-plugin-base.ts index f0f9c86d910..9dad23a4873 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/transformer-plugin-base.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/transformer-plugin-base.ts @@ -10,6 +10,7 @@ import { DataSourceInstance, TransformerPluginProvider, TransformerModelEnhancementProvider, + TransformerAuthProvider, } from '@aws-amplify/graphql-transformer-interfaces'; import { @@ -176,3 +177,9 @@ export abstract class TransformerModelEnhancerBase extends TransformerModelBase super(name, doc, type); } } + +export abstract class TransformerAuthBase extends TransformerPluginBase implements TransformerAuthProvider { + constructor(name: string, doc: DocumentNode | string, type: TransformerPluginType = TransformerPluginType.AUTH) { + super(name, doc, type); + } +} diff --git a/packages/amplify-graphql-transformer-core/src/transformation/utils.ts b/packages/amplify-graphql-transformer-core/src/transformation/utils.ts index e7cb2863963..24523f2256c 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/utils.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/utils.ts @@ -171,6 +171,7 @@ export function sortTransformerPlugins(plugins: TransformerPluginProvider[]): Tr TransformerPluginType.DATA_SOURCE_PROVIDER, TransformerPluginType.DATA_SOURCE_ENHANCER, TransformerPluginType.GENERIC, + TransformerPluginType.AUTH, ]; return plugins.sort((a, b) => { const aIdx = SORT_ORDER.indexOf(a.pluginType); diff --git a/packages/amplify-graphql-transformer-core/src/transformation/validation.ts b/packages/amplify-graphql-transformer-core/src/transformation/validation.ts index 4e94fd7b6e4..a74afcd62bc 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/validation.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/validation.ts @@ -54,6 +54,9 @@ import { NoUndefinedVariables } from 'graphql/validation/rules/NoUndefinedVariab import { NoUnusedVariables } from 'graphql/validation/rules/NoUnusedVariables'; import { UniqueDirectivesPerLocation } from 'graphql/validation/rules/UniqueDirectivesPerLocation'; +// AuthMode Types +import { AppSyncAuthConfiguration, AppSyncAuthMode } from '@aws-amplify/graphql-transformer-interfaces'; + /** * This set includes all validation rules defined by the GraphQL spec. * @@ -109,6 +112,7 @@ directive @aws_api_key on FIELD_DEFINITION | OBJECT directive @aws_iam on FIELD_DEFINITION | OBJECT directive @aws_oidc on FIELD_DEFINITION | OBJECT directive @aws_cognito_user_pools(cognito_groups: [String!]) on FIELD_DEFINITION | OBJECT +directive @allow_public_data_access_with_api_key(in: [String!]) on OBJECT # Allows transformer libraries to deprecate directive arguments. directive @deprecated(reason: String) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ENUM | ENUM_VALUE @@ -142,3 +146,21 @@ export const validateModelSchema = (doc: DocumentNode) => { const schema = buildASTSchema(fullDocument); return validate(schema, fullDocument, specifiedRules); }; + +export const validateAuthModes = (authConfig: AppSyncAuthConfiguration) => { + let additionalAuthModes: AppSyncAuthMode[] = []; + + if (authConfig.additionalAuthenticationProviders) { + additionalAuthModes = authConfig.additionalAuthenticationProviders.map(p => p.authenticationType).filter(t => !!t); + } + + const authModes: AppSyncAuthMode[] = [...additionalAuthModes, authConfig.defaultAuthentication.authenticationType]; + + for (let i = 0; i < authModes.length; i++) { + const mode = authModes[i]; + + if (mode !== 'API_KEY' && mode !== 'AMAZON_COGNITO_USER_POOLS' && mode !== 'AWS_IAM' && mode !== 'OPENID_CONNECT') { + throw new Error(`Invalid auth mode ${mode}`); + } + } +}; diff --git a/packages/amplify-graphql-transformer-core/src/transformer-context/datasource.ts b/packages/amplify-graphql-transformer-core/src/transformer-context/datasource.ts index ded8e6cd888..544438499a3 100644 --- a/packages/amplify-graphql-transformer-core/src/transformer-context/datasource.ts +++ b/packages/amplify-graphql-transformer-core/src/transformer-context/datasource.ts @@ -23,4 +23,8 @@ export class TransformerDataSourceManager implements TransformerDataSourceManage collectDataSources = (): Readonly> => { return this.dataSourceMap; }; + + has = (name: string): boolean => { + return this.dataSourceMap.has(name); + }; } diff --git a/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts b/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts index 59f32bab104..266dc3a4f79 100644 --- a/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts +++ b/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts @@ -5,7 +5,9 @@ import { TransformerContextOutputProvider, TransformerContextProvider, TransformerDataSourceManagerProvider, + AppSyncAuthConfiguration, } from '@aws-amplify/graphql-transformer-interfaces'; +import { TransformerContextMetadataProvider } from '@aws-amplify/graphql-transformer-interfaces/src/transformer-context/transformer-context-provider'; import { App } from '@aws-cdk/core'; import { DocumentNode } from 'graphql'; import { ResolverConfig } from '../config/transformer-config'; @@ -17,6 +19,25 @@ import { TransformerContextProviderRegistry } from './provider-registry'; import { ResolverManager } from './resolver'; import { TransformerResourceHelper } from './resource-helper'; import { StackManager } from './stack-manager'; +export { TransformerResolver } from './resolver'; +export class TransformerContextMetadata implements TransformerContextMetadataProvider { + /** + * Used by transformers to pass information between one another. + */ + private metadata: { [key: string]: any } = new Map(); + + public get(key: string): T | undefined { + return this.metadata[key] as T; + } + + public set(key: string, val: T): void { + this.metadata[key] = val; + } + + public has(key: string) { + return this.metadata[key] !== undefined; + } +} export class TransformerContext implements TransformerContextProvider { public readonly output: TransformerContextOutputProvider; @@ -27,12 +48,15 @@ export class TransformerContext implements TransformerContextProvider { public readonly resourceHelper: TransformerResourceHelper; public readonly featureFlags: FeatureFlagProvider; public _api?: GraphQLAPIProvider; + public readonly authConfig: AppSyncAuthConfiguration; private resolverConfig: ResolverConfig | undefined; + public metadata: TransformerContextMetadata; constructor( app: App, public readonly inputDocument: DocumentNode, stackMapping: Record, + authConfig: AppSyncAuthConfiguration, featureFlags?: FeatureFlagProvider, resolverConfig?: ResolverConfig, ) { @@ -42,9 +66,11 @@ export class TransformerContext implements TransformerContextProvider { this.providerRegistry = new TransformerContextProviderRegistry(); const stackManager = new StackManager(app, stackMapping); this.stackManager = stackManager; + this.authConfig = authConfig; this.resourceHelper = new TransformerResourceHelper(stackManager); this.featureFlags = featureFlags ?? new NoopFeatureFlagProvider(); this.resolverConfig = resolverConfig; + this.metadata = new TransformerContextMetadata(); } /** diff --git a/packages/amplify-graphql-transformer-core/src/transformer-context/resolver.ts b/packages/amplify-graphql-transformer-core/src/transformer-context/resolver.ts index ec0dc0059ba..00e8128bd0a 100644 --- a/packages/amplify-graphql-transformer-core/src/transformer-context/resolver.ts +++ b/packages/amplify-graphql-transformer-core/src/transformer-context/resolver.ts @@ -7,13 +7,14 @@ import { TransformerResolverProvider, TransformerResolversManagerProvider, } from '@aws-amplify/graphql-transformer-interfaces'; -import { CfnFunctionConfiguration } from '@aws-cdk/aws-appsync'; -import { isResolvableObject, Stack } from '@aws-cdk/core'; +import { AuthorizationType, CfnFunctionConfiguration } from '@aws-cdk/aws-appsync'; +import { isResolvableObject, Stack, CfnParameter } from '@aws-cdk/core'; import assert from 'assert'; import { toPascalCase } from 'graphql-transformer-common'; import { dedent } from 'ts-dedent'; import { MappingTemplate, S3MappingTemplate } from '../cdk-compat'; import * as SyncUtils from '../transformation/sync-utils'; +import { IAM_AUTH_ROLE_PARAMETER, IAM_UNAUTH_ROLE_PARAMETER } from '../utils'; import { StackManager } from './stack-manager'; type Slot = { @@ -95,6 +96,11 @@ export class ResolverManager implements TransformerResolversManagerProvider { } }; + hasResolver = (typeName: string, fieldName: string): boolean => { + const key = `${typeName}.${fieldName}`; + return this.resolvers.has(key); + }; + removeResolver = (typeName: string, fieldName: string): TransformerResolverProvider => { const key = `${typeName}.${fieldName}`; if (this.resolvers.has(key)) { @@ -234,24 +240,38 @@ export class TransformerResolver implements TransformerResolverProvider { } break; default: - throw new Error('Unknow DataSource type'); + throw new Error('Unknown DataSource type'); } } + let initResolver = dedent` + $util.qr($ctx.stash.put("typeName", "${this.typeName}")) + $util.qr($ctx.stash.put("fieldName", "${this.fieldName}")) + $util.qr($ctx.stash.put("conditions", [])) + $util.qr($ctx.stash.put("metadata", {})) + $util.qr($ctx.stash.metadata.put("dataSourceType", "${dataSourceType}")) + $util.qr($ctx.stash.metadata.put("apiId", "${api.apiId}")) + ${dataSource} + `; + const authModes = [context.authConfig.defaultAuthentication, ...(context.authConfig.additionalAuthenticationProviders || [])].map( + mode => mode?.authenticationType, + ); + if (authModes.includes(AuthorizationType.IAM)) { + const authRoleParameter = (context.stackManager.getParameter(IAM_AUTH_ROLE_PARAMETER) as CfnParameter).valueAsString; + const unauthRoleParameter = (context.stackManager.getParameter(IAM_UNAUTH_ROLE_PARAMETER) as CfnParameter).valueAsString; + initResolver += dedent`\n + $util.qr($ctx.stash.put("authRole", "arn:aws:sts::${ + Stack.of(context.stackManager.rootStack).account + }:assumed-role/${authRoleParameter}/CognitoIdentityCredentials")) + $util.qr($ctx.stash.put("unauthRole", "arn:aws:sts::${ + Stack.of(context.stackManager.rootStack).account + }:assumed-role/${unauthRoleParameter}/CognitoIdentityCredentials")) + `; + } + initResolver += '\n$util.toJson({})'; api.host.addResolver( this.typeName, this.fieldName, - MappingTemplate.inlineTemplateFromString( - dedent` - $util.qr($ctx.stash.put("typeName", "${this.typeName}")) - $util.qr($ctx.stash.put("fieldName", "${this.fieldName}")) - $util.qr($ctx.stash.put("conditions", [])) - $util.qr($ctx.stash.put("metadata", {})) - $util.qr($ctx.stash.metadata.put("dataSourceType", "${dataSourceType}")) - $util.qr($ctx.stash.metadata.put("apiId", "${api.apiId}")) - ${dataSource} - $util.toJson({}) - `, - ), + MappingTemplate.inlineTemplateFromString(initResolver), MappingTemplate.inlineTemplateFromString('$util.toJson($ctx.prev.result)'), undefined, [...requestFns, dataSourceProviderFn, ...responseFns].map(fn => fn.functionId), diff --git a/packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts b/packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts index 003f41ec957..40faf98fe1c 100644 --- a/packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts +++ b/packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts @@ -23,6 +23,7 @@ export class StackManager implements StackManagerProvider { } createStack = (stackName: string): Stack => { const newStack = new TransformerNestedStack(this.rootStack, stackName); + this.stacks.set(stackName, newStack); return newStack; }; diff --git a/packages/amplify-graphql-transformer-core/src/utils/authType.ts b/packages/amplify-graphql-transformer-core/src/utils/authType.ts index 2be8d32a082..945da6dd5db 100644 --- a/packages/amplify-graphql-transformer-core/src/utils/authType.ts +++ b/packages/amplify-graphql-transformer-core/src/utils/authType.ts @@ -1,8 +1,8 @@ import { AuthorizationConfig, AuthorizationMode, AuthorizationType } from '@aws-cdk/aws-appsync'; import { UserPool } from '@aws-cdk/aws-cognito'; import { Duration, Expiration } from '@aws-cdk/core'; -import { AppSyncAuthConfiguration, AppSyncAuthConfigurationEntry, AppSyncAuthMode } from '../config'; import { StackManager } from '../transformer-context/stack-manager'; +import { AppSyncAuthConfiguration, AppSyncAuthConfigurationEntry, AppSyncAuthMode } from '@aws-amplify/graphql-transformer-interfaces'; const authTypeMap: Record = { API_KEY: AuthorizationType.API_KEY, @@ -10,6 +10,10 @@ const authTypeMap: Record = { AWS_IAM: AuthorizationType.IAM, OPENID_CONNECT: AuthorizationType.OIDC, }; + +export const IAM_AUTH_ROLE_PARAMETER = 'authRoleName'; +export const IAM_UNAUTH_ROLE_PARAMETER = 'unauthRoleName'; + export function adoptAuthModes(stack: StackManager, authConfig: AppSyncAuthConfiguration): AuthorizationConfig { return { defaultAuthorization: adoptAuthMode(stack, authConfig.defaultAuthentication), @@ -24,8 +28,10 @@ export function adoptAuthMode(stackManager: StackManager, entry: AppSyncAuthConf return { authorizationType: authType, apiKeyConfig: { - description: entry.apiKeyConfig.description, - expires: Expiration.after(Duration.days(entry.apiKeyConfig.apiKeyExpirationDays)), + description: entry.apiKeyConfig?.description, + expires: entry.apiKeyConfig?.apiKeyExpirationDays + ? Expiration.after(Duration.days(entry.apiKeyConfig.apiKeyExpirationDays)) + : undefined, }, }; case AuthorizationType.USER_POOL: diff --git a/packages/amplify-graphql-transformer-core/src/utils/index.ts b/packages/amplify-graphql-transformer-core/src/utils/index.ts index ab2e7da49c9..7389a33dfb1 100644 --- a/packages/amplify-graphql-transformer-core/src/utils/index.ts +++ b/packages/amplify-graphql-transformer-core/src/utils/index.ts @@ -2,3 +2,4 @@ export { DirectiveWrapper } from './directive-wrapper'; export { collectDirectives, collectDirectivesByTypeNames } from './type-map-utils'; export { stripDirectives } from './strip-directives'; export { DEFAULT_SCHEMA_DEFINITION } from './defaultSchema'; +export { IAM_AUTH_ROLE_PARAMETER, IAM_UNAUTH_ROLE_PARAMETER } from './authType'; diff --git a/packages/amplify-graphql-transformer-interfaces/src/graphql-api-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/graphql-api-provider.ts index 4e7ce993724..6514c3e48a0 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/graphql-api-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/graphql-api-provider.ts @@ -1,6 +1,51 @@ import { CfnResource, Construct, IAsset, IConstruct } from '@aws-cdk/core'; import { Grant, IGrantable, IRole } from '@aws-cdk/aws-iam'; -import {TransformHostProvider} from './transform-host-provider'; +import { TransformHostProvider } from './transform-host-provider'; + +// Auth Config +export type AppSyncAuthMode = 'API_KEY' | 'AMAZON_COGNITO_USER_POOLS' | 'AWS_IAM' | 'OPENID_CONNECT'; +export type AppSyncAuthConfiguration = { + defaultAuthentication: AppSyncAuthConfigurationEntry; + additionalAuthenticationProviders: Array; +}; + +export type AppSyncAuthConfigurationEntry = + | AppSyncAuthConfigurationUserPoolEntry + | AppSyncAuthConfigurationAPIKeyEntry + | AppSyncAuthConfigurationIAMEntry + | AppSyncAuthConfigurationOIDCEntry; +export type AppSyncAuthConfigurationAPIKeyEntry = { + authenticationType: 'API_KEY'; + apiKeyConfig?: ApiKeyConfig; +}; +export type AppSyncAuthConfigurationUserPoolEntry = { + authenticationType: 'AMAZON_COGNITO_USER_POOLS'; + userPoolConfig?: UserPoolConfig; +}; +export type AppSyncAuthConfigurationIAMEntry = { + authenticationType: 'AWS_IAM'; +}; + +export type AppSyncAuthConfigurationOIDCEntry = { + authenticationType: 'OPENID_CONNECT'; + openIDConnectConfig?: OpenIDConnectConfig; +}; + +export interface ApiKeyConfig { + description?: string; + apiKeyExpirationDays: number; + apiKeyExpirationDate?: Date; +} +export interface UserPoolConfig { + userPoolId: string; +} +export interface OpenIDConnectConfig { + name: string; + issuerUrl: string; + clientId?: string; + iatTTL?: number; + authTTL?: number; +} export interface AppSyncFunctionConfigurationProvider extends IConstruct { readonly arn: string; diff --git a/packages/amplify-graphql-transformer-interfaces/src/index.ts b/packages/amplify-graphql-transformer-interfaces/src/index.ts index 5271dfc2c34..36be2b3a84a 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/index.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/index.ts @@ -21,8 +21,9 @@ export { MutationFieldType, QueryFieldType, SubscriptionFieldType, - TransformerModelEnhancementProvider, TransformerModelProvider, + TransformerModelEnhancementProvider, + TransformerAuthProvider, } from './transformer-model-provider'; export { FeatureFlagProvider } from './feature-flag-provider'; @@ -36,6 +37,15 @@ export { InlineMappingTemplateProvider, APIIAMResourceProvider, TemplateType as MappingTemplateType, + AppSyncAuthConfiguration, + AppSyncAuthConfigurationAPIKeyEntry, + AppSyncAuthConfigurationEntry, + AppSyncAuthConfigurationIAMEntry, + ApiKeyConfig, + AppSyncAuthConfigurationOIDCEntry, + AppSyncAuthConfigurationUserPoolEntry, + AppSyncAuthMode, + UserPoolConfig, } from './graphql-api-provider'; export { TransformHostProvider, DynamoDbDataSourceOptions } from './transform-host-provider'; diff --git a/packages/amplify-graphql-transformer-interfaces/src/transform-host-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transform-host-provider.ts index 6ef36c63475..de89ebcea1b 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transform-host-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transform-host-provider.ts @@ -73,4 +73,7 @@ export interface TransformHostProvider { getDataSource: (name: string) => BaseDataSource | void; hasDataSource: (name: string) => boolean; + + getResolver: (typeName: string, fieldName: string) => CfnResolver | void; + hasResolver: (typeName: string, fieldName: string) => boolean; } diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/stack-manager-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/stack-manager-provider.ts index b528c7eeec1..70d1e23842b 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/stack-manager-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/stack-manager-provider.ts @@ -1,6 +1,7 @@ import { CfnParameter, CfnParameterProps, Stack } from '@aws-cdk/core'; export interface StackManagerProvider { + readonly rootStack: Stack; getStack: (stackName: string) => Stack; createStack: (stackName: string) => Stack; hasStack: (stackName: string) => boolean; diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-context-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-context-provider.ts index afaa8317841..1d3c41c133e 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-context-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-context-provider.ts @@ -4,11 +4,18 @@ import { TransformerProviderRegistry } from './transformer-provider-registry'; import { DocumentNode } from 'graphql'; import { TransformerContextOutputProvider } from './transformer-context-output-provider'; import { StackManagerProvider } from './stack-manager-provider'; -import { GraphQLAPIProvider } from '../graphql-api-provider'; +import { AppSyncAuthConfiguration, GraphQLAPIProvider } from '../graphql-api-provider'; import { TransformerResourceProvider } from './resource-resource-provider'; import { FeatureFlagProvider } from '../feature-flag-provider'; +export interface TransformerContextMetadataProvider { + set(key: string, value: T): void; + get(key: string): T | undefined; + has(key: string): boolean; +} + export interface TransformerContextProvider { + metadata: TransformerContextMetadataProvider; resolvers: TransformerResolversManagerProvider; dataSources: TransformerDataSourceManagerProvider; providerRegistry: TransformerProviderRegistry; @@ -19,6 +26,7 @@ export interface TransformerContextProvider { api: GraphQLAPIProvider; resourceHelper: TransformerResourceProvider; featureFlags: FeatureFlagProvider; + authConfig: AppSyncAuthConfiguration; isProjectUsingDataStore(): boolean; getResolverConfig(): ResolverConfig | undefined; @@ -26,15 +34,30 @@ export interface TransformerContextProvider { export type TransformerBeforeStepContextProvider = Pick< TransformerContextProvider, - 'inputDocument' | 'featureFlags' | 'isProjectUsingDataStore' + 'inputDocument' | 'featureFlags' | 'isProjectUsingDataStore' | 'getResolverConfig' | 'authConfig' >; export type TransformerSchemaVisitStepContextProvider = Pick< TransformerContextProvider, - 'inputDocument' | 'output' | 'providerRegistry' | 'featureFlags' | 'isProjectUsingDataStore' | 'getResolverConfig' + | 'inputDocument' + | 'output' + | 'providerRegistry' + | 'featureFlags' + | 'isProjectUsingDataStore' + | 'getResolverConfig' + | 'metadata' + | 'authConfig' >; export type TransformerValidationStepContextProvider = Pick< TransformerContextProvider, - 'inputDocument' | 'output' | 'providerRegistry' | 'dataSources' | 'featureFlags' | 'isProjectUsingDataStore' | 'getResolverConfig' + | 'inputDocument' + | 'output' + | 'providerRegistry' + | 'dataSources' + | 'featureFlags' + | 'isProjectUsingDataStore' + | 'getResolverConfig' + | 'metadata' + | 'authConfig' >; export type TransformerPrepareStepContextProvider = TransformerValidationStepContextProvider; export type TransformerTransformSchemaStepContextProvider = TransformerValidationStepContextProvider; diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-datasource-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-datasource-provider.ts index c1472951639..8350f8be702 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-datasource-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-datasource-provider.ts @@ -22,6 +22,7 @@ export type DataSourceInstance = ITable | CfnDomain | HttpDataSource | IFunction export interface TransformerDataSourceManagerProvider { add(type: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode, dataSourceInstance: DataSourceInstance): void; get(type: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode): DataSourceInstance; + has(name: string): boolean; } export interface DataSourceProvider extends BackedDataSource {} diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-resolver-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-resolver-provider.ts index 2688c9c192b..b369c6bb234 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-resolver-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-resolver-provider.ts @@ -17,6 +17,7 @@ export interface TransformerResolverProvider { export interface TransformerResolversManagerProvider { addResolver: (typeName: string, fieldName: string, resolver: TransformerResolverProvider) => TransformerResolverProvider; getResolver: (typeName: string, fieldName: string) => TransformerResolverProvider | void; + hasResolver: (typeName: string, fieldName: string) => boolean; removeResolver: (typeName: string, fieldName: string) => TransformerResolverProvider; collectResolvers: () => Map; diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-model-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-model-provider.ts index 9bf6090705e..ee5d4372e67 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-model-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-model-provider.ts @@ -129,4 +129,6 @@ export interface TransformerModelProvider extends TransformerPluginProvider { ) => ObjectTypeDefinitionNode; } +export interface TransformerAuthProvider extends TransformerPluginProvider {} + export interface TransformerModelEnhancementProvider extends Partial {} diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-plugin-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-plugin-provider.ts index e506f490beb..076831255b5 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-plugin-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-plugin-provider.ts @@ -25,6 +25,7 @@ export enum TransformerPluginType { DATA_SOURCE_PROVIDER = 'DATA_SOURCE_PROVIDER', DATA_SOURCE_ENHANCER = 'DATA_SOURCE_ENHANCER', GENERIC = 'GENERIC', + AUTH = 'AUTH', } export interface TransformerPluginProvider { pluginType: TransformerPluginType; diff --git a/packages/amplify-headless-interface/schemas/storage/1/AddStorageRequest.schema.json b/packages/amplify-headless-interface/schemas/storage/1/AddStorageRequest.schema.json index 6eae18fea9d..5828b55af8a 100644 --- a/packages/amplify-headless-interface/schemas/storage/1/AddStorageRequest.schema.json +++ b/packages/amplify-headless-interface/schemas/storage/1/AddStorageRequest.schema.json @@ -1,5 +1,5 @@ { - "description": "Headless mode for add storage is not yet implemented.\nThis interface is subject to change and should not be used.", + "description": "Service configuration for adding AWS S3 through Amplify", "type": "object", "properties": { "version": { @@ -9,7 +9,7 @@ ] }, "serviceConfiguration": { - "$ref": "#/definitions/S3ServiceConfiguration" + "$ref": "#/definitions/AddS3ServiceConfiguration" } }, "required": [ @@ -17,32 +17,35 @@ "version" ], "definitions": { - "S3ServiceConfiguration": { + "AddS3ServiceConfiguration": { "description": "Service configuration for AWS S3 through Amplify", "type": "object", "properties": { - "serviceName": { - "description": "Descriminant used to determine the service config type", - "type": "string", - "enum": [ - "s3" - ] - }, "permissions": { "$ref": "#/definitions/S3Permissions", "description": "The permissions that should be applied to the bucket" }, + "resourceName": { + "description": "Amplify resource name", + "type": "string" + }, "bucketName": { - "description": "Globally unique bucket name", + "description": "Globally unique bucket name - bucket names must be lowercase", "type": "string" }, "lambdaTrigger": { "$ref": "#/definitions/LambdaTriggerConfig", "description": "Optional parameter specifying a lambda that should run when the bucket is modified" + }, + "serviceName": { + "description": "Descriminant used to determine the service config type", + "type": "string", + "enum": [ + "S3" + ] } }, "required": [ - "bucketName", "permissions", "serviceName" ] @@ -56,10 +59,9 @@ "type": "array", "items": { "enum": [ - "CREATE", + "CREATE_AND_UPDATE", "DELETE", - "READ", - "UPDATE" + "READ" ], "type": "string" } @@ -69,10 +71,9 @@ "type": "array", "items": { "enum": [ - "CREATE", + "CREATE_AND_UPDATE", "DELETE", - "READ", - "UPDATE" + "READ" ], "type": "string" } @@ -93,10 +94,9 @@ "type": "array", "items": { "enum": [ - "CREATE", + "CREATE_AND_UPDATE", "DELETE", - "READ", - "UPDATE" + "READ" ], "type": "string" } @@ -124,4 +124,4 @@ } }, "$schema": "http://json-schema.org/draft-07/schema#" -} \ No newline at end of file +} diff --git a/packages/amplify-headless-interface/schemas/storage/1/ImportStorageRequest.schema.json b/packages/amplify-headless-interface/schemas/storage/1/ImportStorageRequest.schema.json new file mode 100644 index 00000000000..ad1d7497004 --- /dev/null +++ b/packages/amplify-headless-interface/schemas/storage/1/ImportStorageRequest.schema.json @@ -0,0 +1,43 @@ +{ + "description": "Service configuration for importing AWS S3 into an Amplify project", + "type": "object", + "properties": { + "version": { + "type": "number", + "enum": [ + 1 + ] + }, + "serviceConfiguration": { + "$ref": "#/definitions/ImportS3ServiceConfiguration" + } + }, + "required": [ + "serviceConfiguration", + "version" + ], + "definitions": { + "ImportS3ServiceConfiguration": { + "description": "Service configuration for AWS S3 through Amplify", + "type": "object", + "properties": { + "bucketName": { + "description": "Globally unique bucket name", + "type": "string" + }, + "serviceName": { + "description": "Descriminant used to determine the service config type", + "type": "string", + "enum": [ + "S3" + ] + } + }, + "required": [ + "bucketName", + "serviceName" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/packages/amplify-headless-interface/schemas/storage/1/RemoveStorageRequest.schema.json b/packages/amplify-headless-interface/schemas/storage/1/RemoveStorageRequest.schema.json new file mode 100644 index 00000000000..abd31f514ca --- /dev/null +++ b/packages/amplify-headless-interface/schemas/storage/1/RemoveStorageRequest.schema.json @@ -0,0 +1,47 @@ +{ + "description": "Service configuration for removing AWS S3 through Amplify", + "type": "object", + "properties": { + "version": { + "type": "number", + "enum": [ + 1 + ] + }, + "serviceConfiguration": { + "$ref": "#/definitions/RemoveS3ServiceConfiguration" + } + }, + "required": [ + "serviceConfiguration", + "version" + ], + "definitions": { + "RemoveS3ServiceConfiguration": { + "description": "Service configuration for AWS S3 through Amplify", + "type": "object", + "properties": { + "resourceName": { + "description": "Amplify resource name", + "type": "string" + }, + "deleteBucketAndContents": { + "description": "Delete data and clean up resource entirely", + "type": "boolean" + }, + "serviceName": { + "description": "Descriminant used to determine the service config type", + "type": "string", + "enum": [ + "S3" + ] + } + }, + "required": [ + "resourceName", + "serviceName" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/packages/amplify-headless-interface/schemas/storage/1/UpdateStorageRequest.schema.json b/packages/amplify-headless-interface/schemas/storage/1/UpdateStorageRequest.schema.json new file mode 100644 index 00000000000..00da1c3bbaa --- /dev/null +++ b/packages/amplify-headless-interface/schemas/storage/1/UpdateStorageRequest.schema.json @@ -0,0 +1,124 @@ +{ + "description": "Service configuration for updating AWS S3 through Amplify", + "type": "object", + "properties": { + "version": { + "type": "number", + "enum": [ + 1 + ] + }, + "serviceModification": { + "$ref": "#/definitions/UpdateS3ServiceModification" + } + }, + "required": [ + "serviceModification", + "version" + ], + "definitions": { + "UpdateS3ServiceModification": { + "description": "Service modification for AWS S3 through Amplify", + "type": "object", + "properties": { + "permissions": { + "$ref": "#/definitions/S3Permissions", + "description": "The permissions that should be applied to the bucket" + }, + "resourceName": { + "description": "Amplify resource name", + "type": "string" + }, + "lambdaTrigger": { + "$ref": "#/definitions/LambdaTriggerConfig", + "description": "Optional parameter specifying a lambda that should run when the bucket is modified" + }, + "serviceName": { + "description": "Descriminant used to determine the service config type", + "type": "string", + "enum": [ + "S3" + ] + } + }, + "required": [ + "permissions", + "resourceName", + "serviceName" + ] + }, + "S3Permissions": { + "description": "Permissions that should be applied to the bucket", + "type": "object", + "properties": { + "auth": { + "description": "Permissions for authenticated users", + "type": "array", + "items": { + "enum": [ + "CREATE_AND_UPDATE", + "DELETE", + "READ" + ], + "type": "string" + } + }, + "guest": { + "description": "Permissions for unauthenticated users", + "type": "array", + "items": { + "enum": [ + "CREATE_AND_UPDATE", + "DELETE", + "READ" + ], + "type": "string" + } + }, + "groups": { + "$ref": "#/definitions/PermissionGroups", + "description": "Permissions for Cognito user groups" + } + }, + "required": [ + "auth" + ] + }, + "PermissionGroups": { + "description": "Permissions for Cognito user groups", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "enum": [ + "CREATE_AND_UPDATE", + "DELETE", + "READ" + ], + "type": "string" + } + } + }, + "LambdaTriggerConfig": { + "description": "Lambda function that runs on bucket change", + "type": "object", + "properties": { + "mode": { + "enum": [ + "existing", + "new" + ], + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "mode", + "name" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/packages/amplify-headless-interface/scripts/generateSchemas.ts b/packages/amplify-headless-interface/scripts/generateSchemas.ts index a0f7fcb0f71..d2a32dcec82 100644 --- a/packages/amplify-headless-interface/scripts/generateSchemas.ts +++ b/packages/amplify-headless-interface/scripts/generateSchemas.ts @@ -23,15 +23,30 @@ const typeDefs: TypeDef[] = [ category: 'api', relativeSourcePaths: [path.join('api', 'add.ts')], }, + { + typeName: 'UpdateApiRequest', + category: 'api', + relativeSourcePaths: ['add.ts', 'update.ts'].map(file => path.join('api', file)), + }, { typeName: 'AddStorageRequest', category: 'storage', relativeSourcePaths: [path.join('storage', 'add.ts')], }, { - typeName: 'UpdateApiRequest', - category: 'api', - relativeSourcePaths: ['add.ts', 'update.ts'].map(file => path.join('api', file)), + typeName: 'UpdateStorageRequest', + category: 'storage', + relativeSourcePaths: [path.join('storage', 'update.ts')], + }, + { + typeName: 'ImportStorageRequest', + category: 'storage', + relativeSourcePaths: [path.join('storage', 'import.ts')], + }, + { + typeName: 'RemoveStorageRequest', + category: 'storage', + relativeSourcePaths: [path.join('storage', 'remove.ts')], }, ]; @@ -57,7 +72,7 @@ typeDefs.forEach(typeDef => { return; } fs.ensureFileSync(schemaFilePath); - fs.writeFileSync(schemaFilePath, JSON.stringify(typeSchema, undefined, 4)); + fs.writeFileSync(schemaFilePath, JSON.stringify(typeSchema, undefined, 4) + '\n'); console.log(`Schema version ${version} written for type ${typeDef.typeName}.`); }); diff --git a/packages/amplify-headless-interface/src/index.ts b/packages/amplify-headless-interface/src/index.ts index a247fac973c..a6bbbdea4f3 100644 --- a/packages/amplify-headless-interface/src/index.ts +++ b/packages/amplify-headless-interface/src/index.ts @@ -3,4 +3,4 @@ export * from './interface/api/update'; export * from './interface/auth/add'; export * from './interface/auth/update'; export * from './interface/auth/import'; -export * from './interface/storage/add'; +export * from './interface/storage'; diff --git a/packages/amplify-headless-interface/src/interface/api/add.ts b/packages/amplify-headless-interface/src/interface/api/add.ts index 51f44124538..081eaf07e57 100644 --- a/packages/amplify-headless-interface/src/interface/api/add.ts +++ b/packages/amplify-headless-interface/src/interface/api/add.ts @@ -130,6 +130,7 @@ export type AppSyncAuthType = export interface AppSyncAPIKeyAuthType { mode: 'API_KEY'; expirationTime?: number; + apiKeyExpirationDate?: Date; keyDescription?: string; } diff --git a/packages/amplify-headless-interface/src/interface/storage/add.ts b/packages/amplify-headless-interface/src/interface/storage/add.ts index 9e7a83dc0ed..882cd76fefe 100644 --- a/packages/amplify-headless-interface/src/interface/storage/add.ts +++ b/packages/amplify-headless-interface/src/interface/storage/add.ts @@ -1,75 +1,34 @@ +import { LambdaTriggerConfig, S3Permissions, S3ServiceConfigurationBase } from './base'; + /** - * Headless mode for add storage is not yet implemented. - * This interface is subject to change and should not be used. + * Service configuration for adding AWS S3 through Amplify */ export interface AddStorageRequest { version: 1; - serviceConfiguration: S3ServiceConfiguration; + serviceConfiguration: AddS3ServiceConfiguration; } /** * Service configuration for AWS S3 through Amplify */ -export interface S3ServiceConfiguration { - /** - * Descriminant used to determine the service config type - */ - serviceName: 's3'; +export interface AddS3ServiceConfiguration extends S3ServiceConfigurationBase { /** * The permissions that should be applied to the bucket */ permissions: S3Permissions; - /** - * Globally unique bucket name - */ - bucketName: string; - /** - * Optional parameter specifying a lambda that should run when the bucket is modified - */ - lambdaTrigger?: LambdaTriggerConfig; -} - -/** - * Permissions that should be applied to the bucket - */ -export interface S3Permissions { - /** - * Permissions for authenticated users - */ - auth: CrudOperations[]; /** - * Permissions for unauthenticated users + * Amplify resource name */ - guest?: CrudOperations[]; + resourceName?: string; /** - * Permissions for Cognito user groups + * Globally unique bucket name - bucket names must be lowercase */ - groups?: PermissionGroups; -} + bucketName?: string; -/** - * Permissions for Cognito user groups - */ -export interface PermissionGroups { /** - * Each key is a Cognito user group name and each value is the CRUD opterations permitted for that group + * Optional parameter specifying a lambda that should run when the bucket is modified */ - [k: string]: CrudOperations[]; -} - -/** - * Lambda function that runs on bucket change - */ -export interface LambdaTriggerConfig { - mode: 'new' | 'existing'; - name: string; -} - -export enum CrudOperations { - CREATE = 'CREATE', - READ = 'READ', - UPDATE = 'UPDATE', - DELETE = 'DELETE', + lambdaTrigger?: LambdaTriggerConfig; } diff --git a/packages/amplify-headless-interface/src/interface/storage/base.ts b/packages/amplify-headless-interface/src/interface/storage/base.ts new file mode 100644 index 00000000000..406d7aa22e6 --- /dev/null +++ b/packages/amplify-headless-interface/src/interface/storage/base.ts @@ -0,0 +1,53 @@ +/** + * Service configuration for AWS S3 through Amplify + */ +export interface S3ServiceConfigurationBase { + /** + * Descriminant used to determine the service config type + */ + serviceName: 'S3'; +} + +/** + * Permissions that should be applied to the bucket + */ +export interface S3Permissions { + /** + * Permissions for authenticated users + */ + auth: CrudOperation[]; + + /** + * Permissions for unauthenticated users + */ + guest?: CrudOperation[]; + + /** + * Permissions for Cognito user groups + */ + groups?: PermissionGroups; +} + +/** + * Permissions for Cognito user groups + */ +export interface PermissionGroups { + /** + * Each key is a Cognito user group name and each value is the CRUD opterations permitted for that group + */ + [k: string]: CrudOperation[]; +} + +/** + * Lambda function that runs on bucket change + */ +export interface LambdaTriggerConfig { + mode: 'new' | 'existing'; + name: string; +} + +export enum CrudOperation { + CREATE_AND_UPDATE = 'CREATE_AND_UPDATE', + READ = 'READ', + DELETE = 'DELETE', +} diff --git a/packages/amplify-headless-interface/src/interface/storage/import.ts b/packages/amplify-headless-interface/src/interface/storage/import.ts new file mode 100644 index 00000000000..bdb97169cac --- /dev/null +++ b/packages/amplify-headless-interface/src/interface/storage/import.ts @@ -0,0 +1,19 @@ +import { S3ServiceConfigurationBase } from './base'; + +/** + * Service configuration for importing AWS S3 into an Amplify project + */ +export interface ImportStorageRequest { + version: 1; + serviceConfiguration: ImportS3ServiceConfiguration; +} + +/** + * Service configuration for AWS S3 through Amplify + */ +export interface ImportS3ServiceConfiguration extends S3ServiceConfigurationBase { + /** + * Globally unique bucket name + */ + bucketName: string; +} diff --git a/packages/amplify-headless-interface/src/interface/storage/index.ts b/packages/amplify-headless-interface/src/interface/storage/index.ts new file mode 100644 index 00000000000..cd2d4f730fc --- /dev/null +++ b/packages/amplify-headless-interface/src/interface/storage/index.ts @@ -0,0 +1,5 @@ +export * from './add'; +export * from './base'; +export * from './import'; +export * from './remove'; +export * from './update'; diff --git a/packages/amplify-headless-interface/src/interface/storage/remove.ts b/packages/amplify-headless-interface/src/interface/storage/remove.ts new file mode 100644 index 00000000000..c7f14b0762c --- /dev/null +++ b/packages/amplify-headless-interface/src/interface/storage/remove.ts @@ -0,0 +1,24 @@ +import { S3ServiceConfigurationBase } from './base'; + +/** + * Service configuration for removing AWS S3 through Amplify + */ +export interface RemoveStorageRequest { + version: 1; + serviceConfiguration: RemoveS3ServiceConfiguration; +} + +/** + * Service configuration for AWS S3 through Amplify + */ +export interface RemoveS3ServiceConfiguration extends S3ServiceConfigurationBase { + /** + * Amplify resource name + */ + resourceName: string; + + /** + * Delete data and clean up resource entirely + */ + deleteBucketAndContents?: boolean; +} diff --git a/packages/amplify-headless-interface/src/interface/storage/update.ts b/packages/amplify-headless-interface/src/interface/storage/update.ts new file mode 100644 index 00000000000..abf10f74a49 --- /dev/null +++ b/packages/amplify-headless-interface/src/interface/storage/update.ts @@ -0,0 +1,29 @@ +import { LambdaTriggerConfig, S3Permissions, S3ServiceConfigurationBase } from './base'; + +/** + * Service configuration for updating AWS S3 through Amplify + */ +export interface UpdateStorageRequest { + version: 1; + serviceModification: UpdateS3ServiceModification; +} + +/** + * Service modification for AWS S3 through Amplify + */ +export interface UpdateS3ServiceModification extends S3ServiceConfigurationBase { + /** + * The permissions that should be applied to the bucket + */ + permissions: S3Permissions; + + /** + * Amplify resource name + */ + resourceName: string; + + /** + * Optional parameter specifying a lambda that should run when the bucket is modified + */ + lambdaTrigger?: LambdaTriggerConfig; +} diff --git a/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.key.migration-2.test.ts b/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.key.migration-2.test.ts index cdac286a907..93e9f286d53 100644 --- a/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.key.migration-2.test.ts +++ b/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.key.migration-2.test.ts @@ -1,5 +1,5 @@ import { - addApiWithSchema, + addApiWithoutSchema, addFeatureFlag, amplifyPush, amplifyPushForce, @@ -28,7 +28,8 @@ describe('amplify key force push', () => { const initialSchema = 'migrations_key/initial_schema.graphql'; // init, add api and push with installed cli await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); // add feature flag addFeatureFlag(projRoot, 'graphqltransformer', 'secondaryKeyAsGSI', true); diff --git a/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.key.migration.test.ts b/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.key.migration.test.ts index c8a75b766cb..ad38a22425a 100644 --- a/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.key.migration.test.ts +++ b/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.key.migration.test.ts @@ -1,5 +1,5 @@ import { - addApiWithSchema, + addApiWithoutSchema, addFeatureFlag, amplifyPush, amplifyPushForce, @@ -28,7 +28,8 @@ describe('amplify key force push', () => { const initialSchema = 'migrations_key/simple_key.graphql'; // init, add api and push with installed cli await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); // gql-compile and force push with codebase cli await apiGqlCompile(projRoot, true); diff --git a/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.searchable.migration.test.ts b/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.searchable.migration.test.ts index b1c7194472d..0b0c0b5d5e9 100644 --- a/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.searchable.migration.test.ts +++ b/packages/amplify-migration-tests/src/__tests__/migration_tests/transformer_migration/api.searchable.migration.test.ts @@ -1,5 +1,5 @@ import { - addApiWithSchema, + addApiWithoutSchema, amplifyPush, amplifyPushUpdate, createNewProjectDir, @@ -26,7 +26,8 @@ describe('amplify searchable migration', () => { const nextSchema = 'migrations_searchable/updated_searchable.graphql'; // init, add api and push with installed cli await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); // update and push with codebase cli updateApiSchema(projRoot, projectName, nextSchema); diff --git a/packages/amplify-migration-tests/src/__tests__/update_tests/api_migration_update.test.ts b/packages/amplify-migration-tests/src/__tests__/update_tests/api_migration_update.test.ts index abdbef5886f..a9589a7c264 100644 --- a/packages/amplify-migration-tests/src/__tests__/update_tests/api_migration_update.test.ts +++ b/packages/amplify-migration-tests/src/__tests__/update_tests/api_migration_update.test.ts @@ -1,6 +1,6 @@ import { - addApiWithSchema, - addApiWithSchemaAndConflictDetection, + addApiWithoutSchema, + addApiWithBlankSchemaAndConflictDetection, amplifyPush, amplifyPushUpdate, createNewProjectDir, @@ -11,7 +11,7 @@ import { getTransformConfig, updateApiSchema, updateApiWithMultiAuth, - updateAPIWithResolutionStrategy, + updateAPIWithResolutionStrategyWithModels, } from 'amplify-e2e-core'; import { existsSync } from 'fs'; import { TRANSFORM_CURRENT_VERSION } from 'graphql-transformer-core'; @@ -38,7 +38,8 @@ describe('api migration update test', () => { const nextSchema = 'next_key_blog.graphql'; // init the project and add api with installed cli await initJSProjectWithProfile(projRoot, { name: projectName }); - await addApiWithSchema(projRoot, initialSchema); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, projectName, initialSchema); await amplifyPush(projRoot); // update api and push with the CLI to be released (the codebase) updateApiSchema(projRoot, projectName, nextSchema); @@ -53,7 +54,8 @@ describe('api migration update test', () => { it('api update migration with multiauth', async () => { // init and add api with installed CLI await initJSProjectWithProfile(projRoot, { name: 'simplemodelmultiauth' }); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, 'simplemodelmultiauth', 'simple_model.graphql'); // update and push with codebase await updateApiWithMultiAuth(projRoot, { testingWithLatestCodebase: true }); await amplifyPush(projRoot, true); @@ -98,7 +100,8 @@ describe('api migration update test', () => { const name = `syncenabled`; // init and add api with locally installed cli await initJSProjectWithProfile(projRoot, { name }); - await addApiWithSchemaAndConflictDetection(projRoot, 'simple_model.graphql'); + await addApiWithBlankSchemaAndConflictDetection(projRoot); + await updateApiSchema(projRoot, name, 'simple_model.graphql'); let transformConfig = getTransformConfig(projRoot, name); expect(transformConfig).toBeDefined(); @@ -108,7 +111,7 @@ describe('api migration update test', () => { expect(transformConfig.ResolverConfig.project.ConflictHandler).toEqual('AUTOMERGE'); //update and push with codebase - await updateAPIWithResolutionStrategy(projRoot, { testingWithLatestCodebase: true }); + await updateAPIWithResolutionStrategyWithModels(projRoot, { testingWithLatestCodebase: true }); transformConfig = getTransformConfig(projRoot, name); expect(transformConfig).toBeDefined(); diff --git a/packages/amplify-migration-tests/src/__tests__/update_tests/function_migration_update.test.ts b/packages/amplify-migration-tests/src/__tests__/update_tests/function_migration_update.test.ts index 1755e98afef..4a42e1e1027 100644 --- a/packages/amplify-migration-tests/src/__tests__/update_tests/function_migration_update.test.ts +++ b/packages/amplify-migration-tests/src/__tests__/update_tests/function_migration_update.test.ts @@ -1,5 +1,5 @@ import { - addApiWithSchema, + addApiWithoutSchema, addFunction, addLayer, amplifyPush, @@ -15,8 +15,10 @@ import { LayerRuntime, loadFunctionTestFile, overrideFunctionSrcNode, + updateApiSchema, updateFunction, validateLayerMetadata, + createRandomName, } from 'amplify-e2e-core'; import { v4 as uuid } from 'uuid'; import { initJSProjectWithProfile } from '../../migration-helpers'; @@ -34,7 +36,10 @@ describe('amplify function migration', () => { }); it('existing lambda updated with additional permissions should be able to scan ddb', async () => { - await initJSProjectWithProfile(projRoot, {}); + const appName = createRandomName(); + await initJSProjectWithProfile(projRoot, { + name: appName, + }); const random = Math.floor(Math.random() * 10000); const fnName = `integtestfn${random}`; @@ -58,7 +63,8 @@ describe('amplify function migration', () => { expect(functionName).toBeDefined(); expect(region).toBeDefined(); - await addApiWithSchema(projRoot, 'simple_model.graphql'); + await addApiWithoutSchema(projRoot); + await updateApiSchema(projRoot, appName, 'simple_model.graphql'); await updateFunction( projRoot, { diff --git a/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyBuild.ts b/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyBuild.ts index c2a93c0accb..5f29758e572 100644 --- a/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyBuild.ts +++ b/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyBuild.ts @@ -60,6 +60,8 @@ function runPackageManager(cwd: string, buildType?: BuildType, scriptName?: stri } catch (error) { if ((error as any).code === 'ENOENT') { throw new Error(`Packaging lambda function failed. Could not find ${packageManager} executable in the PATH.`); + } else if (error.stdout?.includes('YN0050: The --production option is deprecated')) { + throw new Error(`Packaging lambda function failed. Yarn 2 is not supported. Use Yarn 1.x and push again.`); } else { throw new Error(`Packaging lambda function failed with the error \n${error.message}`); } diff --git a/packages/amplify-prompts/src/__tests__/prompter.test.ts b/packages/amplify-prompts/src/__tests__/prompter.test.ts index 47a89d83f26..0e753f0e055 100644 --- a/packages/amplify-prompts/src/__tests__/prompter.test.ts +++ b/packages/amplify-prompts/src/__tests__/prompter.test.ts @@ -1,4 +1,4 @@ -import { prompter } from '../prompter'; +import { byValue, byValues, prompter } from '../prompter'; import { prompt } from 'enquirer'; import * as flags from '../flags'; @@ -77,9 +77,10 @@ describe('input', () => { it('transforms each input part separately when "many" specified', async () => { prompt_mock.mockResolvedValueOnce({ result: ['10', '20'] }); - expect( - await prompter.input<'many'>('test message', { returnSize: 'many', transform: input => `${input}suffix` }), - ).toEqual(['10suffix', '20suffix']); + expect(await prompter.input<'many'>('test message', { returnSize: 'many', transform: input => `${input}suffix` })).toEqual([ + '10suffix', + '20suffix', + ]); }); }); @@ -104,6 +105,19 @@ describe('pick', () => { expect(prompt_mock.mock.calls.length).toBe(0); }); + it('computes selection index using selection function', async () => { + prompt_mock.mockResolvedValueOnce({ result: 'opt2' }); + await prompter.pick('test message', ['opt1', 'opt2', 'opt3'], { initial: byValue('opt2') }); + expect((prompt_mock.mock.calls[0][0] as any).initial).toBe(1); + }); + + it('returns initial selection using selection function when yes flag is set', async () => { + flags_mock.isYes = true; + const result = await prompter.pick('test message', ['opt1', 'opt2', 'opt3'], { initial: byValue('opt2') }); + expect(result).toBe('opt2'); + expect(prompt_mock.mock.calls.length).toBe(0); + }); + it('throws if no choices provided', async () => { expect(() => prompter.pick('test message', [])).rejects.toThrowErrorMatchingInlineSnapshot( `"No choices provided for prompt [test message]"`, @@ -123,8 +137,38 @@ describe('pick', () => { it('returns selected items when multiSelect', async () => { const mockResult = ['val1', 'val3']; prompt_mock.mockResolvedValueOnce({ result: mockResult }); - expect( - await prompter.pick<'many'>('test message', ['val1', 'val2', 'val3'], { returnSize: 'many' }), - ).toEqual(mockResult); + expect(await prompter.pick<'many'>('test message', ['val1', 'val2', 'val3'], { returnSize: 'many' })).toEqual(mockResult); + }); +}); + +describe('byValue', () => { + it('defaults to === when no equals function specified', () => { + expect(byValue('fox')(['the', 'quick', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'])).toBe(2); + }); + + it('returns the index of the first match if multiple present', () => { + expect(byValue('the')(['the', 'quick', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'])).toBe(0); + }); + + it('returns undefined when no match found', () => { + expect(byValue('dne')(['the', 'quick', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'])).toBeUndefined(); + }); + + it('uses the equals function if specified', () => { + expect(byValue('four', (a, b) => a.length === b.length)(['the', 'quick', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'])).toBe(4); + }); +}); + +describe('byValues', () => { + it('defaults to === when no equals function specified', () => { + expect(byValues(['fox', 'the'])(['the', 'quick', 'fox', 'jumped', 'over', 'lazy', 'dog']).sort()).toEqual([0, 2]); + }); + + it('returns [] when no matches found', () => { + expect(byValues(['dne'])(['the', 'quick', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'])).toEqual([]); + }); + + it('uses the equals function if specified', () => { + expect(byValues(['a', 'aa'], (a, b) => a.length === b.length)(['bbbb', 'bb', 'bbb', 'b']).sort()).toEqual([1, 3]); }); }); diff --git a/packages/amplify-prompts/src/demo/demo.ts b/packages/amplify-prompts/src/demo/demo.ts index 87f1855f69b..2ee92d4bc39 100644 --- a/packages/amplify-prompts/src/demo/demo.ts +++ b/packages/amplify-prompts/src/demo/demo.ts @@ -1,5 +1,5 @@ import { printer } from '../printer'; -import { prompter } from '../prompter'; +import { byValue, prompter } from '../prompter'; import { alphanumeric, and, integer, minLength } from '../validators'; const printResult = (result: any) => console.log(`Prompt result was [${result}]`); @@ -117,6 +117,9 @@ const demo = async () => { printer.info('When multiSelect is on, an array of initial indexes can be specified'); printResult(await prompter.pick<'many', number>('Pick your favorite colors', choices2, { returnSize: 'many', initial: [1, 2] })); + printer.info('Choices can also be selected by value using the provided helper function "byValue" (or "byValues" for multi-select)'); + printResult(await prompter.pick<'one', number>('Pick your favorite color', choices2, { initial: byValue(4) })); + printer.info('Individual choices can be disabled or have hint text next to them'); (choices2[1] as any).hint = 'definitely the best'; (choices2[2] as any).disabled = true; diff --git a/packages/amplify-prompts/src/prompter.ts b/packages/amplify-prompts/src/prompter.ts index c4d18dd70c2..b4b949793b1 100644 --- a/packages/amplify-prompts/src/prompter.ts +++ b/packages/amplify-prompts/src/prompter.ts @@ -105,11 +105,14 @@ class AmplifyPrompter implements Prompter { * @param choices The selection set to choose from * @param options Control prompt settings. options.multiSelect = true is required if PickType = 'many' * @returns The item(s) selected. If PickType = 'one' this is a single value. If PickType = 'many', this is an array + * + * Note: due to this TS issue https://github.com/microsoft/TypeScript/issues/30611 type T cannot be an enum. + * If using an enum as the value type for a selection use T = string and assert the return type as the enum type. */ pick = async ( message: string, choices: Choices, - ...options: MaybeOptionalPickOptions + ...options: MaybeOptionalPickOptions ): Promise> => { // some choices must be provided if (choices?.length === 0) { @@ -124,6 +127,11 @@ class AmplifyPrompter implements Prompter { ? ((choices as string[]).map(choice => ({ name: choice, value: choice })) as unknown as GenericChoice[]) // this assertion is safe because the choice array can only be a string[] if the generic type is a string : (choices as GenericChoice[]); + const initialIndexes = initialOptsToIndexes( + genericChoices.map(choice => choice.value), + opts?.initial, + ); + // enquirer requires all choice values be strings, so set up a mapping of string => T // and format choices to conform to enquirer's interface const choiceValueMap = new Map(); @@ -139,13 +147,13 @@ class AmplifyPrompter implements Prompter { if (choices?.length === 1) { this.print.info(`Only one option for [${message}]. Selecting [${result}].`); } else if (isYes) { - if (opts?.initial === undefined || (Array.isArray(opts?.initial) && opts?.initial.length === 0)) { + if (initialIndexes === undefined || (Array.isArray(initialIndexes) && initialIndexes.length === 0)) { throw new Error(`Cannot prompt for [${message}] when '--yes' flag is set`); } - if (typeof opts?.initial === 'number') { - result = genericChoices[opts?.initial].name; + if (typeof initialIndexes === 'number') { + result = genericChoices[initialIndexes].name; } else { - result = opts?.initial.map(idx => genericChoices[idx].name); + result = initialIndexes.map(idx => genericChoices[idx].name); } } else { // enquirer does not clear the stdout buffer on TSTP (Ctrl + Z) so this listener maps it to process.exit() which will clear the buffer @@ -165,7 +173,7 @@ class AmplifyPrompter implements Prompter { name: 'result', message, hint: '(Use arrow keys or type to filter)', - initial: opts?.initial, + initial: initialIndexes, // there is a typo in the .d.ts file for this field -- muliple -> multiple // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -191,6 +199,30 @@ class AmplifyPrompter implements Prompter { export const prompter: Prompter = new AmplifyPrompter(); +/** + * Helper function to generate a function that will return the indices of a selection set from a list + * @param selection The list of values to select from a list + * @param equals An optional function to determine if two elements are equal. If not specified, === is used + * Note that choices are assumed to be unique by the equals function definition + */ +export const byValues = + (selection: T[], equals: EqualsFunction = defaultEquals): MultiFilterFunction => + (choices: T[]) => + selection.map(sel => choices.findIndex(choice => equals(choice, sel))).filter(idx => idx >= 0); + +/** + * Helper function to generate a function that will return an index of a single selection from a list + * @param selection The single selection to find in the list + * @param equals An optional function to determine if two elements are equal. If not specified, === is used + * Note that choices are assumed to be unique by the equals function definition + */ +export const byValue = + (selection: T, equals: EqualsFunction = defaultEquals): SingleFilterFunction => + (choices: T[]) => { + const idx = choices.findIndex(choice => equals(choice, selection)); + return idx < 0 ? undefined : idx; + }; + const validateEachWith = (validator?: Validator) => async (input: string[]) => { if (!validator) { return true; @@ -203,6 +235,20 @@ const validateEachWith = (validator?: Validator) => async (input: string[]) => { return true; }; +const initialOptsToIndexes = ( + values: T[], + initial: InitialSelectionOption['initial'], +): number | number[] | undefined => { + if (initial === undefined || typeof initial === 'number' || Array.isArray(initial)) { + return initial; + } + return initial(values); +}; + +type EqualsFunction = (a: T, b: T) => boolean; + +const defaultEquals = (a: T, b: T) => a === b; + type Prompter = { confirmContinue: (message?: string) => Promise; yesOrNo: (message: string, initial?: boolean) => Promise; @@ -215,7 +261,7 @@ type Prompter = { message: string, choices: Choices, // options is typed using spread because it's the only way to make it required if RS is 'many' but optional if RS is 'one' - ...options: MaybeOptionalPickOptions + ...options: MaybeOptionalPickOptions ) => Promise>; }; @@ -228,10 +274,16 @@ type MaybeAvailableHiddenInputOption = RS extends 'many' hidden?: boolean; }; -type InitialSelectionOption = { - initial?: RS extends 'one' ? number : number[]; +// The initial selection for a pick prompt can be specified either by index or a selection function that generates indexes. +// See byValues and byValue above +type InitialSelectionOption = { + initial?: RS extends 'one' ? number | SingleFilterFunction : number[] | MultiFilterFunction; }; +type SingleFilterFunction = (arr: T[]) => number | undefined; + +type MultiFilterFunction = (arr: T[]) => number[]; + type InitialValueOption = { initial?: T; }; @@ -273,12 +325,12 @@ type MaybeOptionalInputOptions = RS extends 'many' ? [InputOptions?] : [InputOptions]; -type MaybeOptionalPickOptions = RS extends 'many' ? [PickOptions] : [PickOptions?]; +type MaybeOptionalPickOptions = RS extends 'many' ? [PickOptions] : [PickOptions?]; type PromptReturn = RS extends 'many' ? T[] : T; // the following types are the method input types -type PickOptions = ReturnSizeOption & InitialSelectionOption; +type PickOptions = ReturnSizeOption & InitialSelectionOption; type InputOptions = ReturnSizeOption & ValidateValueOption & diff --git a/packages/amplify-prompts/src/validators.ts b/packages/amplify-prompts/src/validators.ts index d917ca606c9..9651f4125dc 100644 --- a/packages/amplify-prompts/src/validators.ts +++ b/packages/amplify-prompts/src/validators.ts @@ -12,50 +12,69 @@ export type Validator = (value: string) => true | string | Promise (input: string) => - /^[a-zA-Z0-9]+$/.test(input) ? true : message; +export const alphanumeric = + (message: string = 'Input must be alphanumeric'): Validator => + (input: string) => + /^[a-zA-Z0-9]+$/.test(input) ? true : message; -export const integer = (message: string = 'Input must be a number'): Validator => (input: string) => - /^[0-9]+$/.test(input) ? true : message; +export const integer = + (message: string = 'Input must be a number'): Validator => + (input: string) => + /^[0-9]+$/.test(input) ? true : message; -export const maxLength = (maxLen: number, message?: string): Validator => (input: string) => - input.length > maxLen ? message || `Input must be less than ${maxLen} characters long` : true; +export const maxLength = + (maxLen: number, message?: string): Validator => + (input: string) => + input.length > maxLen ? message || `Input must be less than ${maxLen} characters long` : true; -export const minLength = (minLen: number, message?: string): Validator => (input: string) => - input.length < minLen ? message || `Input must be more than ${minLen} characters long` : true; +export const minLength = + (minLen: number, message?: string): Validator => + (input: string) => + input.length < minLen ? message || `Input must be more than ${minLen} characters long` : true; + +export const exact = + (expected: string, message?: string): Validator => + (input: string) => + input === expected ? true : message ?? 'Input does not match expected value'; /** * Logically "and"s several validators * If a validator returns an error message, that message is returned by this function, unless an override message is specified */ -export const and = (validators: [Validator, Validator, ...Validator[]], message?: string): Validator => async (input: string) => { - for (const validator of validators) { - const result = await validator(input); - if (typeof result === 'string') { - return message ?? result; +export const and = + (validators: [Validator, Validator, ...Validator[]], message?: string): Validator => + async (input: string) => { + for (const validator of validators) { + const result = await validator(input); + if (typeof result === 'string') { + return message ?? result; + } } - } - return true; -}; + return true; + }; /** * Logically "or" several validators * If all validators return an error message, the LAST error message is returned by this function, unless an override message is specified */ -export const or = (validators: [Validator, Validator, ...Validator[]], message?: string): Validator => async (input: string) => { - let result: string | true = true; - for (const validator of validators) { - result = await validator(input); - if (result === true) { - return true; +export const or = + (validators: [Validator, Validator, ...Validator[]], message?: string): Validator => + async (input: string) => { + let result: string | true = true; + for (const validator of validators) { + result = await validator(input); + if (result === true) { + return true; + } } - } - return message ?? result; -}; + return message ?? result; + }; /** * Logical not operator on a validator * If validator returns true, it returns message. If validator returns an error message, it returns true. */ -export const not = (validator: Validator, message: string): Validator => async (input: string) => - typeof (await validator(input)) === 'string' ? true : message; +export const not = + (validator: Validator, message: string): Validator => + async (input: string) => + typeof (await validator(input)) === 'string' ? true : message; diff --git a/packages/amplify-provider-awscloudformation/package.json b/packages/amplify-provider-awscloudformation/package.json index 81fa37710bf..0184bc4cb36 100644 --- a/packages/amplify-provider-awscloudformation/package.json +++ b/packages/amplify-provider-awscloudformation/package.json @@ -24,6 +24,7 @@ "watch": "tsc --watch" }, "dependencies": { + "@aws-amplify/graphql-auth-transformer": "0.1.0", "@aws-amplify/graphql-default-value-transformer": "0.2.0", "@aws-amplify/graphql-function-transformer": "0.4.5", "@aws-amplify/graphql-http-transformer": "0.5.5", @@ -73,6 +74,7 @@ "@aws-cdk/custom-resources": "~1.124.0", "@aws-cdk/region-info": "~1.124.0", "@octokit/rest": "^18.0.9", + "ajv": "^6.12.3", "amplify-cli-core": "1.31.1", "amplify-cli-logger": "1.1.0", "amplify-codegen": "^2.23.1", diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/disconnect-dependent-resources/__snapshots__/utils.test.ts.snap b/packages/amplify-provider-awscloudformation/src/__tests__/disconnect-dependent-resources/__snapshots__/utils.test.ts.snap new file mode 100644 index 00000000000..6ae1c362769 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/disconnect-dependent-resources/__snapshots__/utils.test.ts.snap @@ -0,0 +1,114 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateIterativeFuncDeploymentSteps generates steps with correct pointers 1`] = ` +Object { + "deploymentSteps": Array [ + Object { + "deployment": Object { + "capabilities": Array [], + "parameters": Object { + "param1": "value1", + }, + "previousMetaKey": undefined, + "stackName": "testStackId", + "stackTemplatePathOrUrl": "amplify-cfn-templates/function/temp/temp-func1-cloudformation-template.json", + "tableNames": Array [], + }, + "rollback": undefined, + }, + Object { + "deployment": Object { + "capabilities": Array [], + "parameters": Object { + "param2": "value2", + }, + "previousMetaKey": "amplify-cfn-templates/function/temp/temp-func1-deployment-meta.json", + "stackName": "testStackId", + "stackTemplatePathOrUrl": "amplify-cfn-templates/function/temp/temp-func2-cloudformation-template.json", + "tableNames": Array [], + }, + "rollback": Object { + "capabilities": Array [], + "parameters": Object { + "param1": "value1", + }, + "previousMetaKey": undefined, + "stackName": "testStackId", + "stackTemplatePathOrUrl": "amplify-cfn-templates/function/temp/temp-func1-cloudformation-template.json", + "tableNames": Array [], + }, + }, + ], + "lastMetaKey": "amplify-cfn-templates/function/temp/temp-func2-deployment-meta.json", +} +`; + +exports[`generateTempFuncCFNTemplates replaces Fn::ImportValue references with placeholder values in template 1`] = ` +Object { + "a": Object { + "b": Object { + "c": Array [ + Object { + "Fn::ImportValue": undefined, + "Fn::Sub": "TemporaryPlaceholderValue", + }, + Object { + "Fn::Join": Array [ + ":", + Object { + "Fn::ImportValue": undefined, + "Fn::Sub": "TemporaryPlaceholderValue", + }, + ], + }, + ], + }, + "d": Object { + "Fn::ImportValue": undefined, + "Fn::Sub": "TemporaryPlaceholderValue", + }, + }, +} +`; + +exports[`prependDeploymentSteps concatenates arrays and moves pointers appropriately 1`] = ` +Array [ + Object { + "deployment": Object { + "previousMetaKey": undefined, + "stackTemplatePathOrUrl": "deploymentStep1", + }, + "rollback": undefined, + }, + Object { + "deployment": Object { + "previousMetaKey": "deploymentStep1MetaKey", + "stackTemplatePathOrUrl": "deploymentStep2", + }, + "rollback": Object { + "previousMetaKey": undefined, + "stackTemplatePathOrUrl": "deploymentStep1", + }, + }, + Object { + "deployment": Object { + "previousMetaKey": "deploymentStep2MetaKey", + "stackTemplatePathOrUrl": "deploymentStep3", + }, + "rollback": Object { + "previousMetaKey": "deploymentStep1MetaKey", + "stackTemplatePathOrUrl": "deploymentStep2", + }, + }, + Object { + "deployment": Object { + "previousMetaKey": "deploymentStep3MetaKey", + "stackTemplatePathOrUrl": "deploymentStep4", + }, + "rollback": Object { + "previousMetaKey": "deploymentStep2MetaKey", + "stackTemplatePathOrUrl": "deploymentStep3", + }, + }, +] +`; diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/disconnect-dependent-resources/utils.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/disconnect-dependent-resources/utils.test.ts new file mode 100644 index 00000000000..9b16d26be34 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/disconnect-dependent-resources/utils.test.ts @@ -0,0 +1,236 @@ +import { + generateIterativeFuncDeploymentSteps, + generateTempFuncCFNTemplates, + getDependentFunctions, + prependDeploymentSteps, + uploadTempFuncDeploymentFiles, +} from '../../disconnect-dependent-resources/utils'; +import { pathManager, stateManager, readCFNTemplate, writeCFNTemplate, CFNTemplateFormat } from 'amplify-cli-core'; +import * as fs from 'fs-extra'; +import { S3 } from '../../aws-utils/aws-s3'; +import { CloudFormation } from 'aws-sdk'; +import { getPreviousDeploymentRecord } from '../../utils/amplify-resource-state-utils'; +import Template from 'cloudform-types/types/template'; +import { DeploymentOp, DeploymentStep } from '../../iterative-deployment'; + +jest.mock('fs-extra'); +jest.mock('amplify-cli-core'); +jest.mock('amplify-cli-logger'); +jest.mock('../../utils/amplify-resource-state-utils'); + +const fs_mock = fs as jest.Mocked; +const pathManager_mock = pathManager as jest.Mocked; +const stateManager_mock = stateManager as jest.Mocked; +const readCFNTemplate_mock = readCFNTemplate as jest.MockedFunction; +const writeCFNTemplate_mock = writeCFNTemplate as jest.MockedFunction; + +const getPreviousDeploymentRecord_mock = getPreviousDeploymentRecord as jest.MockedFunction; + +pathManager_mock.getResourceDirectoryPath.mockReturnValue('mock/path'); + +beforeEach(jest.clearAllMocks); + +describe('getDependentFunctions', () => { + it('returns the subset of functions that have a dependency on the models', async () => { + const func1Params = { + permissions: { + storage: { + someOtherTable: {}, + }, + }, + }; + const func2Params = { + permissions: { + storage: { + ['ModelName:@model(appsync)']: {}, + }, + }, + }; + const funcParamsSupplier = jest.fn().mockReturnValueOnce(func1Params).mockReturnValueOnce(func2Params); + const result = await getDependentFunctions(['ModelName', 'OtherModel'], ['func1', 'func2'], funcParamsSupplier); + expect(result).toEqual(['func2']); + }); +}); + +describe('generateTempFuncCFNTemplates', () => { + readCFNTemplate_mock.mockResolvedValueOnce({ + cfnTemplate: { + a: { + b: { + c: [ + { + 'Fn::ImportValue': { + 'Fn::Sub': 'test string', + }, + }, + { + 'Fn::Join': [ + ':', + { + 'Fn::ImportValue': 'testvalue', + }, + ], + }, + ], + }, + d: { + 'Fn::ImportValue': 'something else', + }, + }, + } as Template, + templateFormat: CFNTemplateFormat.JSON, + }); + it('replaces Fn::ImportValue references with placeholder values in template', async () => { + await generateTempFuncCFNTemplates(['func1']); + expect(writeCFNTemplate_mock.mock.calls[0][0]).toMatchSnapshot(); + }); +}); + +describe('uploadTempFuncDeploymentFiles', () => { + it('uploads template and meta file', async () => { + fs_mock.createReadStream + .mockReturnValueOnce('func1Template' as any) + .mockReturnValueOnce('func1Meta' as any) + .mockReturnValueOnce('func2Template' as any) + .mockReturnValueOnce('func2Meta' as any); + const s3Client_stub = { + uploadFile: jest.fn(), + }; + + await uploadTempFuncDeploymentFiles(s3Client_stub as unknown as S3, ['func1', 'func2']); + expect(s3Client_stub.uploadFile.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "Body": "func1Template", + "Key": "amplify-cfn-templates/function/temp/temp-func1-cloudformation-template.json", + }, + false, + ], + Array [ + Object { + "Body": "func1Meta", + "Key": "amplify-cfn-templates/function/temp/temp-func1-deployment-meta.json", + }, + false, + ], + Array [ + Object { + "Body": "func2Template", + "Key": "amplify-cfn-templates/function/temp/temp-func2-cloudformation-template.json", + }, + false, + ], + Array [ + Object { + "Body": "func2Meta", + "Key": "amplify-cfn-templates/function/temp/temp-func2-deployment-meta.json", + }, + false, + ], + ] + `); + }); + + it('logs and throws upload error', async () => { + const s3Client_stub = { + uploadFile: jest.fn().mockRejectedValue(new Error('test error')), + }; + try { + await uploadTempFuncDeploymentFiles(s3Client_stub as unknown as S3, ['func1', 'func2']); + fail('function call should error'); + } catch (err) { + expect(err.message).toMatchInlineSnapshot(`"test error"`); + } + }); +}); + +describe('generateIterativeFuncDeploymentSteps', () => { + it('generates steps with correct pointers', async () => { + const cfnClient_stub = { + describeStackResources: () => ({ + promise: async () => ({ + StackResources: [ + { + PhysicalResourceId: 'testStackId', + }, + ], + }), + }), + }; + getPreviousDeploymentRecord_mock + .mockResolvedValueOnce({ + parameters: { + param1: 'value1', + }, + capabilities: [], + }) + .mockResolvedValueOnce({ + parameters: { + param2: 'value2', + }, + capabilities: [], + }); + stateManager_mock.getResourceParametersJson.mockReturnValue({}); + stateManager_mock.getTeamProviderInfo.mockReturnValue({}); + stateManager_mock.getLocalEnvInfo.mockReturnValue({ envName: 'testenv' }); + const result = await generateIterativeFuncDeploymentSteps(cfnClient_stub as unknown as CloudFormation, 'testRootStackId', [ + 'func1', + 'func2', + ]); + expect(result).toMatchSnapshot(); + }); +}); + +describe('prependDeploymentSteps', () => { + it('concatenates arrays and moves pointers appropriately', () => { + const beforeSteps: DeploymentStep[] = [ + { + deployment: { + stackTemplatePathOrUrl: 'deploymentStep1', + previousMetaKey: undefined, + } as DeploymentOp, + rollback: undefined, + }, + { + deployment: { + stackTemplatePathOrUrl: 'deploymentStep2', + previousMetaKey: 'deploymentStep1MetaKey', + } as DeploymentOp, + rollback: { + stackTemplatePathOrUrl: 'deploymentStep1', + previousMetaKey: undefined, + } as DeploymentOp, + }, + ]; + + const afterSteps: DeploymentStep[] = [ + { + deployment: { + stackTemplatePathOrUrl: 'deploymentStep3', + previousMetaKey: undefined, + } as DeploymentOp, + rollback: undefined, + }, + { + deployment: { + stackTemplatePathOrUrl: 'deploymentStep4', + previousMetaKey: 'deploymentStep3MetaKey', + } as DeploymentOp, + rollback: { + stackTemplatePathOrUrl: 'deploymentStep3', + previousMetaKey: undefined, + } as DeploymentOp, + }, + ]; + + const result = prependDeploymentSteps(beforeSteps, afterSteps, 'deploymentStep2MetaKey'); + expect(result).toMatchSnapshot(); + }); + + it('returns after array if before array is empty', () => { + const afterSteps = ['test step' as unknown as DeploymentStep]; + const result = prependDeploymentSteps([], afterSteps, 'testmetakey'); + expect(result).toEqual(afterSteps); + }); +}); diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/graphql-transformer/utils.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/graphql-transformer/utils.test.ts new file mode 100644 index 00000000000..9b9e76a1692 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/graphql-transformer/utils.test.ts @@ -0,0 +1,240 @@ +import { mergeUserConfigWithTransformOutput } from '../../graphql-transformer/utils'; +import { TransformerProjectConfig, DeploymentResources } from '@aws-amplify/graphql-transformer-core'; + +describe('graphql transformer utils', () => { + let userConfig: TransformerProjectConfig; + let transformerOutput: DeploymentResources; + + beforeAll(() => { + transformerOutput = { + resolvers: {}, + pipelineFunctions: { + 'Query.listTodos.req.vtl': '## [Start] List Request. **\n' + '#set( $limit = $util.defaultIfNull($context.args.limit, 100) )\n', + }, + functions: {}, + schema: '', + stackMapping: {}, + stacks: {}, + rootStack: { + Parameters: {}, + Resources: {}, + }, + } as DeploymentResources; + }); + + describe('mergeUserConfigWithTransformOutput', () => { + describe('has user created functions', () => { + beforeAll(() => { + userConfig = { + schema: '', + functions: { + userFn: 'userFn()', + }, + pipelineFunctions: {}, + resolvers: {}, + stacks: {}, + config: { Version: 5, ElasticsearchWarning: true }, + } as TransformerProjectConfig; + }); + + it('merges function with transform output functions', () => { + const { functions } = mergeUserConfigWithTransformOutput(userConfig, transformerOutput); + + expect(functions['userFn']).toEqual('userFn()'); + }); + }); + + describe('has user-created resolvers', () => { + beforeAll(() => { + userConfig = { + schema: '', + functions: {}, + pipelineFunctions: {}, + resolvers: { + 'Query.listTodos.req.vtl': '$util.unauthorized\n', + }, + stacks: {}, + config: { Version: 5, ElasticsearchWarning: true }, + } as TransformerProjectConfig; + }); + + it('merges the custom resolver with transformer output', () => { + const output = mergeUserConfigWithTransformOutput(userConfig, transformerOutput); + + expect(output.pipelineFunctions['Query.listTodos.req.vtl']).toEqual('$util.unauthorized\n'); + }); + }); + + describe('has user created pipeline function', () => { + beforeAll(() => { + userConfig = { + schema: '', + functions: {}, + pipelineFunctions: { + 'Query.listTodos.req.vtl': '$util.unauthorized\n', + }, + resolvers: {}, + stacks: {}, + config: { Version: 5, ElasticsearchWarning: true }, + } as TransformerProjectConfig; + }); + + it('merges custom pipeline function with transformer output', () => { + const { pipelineFunctions } = mergeUserConfigWithTransformOutput(userConfig, transformerOutput); + + expect(pipelineFunctions['Query.listTodos.req.vtl']).toEqual('$util.unauthorized\n'); + }); + }); + + describe('has user created stacks', () => { + beforeAll(() => { + userConfig = { + schema: '', + functions: {}, + pipelineFunctions: {}, + resolvers: {}, + stacks: { + 'CustomResources.json': { + Resources: { + QueryCommentsForTodoResolver: { + Type: 'AWS::AppSync::Resolver', + Properties: { + ApiId: { + Ref: 'AppSyncApiId', + }, + DataSourceName: 'CommentTable', + TypeName: 'Query', + FieldName: 'commentsForTodo', + RequestMappingTemplateS3Location: { + 'Fn::Sub': [ + 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.req.vtl', + { + S3DeploymentBucket: { + Ref: 'S3DeploymentBucket', + }, + S3DeploymentRootKey: { + Ref: 'S3DeploymentRootKey', + }, + }, + ], + }, + ResponseMappingTemplateS3Location: { + 'Fn::Sub': [ + 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.res.vtl', + { + S3DeploymentBucket: { + Ref: 'S3DeploymentBucket', + }, + S3DeploymentRootKey: { + Ref: 'S3DeploymentRootKey', + }, + }, + ], + }, + }, + }, + }, + Parameters: { + AppSyncApiId: { + Type: 'String', + Description: 'The id of the AppSync API associated with this project.', + }, + AppSyncApiName: { + Type: 'String', + Description: 'The name of the AppSync API', + Default: 'AppSyncSimpleTransform', + }, + env: { + Type: 'String', + Description: 'The environment name. e.g. Dev, Test, or Production', + Default: 'NONE', + }, + S3DeploymentBucket: { + Type: 'String', + Description: 'The S3 bucket containing all deployment assets for the project.', + }, + S3DeploymentRootKey: { + Type: 'String', + Description: 'An S3 key relative to the S3DeploymentBucket that points to the root\n' + 'of the deployment directory.', + }, + }, + }, + }, + config: { Version: 5, ElasticsearchWarning: true }, + } as unknown as TransformerProjectConfig; + }); + + it('merges custom pipeline function with transformer output', () => { + const { stacks } = mergeUserConfigWithTransformOutput(userConfig, transformerOutput); + + expect(stacks).toEqual({ + 'CustomResources.json': { + Resources: { + QueryCommentsForTodoResolver: { + Type: 'AWS::AppSync::Resolver', + Properties: { + ApiId: { + Ref: 'AppSyncApiId', + }, + DataSourceName: 'CommentTable', + TypeName: 'Query', + FieldName: 'commentsForTodo', + RequestMappingTemplateS3Location: { + 'Fn::Sub': [ + 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.req.vtl', + { + S3DeploymentBucket: { + Ref: 'S3DeploymentBucket', + }, + S3DeploymentRootKey: { + Ref: 'S3DeploymentRootKey', + }, + }, + ], + }, + ResponseMappingTemplateS3Location: { + 'Fn::Sub': [ + 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.res.vtl', + { + S3DeploymentBucket: { + Ref: 'S3DeploymentBucket', + }, + S3DeploymentRootKey: { + Ref: 'S3DeploymentRootKey', + }, + }, + ], + }, + }, + }, + }, + Parameters: { + AppSyncApiId: { + Type: 'String', + Description: 'The id of the AppSync API associated with this project.', + }, + AppSyncApiName: { + Type: 'String', + Description: 'The name of the AppSync API', + Default: 'AppSyncSimpleTransform', + }, + env: { + Type: 'String', + Description: 'The environment name. e.g. Dev, Test, or Production', + Default: 'NONE', + }, + S3DeploymentBucket: { + Type: 'String', + Description: 'The S3 bucket containing all deployment assets for the project.', + }, + S3DeploymentRootKey: { + Type: 'String', + Description: 'An S3 key relative to the S3DeploymentBucket that points to the root\n' + 'of the deployment directory.', + }, + }, + }, + }); + }); + }); + }); +}); diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/initializer.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/initializer.test.ts index 3d69f1af4ee..563b23501b8 100644 --- a/packages/amplify-provider-awscloudformation/src/__tests__/initializer.test.ts +++ b/packages/amplify-provider-awscloudformation/src/__tests__/initializer.test.ts @@ -37,7 +37,7 @@ describe('run', () => { }; CloudFormation_mock.mockImplementation( () => - (({ + ({ createResourceStack: jest.fn().mockResolvedValue({ Stacks: [ { @@ -45,7 +45,7 @@ describe('run', () => { }, ], }), - } as unknown) as CloudFormation), + } as unknown as CloudFormation), ); amplifyServiceManager_mock.init.mockResolvedValueOnce({} as any); JSONUtilities_mock.readJson.mockReturnValueOnce({}); diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/utils/api-key-helpers.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/utils/api-key-helpers.test.ts new file mode 100644 index 00000000000..ac142f3421f --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/utils/api-key-helpers.test.ts @@ -0,0 +1,91 @@ +import { getAppSyncApiConfig, getApiKeyConfig, apiKeyIsActive, hasApiKey } from '../../utils/api-key-helpers'; + +jest.mock('amplify-cli-core', () => { + const original = jest.requireActual('amplify-cli-core'); + const amplifyMeta = { + api: { + myapp: { + service: 'AppSync', + output: { + authConfig: { + defaultAuthentication: { + authenticationType: 'AWS_IAM', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'API_KEY', + apiKeyConfig: { + apiKeyExpirationDays: 2, + apiKeyExpirationDate: '2021-08-20T20:38:07.585Z', + description: '', + }, + }, + ], + }, + }, + }, + }, + }; + + return { + ...original, + stateManager: { + getMeta: jest.fn().mockImplementation(() => amplifyMeta), + }, + }; +}); + +describe('getAppSyncApiConfig', () => { + it('returns the api object', async () => { + const result = getAppSyncApiConfig(); + + expect(result).toStrictEqual({ + service: 'AppSync', + output: { + authConfig: { + defaultAuthentication: { + authenticationType: 'AWS_IAM', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'API_KEY', + apiKeyConfig: { + apiKeyExpirationDays: 2, + apiKeyExpirationDate: '2021-08-20T20:38:07.585Z', + description: '', + }, + }, + ], + }, + }, + }); + }); +}); + +describe('getApiKeyConfig', () => { + it('returns the api key config', () => { + const result = getApiKeyConfig(); + + expect(result).toStrictEqual({ + apiKeyExpirationDays: 2, + apiKeyExpirationDate: '2021-08-20T20:38:07.585Z', + description: '', + }); + }); +}); + +describe('apiKeyIsActive', () => { + describe('with expired key', () => { + it('returns false', () => { + expect(apiKeyIsActive()).toBe(false); + }); + }); +}); + +describe('hasApiKey', () => { + describe('if api key config is present', () => { + it('returns true if api key is present', () => { + expect(hasApiKey()).toBe(true); + }); + }); +}); diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/utils/sandbox-mode-helpers.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/utils/sandbox-mode-helpers.test.ts new file mode 100644 index 00000000000..c653c5ff48f --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/utils/sandbox-mode-helpers.test.ts @@ -0,0 +1,117 @@ +import { showSandboxModePrompts, removeSandboxDirectiveFromSchema, showGlobalSandboxModeWarning } from '../../utils/sandbox-mode-helpers'; +import { $TSContext } from 'amplify-cli-core'; +import chalk from 'chalk'; +import * as prompts from 'amplify-prompts'; +import * as apiKeyHelpers from '../../utils/api-key-helpers'; + +let ctx; +let apiKeyPresent = true; + +describe('sandbox mode helpers', () => { + beforeEach(() => { + const envName = 'dev'; + ctx = { + amplify: { + getEnvInfo() { + return { envName }; + }, + invokePluginMethod: jest.fn(), + }, + } as unknown as $TSContext; + + jest.spyOn(prompts.printer, 'info').mockImplementation(); + jest.spyOn(apiKeyHelpers, 'hasApiKey').mockReturnValue(apiKeyPresent); + }); + + describe('showSandboxModePrompts', () => { + describe('missing api key', () => { + beforeAll(() => { + apiKeyPresent = false; + }); + + it('displays warning', async () => { + await showSandboxModePrompts(ctx); + + expect(prompts.printer.info).toBeCalledWith( + ` +⚠️ WARNING: Global Sandbox Mode has been enabled, which requires a valid API key. If +you'd like to disable, remove ${chalk.green('"type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key"')} +from your GraphQL schema and run 'amplify push' again. If you'd like to proceed with +sandbox mode disabled in '${ctx.amplify.getEnvInfo().envName}', do not create an API Key. +`, + 'yellow', + ); + expect(ctx.amplify.invokePluginMethod).toBeCalledWith(ctx, 'api', undefined, 'promptToAddApiKey', [ctx]); + }); + }); + }); + + describe('showGlobalSandboxModeWarning', () => { + it('prints sandbox api key message', () => { + showGlobalSandboxModeWarning(); + + expect(prompts.printer.info).toBeCalledWith( + ` +⚠️ 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-transformer/auth +`, + 'yellow', + ); + }); + }); + + describe('removeSandboxDirectiveFromSchema', () => { + it('removes sandbox mode directive', () => { + const schema = ` +type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: "dev") + `; + + expect(removeSandboxDirectiveFromSchema(schema)).toEqual(` + + `); + }); + + it('does not change user schema with directive', () => { + const schema = ` +type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: "dev10105") # FOR TESTING ONLY! + +type Todo @model { + id: ID! + name: String! + description: String +} + `; + + expect(removeSandboxDirectiveFromSchema(schema)).toEqual(` + # FOR TESTING ONLY! + +type Todo @model { + id: ID! + name: String! + description: String +} + `); + }); + + it('does not change user schema with directive and single quotes', () => { + const schema = ` +type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: 'dev10105') # FOR TESTING ONLY! + +type Todo @model { + id: ID! + name: String! + description: String +} + `; + + expect(removeSandboxDirectiveFromSchema(schema)).toEqual(` + # FOR TESTING ONLY! + +type Todo @model { + id: ID! + name: String! + description: String +} + `); + }); + }); +}); diff --git a/packages/amplify-provider-awscloudformation/src/aws-utils/S3Service.ts b/packages/amplify-provider-awscloudformation/src/aws-utils/S3Service.ts index 26c6196882a..c0a4ddb9198 100644 --- a/packages/amplify-provider-awscloudformation/src/aws-utils/S3Service.ts +++ b/packages/amplify-provider-awscloudformation/src/aws-utils/S3Service.ts @@ -35,14 +35,20 @@ export class S3Service implements IS3Service { } public async bucketExists(bucketName: string): Promise { - const response = await this.s3 - .headBucket({ - Bucket: bucketName, - }) - .promise(); - - // If the return object has no keys then it means successful empty object was returned. - return Object.keys(response).length === 0; + try { + const response = await this.s3 + .headBucket({ + Bucket: bucketName, + }) + .promise(); + // If the return object has no keys then it means successful empty object was returned. + return Object.keys(response).length === 0; + } catch (error) { + if (error.code === 'NotFound') { + return false; + } + throw error; + } } public async getBucketLocation(bucketName: string): Promise { @@ -51,14 +57,12 @@ export class S3Service implements IS3Service { Bucket: bucketName, }) .promise(); - // For us-east-1 buckets the LocationConstraint is always emtpy, we have to return a // region in every case. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html - if (response.LocationConstraint === '' || response.LocationConstraint === null) { + if (response.LocationConstraint === undefined || response.LocationConstraint === '' || response.LocationConstraint === null) { return 'us-east-1'; } - return response.LocationConstraint; } } diff --git a/packages/amplify-provider-awscloudformation/src/aws-utils/aws-location.ts b/packages/amplify-provider-awscloudformation/src/aws-utils/aws-location.ts index e01d7012f75..cef85c73fdc 100644 --- a/packages/amplify-provider-awscloudformation/src/aws-utils/aws-location.ts +++ b/packages/amplify-provider-awscloudformation/src/aws-utils/aws-location.ts @@ -21,16 +21,16 @@ const serviceRegionMap = { 'ap-northeast-2': 'us-west-2', 'eu-west-2': 'eu-west-1', 'eu-west-3': 'eu-west-1', - 'me-south-1': 'ap-southeast-1' + 'me-south-1': 'ap-southeast-1', }; export const getLocationSupportedRegion = (region: string): string => { - if (serviceRegionMap[region]) { + if (serviceRegionMap[region]) { return serviceRegionMap[region]; - } - return defaultLocationRegion; + } + return defaultLocationRegion; }; export const getLocationRegionMapping = (): $TSObject => { - return serviceRegionMap; -} + return serviceRegionMap; +}; diff --git a/packages/amplify-provider-awscloudformation/src/aws-utils/aws-pinpoint.js b/packages/amplify-provider-awscloudformation/src/aws-utils/aws-pinpoint.js index c72194b8c6a..08d0aed06b3 100644 --- a/packages/amplify-provider-awscloudformation/src/aws-utils/aws-pinpoint.js +++ b/packages/amplify-provider-awscloudformation/src/aws-utils/aws-pinpoint.js @@ -3,31 +3,8 @@ const { FeatureFlags } = require('amplify-cli-core'); const proxyAgent = require('proxy-agent'); const configurationManager = require('../configuration-manager'); const { formUserAgentParam } = require('./user-agent'); -const latestPinpointRegions = FeatureFlags.getNumber('latestRegionSupport.pinpoint'); const defaultPinpointRegion = 'us-east-1'; -const serviceRegionMap = { - 'us-east-1': 'us-east-1', - 'us-east-2': 'us-east-1', - 'sa-east-1': 'us-east-1', - 'ca-central-1': latestPinpointRegions >= 1 ? 'ca-central-1' : 'us-east-1', - 'us-west-1': 'us-west-2', - 'us-west-2': 'us-west-2', - 'cn-north-1': 'us-west-2', - 'cn-northwest-1': 'us-west-2', - 'ap-south-1': latestPinpointRegions >= 1 ? 'ap-south-1' : 'us-west-2', - 'ap-northeast-3': 'us-west-2', - 'ap-northeast-2': latestPinpointRegions >= 1 ? 'ap-northeast-2' : 'us-west-2', - 'ap-southeast-1': latestPinpointRegions >= 1 ? 'ap-southeast-1' : 'us-west-2', - 'ap-southeast-2': latestPinpointRegions >= 1 ? 'ap-southeast-2' : 'us-west-2', - 'ap-northeast-1': latestPinpointRegions >= 1 ? 'ap-northeast-1' : 'us-west-2', - 'eu-central-1': 'eu-central-1', - 'eu-north-1': 'eu-central-1', - 'eu-west-1': 'eu-west-1', - 'eu-west-2': latestPinpointRegions >= 1 ? 'eu-west-2' : 'eu-west-1', - 'eu-west-3': 'eu-west-1', - 'me-south-1': 'ap-south-1', -}; async function getConfiguredPinpointClient(context, category, action, envName) { let cred = {}; @@ -62,6 +39,7 @@ async function getConfiguredPinpointClient(context, category, action, envName) { } function mapServiceRegion(region) { + const serviceRegionMap = getPinpointRegionMapping(); if (serviceRegionMap[region]) { return serviceRegionMap[region]; } @@ -69,7 +47,30 @@ function mapServiceRegion(region) { } function getPinpointRegionMapping() { - return serviceRegionMap; + const latestPinpointRegions = FeatureFlags.getNumber('latestRegionSupport.pinpoint'); + + return { + 'us-east-1': 'us-east-1', + 'us-east-2': 'us-east-1', + 'sa-east-1': 'us-east-1', + 'ca-central-1': latestPinpointRegions >= 1 ? 'ca-central-1' : 'us-east-1', + 'us-west-1': 'us-west-2', + 'us-west-2': 'us-west-2', + 'cn-north-1': 'us-west-2', + 'cn-northwest-1': 'us-west-2', + 'ap-south-1': latestPinpointRegions >= 1 ? 'ap-south-1' : 'us-west-2', + 'ap-northeast-3': 'us-west-2', + 'ap-northeast-2': latestPinpointRegions >= 1 ? 'ap-northeast-2' : 'us-west-2', + 'ap-southeast-1': latestPinpointRegions >= 1 ? 'ap-southeast-1' : 'us-west-2', + 'ap-southeast-2': latestPinpointRegions >= 1 ? 'ap-southeast-2' : 'us-west-2', + 'ap-northeast-1': latestPinpointRegions >= 1 ? 'ap-northeast-1' : 'us-west-2', + 'eu-central-1': 'eu-central-1', + 'eu-north-1': 'eu-central-1', + 'eu-west-1': 'eu-west-1', + 'eu-west-2': latestPinpointRegions >= 1 ? 'eu-west-2' : 'eu-west-1', + 'eu-west-3': 'eu-west-1', + 'me-south-1': 'ap-south-1', + }; } module.exports = { diff --git a/packages/amplify-provider-awscloudformation/src/constants.js b/packages/amplify-provider-awscloudformation/src/constants.js index d05deef8387..4b6232b492c 100644 --- a/packages/amplify-provider-awscloudformation/src/constants.js +++ b/packages/amplify-provider-awscloudformation/src/constants.js @@ -15,4 +15,5 @@ module.exports = { FunctionCategoryName: 'function', // keep in sync with ServiceName in amplify-category-function, but probably it will not change FunctionServiceNameLambdaLayer: 'LambdaLayer', + destructiveUpdatesFlag: 'allow-destructive-graphql-schema-updates', }; diff --git a/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/index.ts b/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/index.ts new file mode 100644 index 00000000000..40f83217ef5 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/index.ts @@ -0,0 +1,64 @@ +import { $TSAny, $TSContext, pathManager, stateManager } from 'amplify-cli-core'; +import { CloudFormation } from 'aws-sdk'; +import { S3 } from '../aws-utils/aws-s3'; +import { loadConfiguration } from '../configuration-manager'; +import { DeploymentStep } from '../iterative-deployment'; +import { + getDependentFunctions, + generateIterativeFuncDeploymentSteps, + prependDeploymentSteps, + generateTempFuncCFNTemplates, + uploadTempFuncDeploymentFiles, + s3Prefix, + localPrefix, +} from './utils'; +import * as fs from 'fs-extra'; + +let functionsDependentOnReplacedModelTables: string[] = []; + +/** + * Identifies if any functions depend on a model table that is being replaced. + * If so, it creates temporary CFN templates for these functions that do not reference the replaced table and adds deployment steps to the array to update the functions before the table is replaced + * @param context Amplify context + * @param modelsBeingReplaced Names of the models being replaced during this push operation + * @param deploymentSteps The existing list of deployment steps that will be prepended to in the case of dependent functions + * @returns The new list of deploymentSteps + */ +export const prependDeploymentStepsToDisconnectFunctionsFromReplacedModelTables = async ( + context: $TSContext, + modelsBeingReplaced: string[], + deploymentSteps: DeploymentStep[], +): Promise => { + const amplifyMeta = stateManager.getMeta(); + const rootStackId = amplifyMeta?.providers?.awscloudformation?.StackId; + const allFunctionNames = Object.keys(amplifyMeta?.function || {}); + functionsDependentOnReplacedModelTables = await getDependentFunctions( + modelsBeingReplaced, + allFunctionNames, + getFunctionParamsSupplier(context), + ); + // generate deployment steps that will remove references to the replaced tables in the dependent functions + const { deploymentSteps: disconnectFuncsSteps, lastMetaKey } = await generateIterativeFuncDeploymentSteps( + new CloudFormation(await loadConfiguration(context)), + rootStackId, + functionsDependentOnReplacedModelTables, + ); + await generateTempFuncCFNTemplates(functionsDependentOnReplacedModelTables); + await uploadTempFuncDeploymentFiles(await S3.getInstance(context), functionsDependentOnReplacedModelTables); + return prependDeploymentSteps(disconnectFuncsSteps, deploymentSteps, lastMetaKey); +}; + +export const postDeploymentCleanup = async (s3Client: S3, deploymentBucketName: string) => { + if (functionsDependentOnReplacedModelTables.length < 1) { + return; + } + await s3Client.deleteDirectory(deploymentBucketName, s3Prefix); + await Promise.all(functionsDependentOnReplacedModelTables.map(funcName => fs.remove(localPrefix(funcName)))); +}; + +// helper function to load the function-parameters.json file given a functionName +const getFunctionParamsSupplier = (context: $TSContext) => async (functionName: string) => { + return context.amplify.invokePluginMethod(context, 'function', undefined, 'loadFunctionParameters', [ + pathManager.getResourceDirectoryPath(undefined, 'function', functionName), + ]) as $TSAny; +}; diff --git a/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/utils.ts b/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/utils.ts new file mode 100644 index 00000000000..e91ede4168f --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/disconnect-dependent-resources/utils.ts @@ -0,0 +1,183 @@ +import { $TSAny, JSONUtilities, pathManager, readCFNTemplate, stateManager, writeCFNTemplate } from 'amplify-cli-core'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { S3 } from '../aws-utils/aws-s3'; +import { fileLogger } from '../utils/aws-logger'; +import { CloudFormation } from 'aws-sdk'; +import { getPreviousDeploymentRecord } from '../utils/amplify-resource-state-utils'; +import { DeploymentOp, DeploymentStep } from '../iterative-deployment'; +import _ from 'lodash'; + +const logger = fileLogger('disconnect-dependent-resources'); + +/** + * Returns the subset of functionNames that have a dependency on a model in modelNames + */ +export const getDependentFunctions = async ( + modelNames: string[], + functionNames: string[], + functionParamsSupplier: (functionName: string) => Promise<$TSAny>, +) => { + const dependentFunctions: string[] = []; + for (const funcName of functionNames) { + const funcParams = await functionParamsSupplier(funcName); + const dependentModels = funcParamsToDependentAppSyncModels(funcParams); + const hasDep = dependentModels.map(model => modelNames.includes(model)).reduce((acc, it) => acc || it, false); + if (hasDep) { + dependentFunctions.push(funcName); + } + } + return dependentFunctions; +}; + +/** + * Generates temporary CFN templates for the given functions that have placeholder values for all references to replaced model tables + */ +export const generateTempFuncCFNTemplates = async (dependentFunctions: string[]) => { + const tempPaths: string[] = []; + for (const funcName of dependentFunctions) { + const { cfnTemplate, templateFormat } = await readCFNTemplate( + path.join(pathManager.getResourceDirectoryPath(undefined, 'function', funcName), `${funcName}-cloudformation-template.json`), + ); + replaceFnImport(cfnTemplate); + const tempPath = getTempFuncTemplateLocalPath(funcName); + await writeCFNTemplate(cfnTemplate, tempPath, { templateFormat }); + tempPaths.push(tempPath); + } +}; + +/** + * Uploads the CFN template and iterative deployment meta file to S3 + */ +export const uploadTempFuncDeploymentFiles = async (s3Client: S3, funcNames: string[]) => { + for (const funcName of funcNames) { + const uploads = [ + { + Body: fs.createReadStream(getTempFuncTemplateLocalPath(funcName)), + Key: getTempFuncTemplateS3Key(funcName), + }, + { + Body: fs.createReadStream(getTempFuncMetaLocalPath(funcName)), + Key: getTempFuncMetaS3Key(funcName), + }, + ]; + const log = logger('uploadTemplateToS3.s3.uploadFile', [{ Key: uploads[0].Key }]); + for (const upload of uploads) { + try { + await s3Client.uploadFile(upload, false); + } catch (error) { + log(error); + throw error; + } + } + } +}; + +export const generateIterativeFuncDeploymentSteps = async ( + cfnClient: CloudFormation, + rootStackId: string, + functionNames: string[], +): Promise<{ deploymentSteps: DeploymentStep[]; lastMetaKey: string }> => { + let rollback: DeploymentOp; + let previousMetaKey: string; + const steps: DeploymentStep[] = []; + for (const funcName of functionNames) { + const deploymentOp = await generateIterativeFuncDeploymentOp(cfnClient, rootStackId, funcName); + deploymentOp.previousMetaKey = previousMetaKey; + steps.push({ + deployment: deploymentOp, + rollback, + }); + rollback = deploymentOp; + previousMetaKey = getTempFuncMetaS3Key(funcName); + } + return { deploymentSteps: steps, lastMetaKey: previousMetaKey }; +}; + +/** + * Prepends beforeSteps and afterSteps into a single array of deployment steps. + * Moves rollback and previousMetaKey pointers to maintain the integrity of the deployment steps. + */ +export const prependDeploymentSteps = (beforeSteps: DeploymentStep[], afterSteps: DeploymentStep[], beforeStepsLastMetaKey: string) => { + if (beforeSteps.length === 0) { + return afterSteps; + } + beforeSteps[0].rollback = _.cloneDeep(afterSteps[0].rollback); + beforeSteps[0].deployment.previousMetaKey = afterSteps[0].deployment.previousMetaKey; + afterSteps[0].rollback = _.cloneDeep(beforeSteps[beforeSteps.length - 1].deployment); + afterSteps[0].deployment.previousMetaKey = beforeStepsLastMetaKey; + if (afterSteps.length > 1) { + afterSteps[1].rollback.previousMetaKey = beforeStepsLastMetaKey; + } + return beforeSteps.concat(afterSteps); +}; + +/** + * Generates a deployment operation for a temporary function deployment. + * Also writes the deployment operation to the temp meta path + */ +const generateIterativeFuncDeploymentOp = async (cfnClient: CloudFormation, rootStackId: string, functionName: string) => { + const funcStack = await cfnClient + .describeStackResources({ StackName: rootStackId, LogicalResourceId: `function${functionName}` }) + .promise(); + const funcStackId = funcStack.StackResources[0].PhysicalResourceId; + const { parameters, capabilities } = await getPreviousDeploymentRecord(cfnClient, funcStackId); + const funcCfnParams = stateManager.getResourceParametersJson(undefined, 'function', functionName, { + throwIfNotExist: false, + default: {}, + }); + const tpi = stateManager.getTeamProviderInfo(undefined, { throwIfNotExist: false, default: {} }); + const env = stateManager.getLocalEnvInfo().envName; + const tpiCfnParams = tpi?.[env]?.categories?.function?.[functionName] || {}; + const params = { ...parameters, ...funcCfnParams, ...tpiCfnParams }; + const deploymentStep: DeploymentOp = { + stackTemplatePathOrUrl: getTempFuncTemplateS3Key(functionName), + parameters: params, + stackName: funcStackId, + capabilities, + tableNames: [], + }; + + JSONUtilities.writeJson(getTempFuncMetaLocalPath(functionName), deploymentStep); + return deploymentStep; +}; + +// helper functions for constructing local paths and S3 keys for function templates and deployment meta files +const getTempFuncTemplateS3Key = (funcName: string): string => path.posix.join(s3Prefix, tempTemplateFilename(funcName)); +const getTempFuncTemplateLocalPath = (funcName: string): string => path.join(localPrefix(funcName), tempTemplateFilename(funcName)); +const getTempFuncMetaLocalPath = (funcName: string): string => path.join(localPrefix(funcName), tempMetaFilename(funcName)); +const getTempFuncMetaS3Key = (funcName: string): string => path.posix.join(s3Prefix, tempMetaFilename(funcName)); + +const tempTemplateFilename = (funcName: string) => `temp-${funcName}-cloudformation-template.json`; +const tempMetaFilename = (funcName: string) => `temp-${funcName}-deployment-meta.json`; +export const s3Prefix = 'amplify-cfn-templates/function/temp'; +export const localPrefix = funcName => path.join(pathManager.getResourceDirectoryPath(undefined, 'function', funcName), 'temp'); + +/** + * Recursively searches for 'Fn::ImportValue' nodes in a CFN template object and replaces them with a placeholder value + * @param node + * @returns + */ +const replaceFnImport = (node: $TSAny) => { + if (typeof node !== 'object') { + return; + } + if (Array.isArray(node)) { + node.forEach(el => replaceFnImport(el)); + } + const nodeKeys = Object.keys(node); + if (nodeKeys.length === 1 && nodeKeys[0] === 'Fn::ImportValue') { + node['Fn::ImportValue'] = undefined; + node['Fn::Sub'] = 'TemporaryPlaceholderValue'; + return; + } + Object.values(node).forEach(value => replaceFnImport(value)); +}; + +/** + * Given the contents of the function-parameters.json file for a function, returns the list of AppSync models this function depends on. + */ +const funcParamsToDependentAppSyncModels = (funcParams: $TSAny): string[] => + Object.keys(funcParams?.permissions?.storage || {}) + .filter(key => key.endsWith(':@model(appsync)')) + .map(key => key.slice(0, key.lastIndexOf(':'))); diff --git a/packages/amplify-provider-awscloudformation/src/graphql-transformer/amplify-graphql-resource-manager.ts b/packages/amplify-provider-awscloudformation/src/graphql-transformer/amplify-graphql-resource-manager.ts index cd82d286df8..2222c0fcceb 100644 --- a/packages/amplify-provider-awscloudformation/src/graphql-transformer/amplify-graphql-resource-manager.ts +++ b/packages/amplify-provider-awscloudformation/src/graphql-transformer/amplify-graphql-resource-manager.ts @@ -26,6 +26,7 @@ export type GQLResourceManagerProps = { resourceMeta?: ResourceMeta; backendDir: string; cloudBackendDir: string; + rebuildAllTables?: boolean; }; export type ResourceMeta = { @@ -52,8 +53,9 @@ export class GraphQLResourceManager { private cloudBackendApiProjectRoot: string; private backendApiProjectRoot: string; private templateState: TemplateState; + private rebuildAllTables: boolean = false; // indicates that all underlying model tables should be rebuilt - public static createInstance = async (context: $TSContext, gqlResource: any, StackId: string) => { + public static createInstance = async (context: $TSContext, gqlResource: any, StackId: string, rebuildAllTables: boolean = false) => { try { const cred = await loadConfiguration(context); const cfn = new CloudFormation(cred); @@ -65,6 +67,7 @@ export class GraphQLResourceManager { resourceMeta: { ...gqlResource, stackId: apiStack.StackResources[0].PhysicalResourceId }, backendDir: pathManager.getBackendDirPath(), cloudBackendDir: pathManager.getCurrentCloudBackendDirPath(), + rebuildAllTables, }); } catch (err) { throw err; @@ -82,6 +85,7 @@ export class GraphQLResourceManager { this.backendApiProjectRoot = path.join(props.backendDir, GraphQLResourceManager.categoryName, this.resourceMeta.resourceName); this.cloudBackendApiProjectRoot = path.join(props.cloudBackendDir, GraphQLResourceManager.categoryName, this.resourceMeta.resourceName); this.templateState = new TemplateState(); + this.rebuildAllTables = props.rebuildAllTables || false; } run = async (): Promise => { @@ -102,7 +106,10 @@ export class GraphQLResourceManager { throw err; } } - this.gsiManagement(gqlDiff.diff, gqlDiff.current, gqlDiff.next); + if (!this.rebuildAllTables) { + this.gsiManagement(gqlDiff.diff, gqlDiff.current, gqlDiff.next); + } + this.tableRecreationManagement(gqlDiff.current, gqlDiff.next); return await this.getDeploymentSteps(); }; @@ -217,9 +224,9 @@ export class GraphQLResourceManager { }); const tableWithGSIChanges = _.uniqBy(gsiChanges, diff => diff.path?.slice(0, 3).join('/')).map(gsiChange => { - const tableName = gsiChange.path[3]; + const tableName = gsiChange.path[3] as string; - const stackName = gsiChange.path[1].split('.')[0]; + const stackName = gsiChange.path[1].split('.')[0] as string; const currentTable = this.getTable(gsiChange, currentState); const nextTable = this.getTable(gsiChange, nextState); @@ -266,6 +273,44 @@ export class GraphQLResourceManager { } }; + private tableRecreationManagement = (currentState: DiffableProject, nextState: DiffableProject) => { + this.getTablesBeingReplaced().forEach(tableMeta => { + const ddbResource = this.getStack(tableMeta.stackName, currentState); + this.dropTable(tableMeta.tableName, ddbResource); + // clear any other states created by GSI updates as dropping and recreating supercedes those changes + this.clearTemplateState(tableMeta.stackName); + this.templateState.add(tableMeta.stackName, JSONUtilities.stringify(ddbResource)); + this.templateState.add(tableMeta.stackName, JSONUtilities.stringify(this.getStack(tableMeta.stackName, nextState))); + }); + }; + + getTablesBeingReplaced = () => { + const gqlDiff = getGQLDiff(this.backendApiProjectRoot, this.cloudBackendApiProjectRoot); + const [diffs, currentState] = [gqlDiff.diff, gqlDiff.current]; + const getTablesRequiringReplacement = () => { + if (!diffs) { + return []; + } + return _.uniq( + diffs + .filter(diff => diff.path.includes('KeySchema') || diff.path.includes('LocalSecondaryIndexes')) // filter diffs with changes that require replacement + .map(diff => ({ + // extract table name and stack name from diff path + tableName: diff.path?.[3] as string, + stackName: diff.path[1].split('.')[0] as string, + })), + ) as { tableName: string; stackName: string }[]; + } + const getAllTables = () => + Object.entries(currentState.stacks) + .map(([name, template]) => ({ + tableName: this.getTableNameFromTemplate(template), + stackName: path.basename(name, '.json'), + })) + .filter(meta => !!meta.tableName); + return this.rebuildAllTables ? getAllTables() : getTablesRequiringReplacement(); + }; + private getTable = (gsiChange: Diff, proj: DiffableProject): DynamoDB.Table => { return proj.stacks[gsiChange.path[1]].Resources[gsiChange.path[3]] as DynamoDB.Table; }; @@ -283,6 +328,21 @@ export class GraphQLResourceManager { const table = template.Resources[tableName] as DynamoDB.Table; template.Resources[tableName] = removeGSI(indexName, table); }; + + private dropTable = (tableName: string, template: Template): void => { + // remove table and all output refs to it + template.Resources[tableName] = undefined; + template.Outputs = _.omitBy(template.Outputs, (_, key) => key.includes(tableName)); + }; + + private clearTemplateState = (stackName: string) => { + while (this.templateState.has(stackName)) { + this.templateState.pop(stackName); + } + }; + + private getTableNameFromTemplate = (template: Template): string | undefined => + Object.entries(template?.Resources || {}).find(([_, resource]) => resource.Type === 'AWS::DynamoDB::Table')?.[0]; } // https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript diff --git a/packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts b/packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts index 29429ef53f7..01787185539 100644 --- a/packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts +++ b/packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts @@ -3,14 +3,16 @@ import path from 'path'; import importGlobal from 'import-global'; import { print } from 'graphql'; import importFrom from 'import-from'; -import { TransformerPluginProvider } from '@aws-amplify/graphql-transformer-interfaces'; +import { TransformerPluginProvider, AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; import { getAppSyncServiceExtraDirectives, GraphQLTransform, collectDirectivesByTypeNames, + collectDirectives, TransformerProjectConfig, } from '@aws-amplify/graphql-transformer-core'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; import { FunctionTransformer } from '@aws-amplify/graphql-function-transformer'; import { HttpTransformer } from '@aws-amplify/graphql-http-transformer'; import { PredictionsTransformer } from '@aws-amplify/graphql-predictions-transformer'; @@ -25,15 +27,20 @@ import { SearchableModelTransformer } from '@aws-amplify/graphql-searchable-tran import { DefaultValueTransformer } from '@aws-amplify/graphql-default-value-transformer'; import { ProviderName as providerName } from '../constants'; import { hashDirectory } from '../upload-appsync-files'; -import { writeDeploymentToDisk } from './utils'; +import { mergeUserConfigWithTransformOutput, writeDeploymentToDisk } from './utils'; import { loadProject as readTransformerConfiguration } from './transform-config'; import { loadProject } from 'graphql-transformer-core'; -import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-core'; import { Template } from '@aws-amplify/graphql-transformer-core/lib/config/project-config'; import { AmplifyCLIFeatureFlagAdapter } from '../utils/amplify-cli-feature-flag-adapter'; -import { JSONUtilities } from 'amplify-cli-core'; +import { JSONUtilities, stateManager, $TSContext } from 'amplify-cli-core'; import { searchablePushChecks } from '../transform-graphql-schema'; import { ResourceConstants } from 'graphql-transformer-common'; +import { isAmplifyAdminApp } from '../utils/admin-helpers'; +import { + showSandboxModePrompts, + getSandboxModeEnvNameFromDirectiveSet, + removeSandboxDirectiveFromSchema, +} from '../utils/sandbox-mode-helpers'; const API_CATEGORY = 'api'; const STORAGE_CATEGORY = 'storage'; @@ -55,11 +62,28 @@ function warnOnAuth(context, map) { } } -function getTransformerFactory(context, resourceDir) { +function getTransformerFactory( + context: $TSContext, + resourceDir: string, +): (options: TransformerFactoryArgs) => Promise { return async (options?: TransformerFactoryArgs) => { + // TODO: Build dependency mechanism into transformers. Auth runs last + // so any resolvers that need to be protected will already be created. + + let adminUserPoolID: string; + try { + const amplifyMeta = stateManager.getMeta(); + const appId = amplifyMeta?.providers?.[providerName]?.AmplifyAppId; + const res = await isAmplifyAdminApp(appId); + adminUserPoolID = res.userPoolID; + } catch (err) { + console.info('App not deployed yet.'); + } + const modelTransformer = new ModelTransformer(); const indexTransformer = new IndexTransformer(); const hasOneTransformer = new HasOneTransformer(); + const authTransformer = new AuthTransformer({ authConfig: options?.authConfig, addAwsIamAuthInOutputSchema: false, adminUserPoolID }); const transformerList: TransformerPluginProvider[] = [ modelTransformer, new FunctionTransformer(), @@ -70,8 +94,9 @@ function getTransformerFactory(context, resourceDir) { new BelongsToTransformer(), new HasManyTransformer(), hasOneTransformer, - new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer), + new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer), new DefaultValueTransformer(), + authTransformer, // TODO: initialize transformer plugins ]; @@ -145,10 +170,6 @@ function getTransformerFactory(context, resourceDir) { transformerList.push(...customTransformers); } - // TODO: Build dependency mechanism into transformers. Auth runs last - // so any resolvers that need to be protected will already be created. - // transformerList.push(new ModelAuthTransformer({ authConfig })); - return transformerList; }; } @@ -224,7 +245,7 @@ export async function transformGraphQLSchema(context, options) { } } - let { authConfig } = options; + let { authConfig }: { authConfig: AppSyncAuthConfiguration } = options; // // If we don't have an authConfig from the caller, use it from the @@ -283,6 +304,17 @@ export async function transformGraphQLSchema(context, options) { const transformerListFactory = getTransformerFactory(context, resourceDir); + const { envName } = context.amplify._getEnvInfo(); + const sandboxModeEnv = getSandboxModeEnvNameFromDirectiveSet(collectDirectives(project.schema)); + const sandboxModeEnabled = envName === sandboxModeEnv; + + if (sandboxModeEnabled && options.promptApiKeyCreation) { + const apiKeyConfig = await showSandboxModePrompts(context); + if (apiKeyConfig) { + authConfig.additionalAuthenticationProviders.push(apiKeyConfig); + } + } + let searchableTransformerFlag = false; if (directiveMap.directives.includes('searchable')) { @@ -294,17 +326,19 @@ export async function transformGraphQLSchema(context, options) { buildParameters, projectDirectory: resourceDir, transformersFactory: transformerListFactory, - transformersFactoryArgs: { addSearchableTransformer: searchableTransformerFlag, storageConfig }, + transformersFactoryArgs: { addSearchableTransformer: searchableTransformerFlag, storageConfig, authConfig }, rootStackFileName: 'cloudformation-template.json', currentCloudBackendDirectory: previouslyDeployedBackendDir, minify: options.minify, projectConfig: project, lastDeployedProjectConfig, authConfig, + sandboxModeEnabled, }; + const transformerOutput = await buildAPIProject(buildConfig); - context.print.success(`\nGraphQL schema compiled successfully.\n\nEdit your schema at ${schemaFilePath} or \ + context.print.success(`GraphQL schema compiled successfully.\n\nEdit your schema at ${schemaFilePath} or \ place .graphql files in a directory at ${schemaDirPath}`); if (!options.dryRun) { @@ -314,6 +348,17 @@ place .graphql files in a directory at ${schemaDirPath}`); return transformerOutput; } +async function addGraphQLAuthRequirement(context, authType) { + return await context.amplify.invokePluginMethod(context, 'api', undefined, 'addGraphQLAuthorizationMode', [ + context, + { + authType: authType, + printLeadText: true, + authSettings: undefined, + }, + ]); +} + function getProjectBucket(context) { const projectDetails = context.amplify.getProjectDetails(); const projectBucket = projectDetails.amplifyMeta.providers ? projectDetails.amplifyMeta.providers[providerName].DeploymentBucketName : ''; @@ -336,8 +381,8 @@ async function getPreviousDeploymentRootKey(previouslyDeployedBackendDir) { } } -export async function getDirectiveDefinitions(context, resourceDir) { - const transformList = await getTransformerFactory(context, resourceDir)({ addSearchableTransformer: true }); +export async function getDirectiveDefinitions(context: $TSContext, resourceDir: string) { + const transformList = await getTransformerFactory(context, resourceDir)({ addSearchableTransformer: true, authConfig: {} }); const appSynDirectives = getAppSyncServiceExtraDirectives(); const transformDirectives = transformList .map(transformPluginInst => [transformPluginInst.directive, ...transformPluginInst.typeDefinitions].map(node => print(node)).join('\n')) @@ -384,6 +429,7 @@ function getBucketName(context, s3ResourceName, backEndDir) { type TransformerFactoryArgs = { addSearchableTransformer: boolean; + authConfig: any; storageConfig?: any; }; export type ProjectOptions = { @@ -392,7 +438,7 @@ export type ProjectOptions = { S3DeploymentRootKey: string; }; projectDirectory?: string; - transformersFactory: (options: T) => TransformerPluginProvider[]; + transformersFactory: (options: T) => Promise; transformersFactoryArgs: T; rootStackFileName: 'cloudformation-template.json'; currentCloudBackendDirectory: string; @@ -402,6 +448,7 @@ export type ProjectOptions = { dryRun?: boolean; authConfig?: AppSyncAuthConfiguration; stacks: Record; + sandboxModeEnabled?: boolean; }; export async function buildAPIProject(opts: ProjectOptions) { @@ -419,6 +466,9 @@ export async function buildAPIProject(opts: ProjectOptions) { buildParameters: opts.buildParameters, stacks: opts.projectConfig.stacks || {}, featureFlags: new AmplifyCLIFeatureFlagAdapter(), + sandboxModeEnabled: opts.sandboxModeEnabled, }); - return transform.transform(userProjectConfig.schema.toString()); + + let schema = userProjectConfig.schema.toString(); + if (opts.sandboxModeEnabled) schema = removeSandboxDirectiveFromSchema(schema); + + const transformOutput = transform.transform(schema); + + return mergeUserConfigWithTransformOutput(userProjectConfig, transformOutput); } diff --git a/packages/amplify-provider-awscloudformation/src/graphql-transformer/utils.ts b/packages/amplify-provider-awscloudformation/src/graphql-transformer/utils.ts index c849a903223..e656f56ad9e 100644 --- a/packages/amplify-provider-awscloudformation/src/graphql-transformer/utils.ts +++ b/packages/amplify-provider-awscloudformation/src/graphql-transformer/utils.ts @@ -1,10 +1,11 @@ import fs from 'fs-extra'; import * as path from 'path'; -import { DeploymentResources } from '@aws-amplify/graphql-transformer-core'; +import { TransformerProjectConfig, DeploymentResources } from '@aws-amplify/graphql-transformer-core'; import rimraf from 'rimraf'; import { JSONUtilities } from 'amplify-cli-core'; -import { Template } from 'cloudform'; +import { CloudFormation, Template, Fn } from 'cloudform'; import { Diff, diff as getDiffs } from 'deep-diff'; +import { ResourceConstants } from 'graphql-transformer-common'; const ROOT_STACK_FILE_NAME = 'cloudformation-template.json'; const PARAMETERS_FILE_NAME = 'parameters.json'; @@ -34,20 +35,11 @@ export const getGQLDiff = (currentBackendDir: string, cloudBackendDir: string): return null; }; -export const getGqlUpdatedResource = (resources: any[]) => { - if (resources.length > 0) { - const resource = resources[0]; - if ( - resource.service === 'AppSync' && - resource.providerMetadata && - resource.providerMetadata.logicalId && - resource.providerPlugin === 'awscloudformation' - ) { - return resource; - } - } - return null; -}; +export const getGqlUpdatedResource = (resources: any[]) => + resources.find( + resource => + resource?.service === 'AppSync' && resource?.providerMetadata?.logicalId && resource?.providerPlugin === 'awscloudformation', + ) || null; export function loadDiffableProject(path: string, rootStackName: string): DiffableProject { const project = readFromPath(path); @@ -85,6 +77,104 @@ export function readFromPath(directory: string): any { return accum; } +export function mergeUserConfigWithTransformOutput( + userConfig: TransformerProjectConfig, + transformOutput: DeploymentResources, +): DeploymentResources { + const userFunctions = userConfig.functions || {}; + const userResolvers = userConfig.resolvers || {}; + const userPipelineFunctions = userConfig.pipelineFunctions || {}; + const functions = transformOutput.functions; + const pipelineFunctions = transformOutput.pipelineFunctions; + + for (const userFunction of Object.keys(userFunctions)) functions[userFunction] = userFunctions[userFunction]; + for (const userPipelineFunction of Object.keys(userPipelineFunctions)) + pipelineFunctions[userPipelineFunction] = userPipelineFunctions[userPipelineFunction]; + for (const userResolver of Object.keys(userResolvers)) pipelineFunctions[userResolver] = userResolvers[userResolver]; + + const stacks = overrideUserDefinedStacks(userConfig, transformOutput); + + return { + ...transformOutput, + functions, + pipelineFunctions, + stacks, + }; +} + +function overrideUserDefinedStacks(userConfig: TransformerProjectConfig, transformOutput: DeploymentResources) { + const userStacks = userConfig.stacks || {}; + const { stacks, rootStack } = transformOutput; + + const resourceTypesToDependOn = { + 'AWS::CloudFormation::Stack': true, + 'AWS::AppSync::GraphQLApi': true, + 'AWS::AppSync::GraphQLSchema': true, + }; + + const allResourceIds = Object.keys(rootStack.Resources).filter((k: string) => { + const resource = rootStack.Resources[k]; + return resourceTypesToDependOn[resource.Type]; + }); + + const parametersKeys = Object.keys(rootStack.Parameters); + const customStackParams = parametersKeys.reduce( + (acc: any, k: string) => ({ + ...acc, + [k]: Fn.Ref(k), + }), + {}, + ); + + customStackParams[ResourceConstants.PARAMETERS.AppSyncApiId] = Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'); + + let updatedParameters = rootStack.Parameters; + + for (const userStack of Object.keys(userStacks)) { + if (stacks[userStack]) { + throw new Error(`You cannot provide a stack named ${userStack} as it \ + will be overwritten by a stack generated by the GraphQL Transform.`); + } + const userDefinedStack = userStacks[userStack]; + + for (const key of Object.keys(userDefinedStack.Parameters)) { + if (customStackParams[key] == null) { + customStackParams[key] = Fn.Ref(key); + + if (updatedParameters[key]) throw new Error(`Cannot redefine CloudFormation parameter ${key} in stack ${userStack}.`); + else updatedParameters[key] = userDefinedStack.Parameters[key]; + } + } + + const parametersForStack = Object.keys(userDefinedStack.Parameters).reduce( + (acc, k) => ({ + ...acc, + [k]: customStackParams[k], + }), + {}, + ); + + stacks[userStack] = userDefinedStack; + + const stackResourceId = userStack.split(/[^A-Za-z]/).join(''); + const customNestedStack = new CloudFormation.Stack({ + Parameters: parametersForStack, + TemplateURL: Fn.Join('/', [ + 'https://s3.amazonaws.com', + Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket), + Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey), + 'stacks', + userStack, + ]), + }).dependsOn(allResourceIds); + rootStack.Resources[stackResourceId] = customNestedStack; + } + + rootStack.Parameters = updatedParameters; + + return stacks; +} + /** * Writes a deployment to disk at a path. */ diff --git a/packages/amplify-provider-awscloudformation/src/index.ts b/packages/amplify-provider-awscloudformation/src/index.ts index 3e5a696e0b9..d90f3de0ece 100644 --- a/packages/amplify-provider-awscloudformation/src/index.ts +++ b/packages/amplify-provider-awscloudformation/src/index.ts @@ -37,6 +37,7 @@ export { getLocationSupportedRegion, getLocationRegionMapping } from './aws-util import { updateEnv } from './update-env'; import { uploadHooksDirectory } from './utils/hooks-manager'; +import { getTransformerVersion } from './transform-graphql-schema'; export const cfnRootStackFileName = 'root-cloudformation-stack.json'; export { storeRootStackTemplate } from './initializer'; @@ -64,8 +65,8 @@ function onInitSuccessful(context) { return initializer.onInitSuccessful(context); } -function pushResources(context, resourceList) { - return resourcePusher.run(context, resourceList); +function pushResources(context, resourceList, rebuild: boolean = false) { + return resourcePusher.run(context, resourceList, rebuild); } function storeCurrentCloudBackend(context) { @@ -171,6 +172,7 @@ module.exports = { uploadHooksDirectory, getLocationSupportedRegion, getLocationRegionMapping, + getTransformerVersion, transformResourceWithOverrides, rootStackFileName, }; diff --git a/packages/amplify-provider-awscloudformation/src/iterative-deployment/deployment-manager.ts b/packages/amplify-provider-awscloudformation/src/iterative-deployment/deployment-manager.ts index 32507727607..04d2b94df8d 100644 --- a/packages/amplify-provider-awscloudformation/src/iterative-deployment/deployment-manager.ts +++ b/packages/amplify-provider-awscloudformation/src/iterative-deployment/deployment-manager.ts @@ -327,9 +327,15 @@ export class DeploymentManager { try { const response = await this.ddbClient.describeTable({ TableName: tableName }).promise(); + if (response.Table?.TableStatus === 'DELETING') { + return false; + } const gsis = response.Table?.GlobalSecondaryIndexes; return gsis ? gsis.every(idx => idx.IndexStatus === 'ACTIVE') : true; } catch (err) { + if (err?.code === 'ResourceNotFoundException') { + return true; // in the case of an iterative update that recreates a table, non-existance means the table has been fully removed + } this.logger('getTableStatus', [{ tableName }])(err); throw err; } diff --git a/packages/amplify-provider-awscloudformation/src/pre-push-cfn-processor/cfn-pre-processor.ts b/packages/amplify-provider-awscloudformation/src/pre-push-cfn-processor/cfn-pre-processor.ts index 85c9290a317..e76ea2e3e10 100644 --- a/packages/amplify-provider-awscloudformation/src/pre-push-cfn-processor/cfn-pre-processor.ts +++ b/packages/amplify-provider-awscloudformation/src/pre-push-cfn-processor/cfn-pre-processor.ts @@ -134,7 +134,7 @@ async function validateCustomPolicies(data: CustomIAMPolicies, categoryName: str if (!validatePolicy(data)) { let errorMessage = `Invalid custom IAM policies in the ${resourceName} ${categoryName}.\n Edit /amplify/backend/function/${resourceName}/custom-policies.json to fix - Learn more about custom IAM policies for ${categoryName}: https://docs.amplify.aws/function/custom-policies\n`; + Learn more about custom IAM policies for ${categoryName}: https://docs.amplify.aws/cli/function/#access-existing-aws-resource-from-lambda-function\n`; validatePolicy.errors.forEach(error => (errorMessage += `${error.message}\n`)); throw new CustomPoliciesFormatError(errorMessage); } diff --git a/packages/amplify-provider-awscloudformation/src/push-resources.ts b/packages/amplify-provider-awscloudformation/src/push-resources.ts index 5b3498c2e88..e542c6f2013 100644 --- a/packages/amplify-provider-awscloudformation/src/push-resources.ts +++ b/packages/amplify-provider-awscloudformation/src/push-resources.ts @@ -48,6 +48,10 @@ import { preProcessCFNTemplate, writeCustomPoliciesToCFNTemplate } from './pre-p import { AUTH_TRIGGER_STACK, AUTH_TRIGGER_TEMPLATE } from './utils/upload-auth-trigger-template'; import { ensureValidFunctionModelDependencies } from './utils/remove-dependent-function'; import { legacyLayerMigration, postPushLambdaLayerCleanup, prePushLambdaLayerPrompt } from './lambdaLayerInvocations'; +import { + postDeploymentCleanup, + prependDeploymentStepsToDisconnectFunctionsFromReplacedModelTables, +} from './disconnect-dependent-resources'; import { storeRootStackTemplate } from './initializer'; import { transformRootStack } from './override-manager'; @@ -71,7 +75,7 @@ const deploymentInProgressErrorMessage = (context: $TSContext) => { context.print.error('"amplify push --force" to re-deploy'); }; -export async function run(context: $TSContext, resourceDefinition: $TSObject) { +export async function run(context: $TSContext, resourceDefinition: $TSObject, rebuild: boolean = false) { const deploymentStateManager = await DeploymentStateManager.createDeploymentStateManager(context); let iterativeDeploymentWasInvoked = false; let layerResources = []; @@ -91,7 +95,7 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { parameters: { options }, } = context; - let resources = !!context?.exeInfo?.forcePush ? allResources : resourcesToBeCreated.concat(resourcesToBeUpdated); + let resources = !!context?.exeInfo?.forcePush || rebuild ? allResources : resourcesToBeCreated.concat(resourcesToBeUpdated); layerResources = resources.filter(r => r.service === FunctionServiceNameLambdaLayer); if (deploymentStateManager.isDeploymentInProgress() && !deploymentStateManager.isDeploymentFinished()) { @@ -120,7 +124,7 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { } validateCfnTemplates(context, resources); - for await (const resource of resources) { + for (const resource of resources) { if (resource.service === ApiServiceNameElasticContainer && resource.category === 'api') { const { exposedContainer, @@ -147,9 +151,7 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { } } - for await (const resource of resources.filter( - r => r.category === FunctionCategoryName && r.service === FunctionServiceNameLambdaLayer, - )) { + for (const resource of resources.filter(r => r.category === FunctionCategoryName && r.service === FunctionServiceNameLambdaLayer)) { await legacyLayerMigration(context, resource.resourceName); } @@ -159,6 +161,7 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { await transformGraphQLSchema(context, { handleMigration: opts => updateStackForAPIMigration(context, 'api', undefined, opts), minify: options['minify'], + promptApiKeyCreation: true, }); // If there is a deployment already in progress we have to fail the push operation as another @@ -175,11 +178,19 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { // Check if iterative updates are enabled or not and generate the required deployment steps if needed. if (FeatureFlags.getBoolean('graphQLTransformer.enableIterativeGSIUpdates')) { - const gqlResource = getGqlUpdatedResource(resourcesToBeUpdated); + const gqlResource = getGqlUpdatedResource(rebuild ? resources : resourcesToBeUpdated); if (gqlResource) { - const gqlManager = await GraphQLResourceManager.createInstance(context, gqlResource, cloudformationMeta.StackId); + const gqlManager = await GraphQLResourceManager.createInstance(context, gqlResource, cloudformationMeta.StackId, rebuild); deploymentSteps = await gqlManager.run(); + + // If any models are being replaced, we prepend steps to the iterative deployment to remove references to the replaced table in functions that have a dependeny on the tables + const modelsBeingReplaced = gqlManager.getTablesBeingReplaced().map(meta => meta.stackName); // stackName is the same as the model name + deploymentSteps = await prependDeploymentStepsToDisconnectFunctionsFromReplacedModelTables( + context, + modelsBeingReplaced, + deploymentSteps, + ); if (deploymentSteps.length > 1) { iterativeDeploymentWasInvoked = true; @@ -215,7 +226,8 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { resourcesToBeDeleted.length > 0 || tagsUpdated || rootStackUpdated || - context.exeInfo.forcePush + context.exeInfo.forcePush || + rebuild ) { // If there is an API change, there will be one deployment step. But when there needs an iterative update the step count is > 1 if (deploymentSteps.length > 1) { @@ -260,10 +272,11 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { context.print.error(`Could not delete state directory locally: ${err}`); } } + const s3 = await S3.getInstance(context); if (stateFolder.cloud) { - const s3 = await S3.getInstance(context); await s3.deleteDirectory(cloudformationMeta.DeploymentBucketName, stateFolder.cloud); } + postDeploymentCleanup(s3, cloudformationMeta.DeploymentBucketName); } else { // Non iterative update spinner.start(); @@ -406,8 +419,9 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { if (iterativeDeploymentWasInvoked) { await deploymentStateManager.failDeployment(); } - spinner.fail('An error occurred when pushing the resources to the cloud'); - + if (!(await canAutoResolveGraphQLAuthError(error.message))) { + spinner.fail('An error occurred when pushing the resources to the cloud'); + } rollbackLambdaLayers(layerResources); logger('run', [resourceDefinition])(error); @@ -416,6 +430,19 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { } } +async function canAutoResolveGraphQLAuthError(message: string) { + if ( + message === `@auth directive with 'iam' provider found, but the project has no IAM authentication provider configured.` || + message === + `@auth directive with 'userPools' provider found, but the project has no Cognito User Pools authentication provider configured.` || + message === `@auth directive with 'oidc' provider found, but the project has no OPENID_CONNECT authentication provider configured.` || + message === `@auth directive with 'apiKey' provider found, but the project has no API Key authentication provider configured.` || + message === `@auth directive with 'function' provider found, but the project has no Lambda authentication provider configured.` + ) { + return true; + } +} + export async function updateStackForAPIMigration(context: $TSContext, category: string, resourceName: string, options: $TSAny) { const { resourcesToBeCreated, resourcesToBeUpdated, resourcesToBeDeleted, allResources } = await context.amplify.getResourceStatus( category, @@ -870,7 +897,7 @@ async function formNestedStack( rootStack.Description = 'Root Stack for AWS Amplify Console'; } } catch (err) { - console.info('App not deployed yet.'); + // if it is not an AmplifyAdmin app, do nothing } const { amplifyMeta } = projectDetails; diff --git a/packages/amplify-provider-awscloudformation/src/system-config-manager.ts b/packages/amplify-provider-awscloudformation/src/system-config-manager.ts index 870731bc9d6..833cb996103 100644 --- a/packages/amplify-provider-awscloudformation/src/system-config-manager.ts +++ b/packages/amplify-provider-awscloudformation/src/system-config-manager.ts @@ -297,10 +297,10 @@ export function getProfileCredentials(profileName: string) { function validateCredentials(credentials: $TSAny, profileName: string) { const missingKeys = []; - if (!credentials?.accessKeyId) { + if (!credentials?.accessKeyId && !process.env.AWS_ACCESS_KEY_ID) { missingKeys.push('aws_access_key_id'); } - if (!credentials?.secretAccessKey) { + if (!credentials?.secretAccessKey && !process.env.AWS_SECRET_ACCESS_KEY) { missingKeys.push('aws_secret_access_key'); } if (missingKeys.length > 0) { diff --git a/packages/amplify-provider-awscloudformation/src/transform-graphql-schema.ts b/packages/amplify-provider-awscloudformation/src/transform-graphql-schema.ts index a29a482b8fc..c506f7937a9 100644 --- a/packages/amplify-provider-awscloudformation/src/transform-graphql-schema.ts +++ b/packages/amplify-provider-awscloudformation/src/transform-graphql-schema.ts @@ -13,10 +13,10 @@ import { FunctionTransformer } from 'graphql-function-transformer'; import { HttpTransformer } from 'graphql-http-transformer'; import { PredictionsTransformer } from 'graphql-predictions-transformer'; import { KeyTransformer } from 'graphql-key-transformer'; -import { ProviderName as providerName } from './constants'; +import { destructiveUpdatesFlag, ProviderName as providerName } from './constants'; import { AmplifyCLIFeatureFlagAdapter } from './utils/amplify-cli-feature-flag-adapter'; import { isAmplifyAdminApp } from './utils/admin-helpers'; -import { JSONUtilities, stateManager } from 'amplify-cli-core'; +import { JSONUtilities, pathManager, stateManager } from 'amplify-cli-core'; import { ResourceConstants } from 'graphql-transformer-common'; import { printer } from 'amplify-prompts'; import _ from 'lodash'; @@ -173,7 +173,7 @@ function getTransformerFactory(context, resourceDir, authConfig?) { const res = await isAmplifyAdminApp(appId); amplifyAdminEnabled = res.isAdminApp; } catch (err) { - console.info('App not deployed yet.'); + // if it is not an AmplifyAdmin app, do nothing } transformerList.push(new ModelAuthTransformer({ authConfig, addAwsIamAuthInOutputSchema: amplifyAdminEnabled })); @@ -314,8 +314,8 @@ async function migrateProject(context, options) { } export async function transformGraphQLSchema(context, options) { - const useExperimentalPipelineTransformer = FeatureFlags.getBoolean('graphQLTransformer.useExperimentalPipelinedTransformer'); - if (useExperimentalPipelineTransformer) { + const transformerVersion = getTransformerVersion(context); + if (transformerVersion === 2) { return transformGraphQLSchemaV6(context, options); } const backEndDir = context.amplify.pathManager.getBackendDirPath(); @@ -384,7 +384,7 @@ export async function transformGraphQLSchema(context, options) { if (!parameters && fs.existsSync(parametersFilePath)) { try { - parameters = context.amplify.readJsonFile(parametersFilePath); + parameters = JSONUtilities.readJson(parametersFilePath); } catch (e) { parameters = {}; } @@ -498,7 +498,8 @@ export async function transformGraphQLSchema(context, options) { } const ff = new AmplifyCLIFeatureFlagAdapter(); - const sanityCheckRulesList = getSanityCheckRules(isNewAppSyncAPI, ff); + const allowDestructiveUpdates = context?.input?.options?.[destructiveUpdatesFlag] || context?.input?.options?.force; + const sanityCheckRulesList = getSanityCheckRules(isNewAppSyncAPI, ff, allowDestructiveUpdates); const buildConfig = { ...options, @@ -512,8 +513,10 @@ export async function transformGraphQLSchema(context, options) { featureFlags: ff, sanityCheckRules: sanityCheckRulesList, }; + const transformerOutput = await buildAPIProject(buildConfig); - context.print.success(`\nGraphQL schema compiled successfully.\n\nEdit your schema at ${schemaFilePath} or \ + + context.print.success(`GraphQL schema compiled successfully.\n\nEdit your schema at ${schemaFilePath} or \ place .graphql files in a directory at ${schemaDirPath}`); if (!options.dryRun) { @@ -523,6 +526,17 @@ place .graphql files in a directory at ${schemaDirPath}`); return transformerOutput; } +async function addGraphQLAuthRequirement(context, authType) { + return await context.amplify.invokePluginMethod(context, 'api', undefined, 'addGraphQLAuthorizationMode', [ + context, + { + authType: authType, + printLeadText: true, + authSettings: undefined, + }, + ]); +} + function getProjectBucket(context) { const projectDetails = context.amplify.getProjectDetails(); const projectBucket = projectDetails.amplifyMeta.providers ? projectDetails.amplifyMeta.providers[providerName].DeploymentBucketName : ''; @@ -559,8 +573,8 @@ async function getPreviousDeploymentRootKey(previouslyDeployedBackendDir) { // } export async function getDirectiveDefinitions(context, resourceDir) { - const useExperimentalPipelineTransformer = FeatureFlags.getBoolean('graphQLTransformer.useExperimentalPipelinedTransformer'); - if (useExperimentalPipelineTransformer) { + const transformerVersion = getTransformerVersion(context); + if (transformerVersion === 2) { return getDirectiveDefinitionsV6(context, resourceDir); } @@ -608,3 +622,35 @@ function getBucketName(context, s3ResourceName, backEndDir) { : `${bucketParameters.bucketName}${s3ResourceName}-\${env}`; return { bucketName }; } + +export function getTransformerVersion(context) { + migrateToTransformerVersionFeatureFlag(context); + + const transformerVersion = FeatureFlags.getNumber('graphQLTransformer.transformerVersion'); + if (transformerVersion !== 1 && transformerVersion !== 2) { + throw new Error(`Invalid value specified for transformerVersion: '${transformerVersion}'`); + } + + return transformerVersion; +} + +function migrateToTransformerVersionFeatureFlag(context) { + const projectPath = pathManager.findProjectRoot() ?? process.cwd(); + + let config = stateManager.getCLIJSON(projectPath, undefined, { + throwIfNotExist: false, + preserveComments: true, + }); + + const useExperimentalPipelineTransformer = FeatureFlags.getBoolean('graphQLTransformer.useExperimentalPipelinedTransformer'); + const transformerVersion = FeatureFlags.getNumber('graphQLTransformer.transformerVersion'); + + if (useExperimentalPipelineTransformer && transformerVersion === 1) { + config.features.graphqltransformer.transformerversion = 2; + stateManager.setCLIJSON(projectPath, config); + + context.print.warning( + `\nThe project is configured with 'transformerVersion': ${transformerVersion}, but 'useExperimentalPipelinedTransformer': ${useExperimentalPipelineTransformer}. Setting the 'transformerVersion': ${config.features.graphqltransformer.transformerversion}. 'useExperimentalPipelinedTransformer' is deprecated.`, + ); + } +} diff --git a/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts b/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts index 0cd6143c3db..13c24707899 100644 --- a/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts +++ b/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts @@ -17,7 +17,7 @@ export function doAdminTokensExist(appId: string): boolean { return !!stateManager.getAmplifyAdminConfigEntry(appId); } -export async function isAmplifyAdminApp(appId: string): Promise<{ isAdminApp: boolean; region: string }> { +export async function isAmplifyAdminApp(appId: string): Promise<{ isAdminApp: boolean; region: string; userPoolID: string }> { if (!appId) { throw `Failed to check if Admin UI is enabled: appId is undefined`; } @@ -25,7 +25,8 @@ export async function isAmplifyAdminApp(appId: string): Promise<{ isAdminApp: bo if (appState.appId && appState.region && appState.region !== 'us-east-1') { appState = await getAdminAppState(appId, appState.region); } - return { isAdminApp: !!appState.appId, region: appState.region }; + const userPoolID = appState.loginAuthConfig ? JSON.parse(appState.loginAuthConfig).aws_user_pools_id : ''; + return { isAdminApp: !!appState.appId, region: appState.region, userPoolID }; } export async function getTempCredsWithAdminTokens(context: $TSContext, appId: string): Promise { diff --git a/packages/amplify-provider-awscloudformation/src/utils/api-key-helpers.ts b/packages/amplify-provider-awscloudformation/src/utils/api-key-helpers.ts new file mode 100644 index 00000000000..e9362dfb763 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/utils/api-key-helpers.ts @@ -0,0 +1,51 @@ +import { stateManager } from 'amplify-cli-core'; +import { ApiKeyConfig } from '@aws-amplify/graphql-transformer-interfaces'; + +export function getAppSyncApiConfig(): any { + const apiConfig = stateManager.getMeta()?.api; + let appSyncApi; + + Object.keys(apiConfig).forEach(k => { + if (apiConfig[k]['service'] === 'AppSync') appSyncApi = apiConfig[k]; + }); + + return appSyncApi; +} + +function getDefaultIfApiKey(): ApiKeyConfig { + const authConfig = getAppSyncApiConfig()?.output?.authConfig; + const { defaultAuthentication } = authConfig; + + if (defaultAuthentication.authenticationType === 'API_KEY') return defaultAuthentication.apiKeyConfig; +} + +function getAdditionalApiKeyConfig(): ApiKeyConfig { + const authConfig = getAppSyncApiConfig()?.output?.authConfig; + const { additionalAuthenticationProviders } = authConfig; + let apiKeyConfig; + + additionalAuthenticationProviders.forEach(authProvider => { + if (authProvider.authenticationType === 'API_KEY') apiKeyConfig = authProvider.apiKeyConfig; + }); + + return apiKeyConfig; +} + +export function getApiKeyConfig(): ApiKeyConfig { + const emptyConfig = {} as ApiKeyConfig; + return getDefaultIfApiKey() || getAdditionalApiKeyConfig() || emptyConfig; +} + +export function apiKeyIsActive(): boolean { + const today = new Date(); + const { apiKeyExpirationDate } = getApiKeyConfig() || {}; + + if (!apiKeyExpirationDate) return false; + + return new Date(apiKeyExpirationDate) > today; +} + +export function hasApiKey(): boolean { + const apiKeyConfig = getApiKeyConfig(); + return !!apiKeyConfig && !!apiKeyConfig?.apiKeyExpirationDays; +} diff --git a/packages/amplify-provider-awscloudformation/src/utils/sandbox-mode-helpers.ts b/packages/amplify-provider-awscloudformation/src/utils/sandbox-mode-helpers.ts new file mode 100644 index 00000000000..8aca7711d5f --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/utils/sandbox-mode-helpers.ts @@ -0,0 +1,43 @@ +import chalk from 'chalk'; +import { $TSContext } from 'amplify-cli-core'; +import { hasApiKey } from './api-key-helpers'; +import { printer } from 'amplify-prompts'; + +export async function showSandboxModePrompts(context: $TSContext): Promise { + if (hasApiKey()) return showGlobalSandboxModeWarning(); + else { + printer.info( + ` +⚠️ WARNING: Global Sandbox Mode has been enabled, which requires a valid API key. If +you'd like to disable, remove ${chalk.green('"type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key"')} +from your GraphQL schema and run 'amplify push' again. If you'd like to proceed with +sandbox mode disabled in '${context.amplify.getEnvInfo().envName}', do not create an API Key. +`, + 'yellow', + ); + return await context.amplify.invokePluginMethod(context, 'api', undefined, 'promptToAddApiKey', [context]); + } +} + +export function showGlobalSandboxModeWarning(): void { + printer.info( + ` +⚠️ 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-transformer/auth +`, + 'yellow', + ); +} + +export function getSandboxModeEnvNameFromDirectiveSet(input: any): string { + const sandboxModeDirective = input.find((el: any) => el.name.value === 'allow_public_data_access_with_api_key'); + + if (!sandboxModeDirective) return ''; + + const inField = sandboxModeDirective.arguments.find((el: any) => el.name.value === 'in'); + return inField.value.value; +} + +export function removeSandboxDirectiveFromSchema(schema): string { + const ampGlobalRegex = /(type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key\(in:)+(.*?)+(\))/g; + return schema.replace(ampGlobalRegex, ''); +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.invalid.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.invalid.version.json new file mode 100644 index 00000000000..fc23209de88 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.invalid.version.json @@ -0,0 +1,11 @@ +{ + "version": 9999, + "serviceConfiguration": { + "serviceName": "S3", + "permissions": { + "auth": ["READ"] + }, + "bucketName": "something", + "resourceName": "something" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.permissions.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.permissions.json new file mode 100644 index 00000000000..dcaf5b6e5c6 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.permissions.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "serviceConfiguration": { + "serviceName": "S3", + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.serviceConfiguration.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.serviceConfiguration.json new file mode 100644 index 00000000000..61a2092b1b7 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.serviceConfiguration.json @@ -0,0 +1,3 @@ +{ + "version": 1 +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.serviceName.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.serviceName.json new file mode 100644 index 00000000000..eb4536ce534 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.serviceName.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "serviceConfiguration": { + "permissions": { + "auth": ["READ", "CREATE_AND_UPDATE"], + "guest": ["READ"] + }, + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.version.json new file mode 100644 index 00000000000..bc741c2bd68 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.missing.version.json @@ -0,0 +1,10 @@ +{ + "serviceConfiguration": { + "serviceName": "S3", + "permissions": { + "auth": ["READ"] + }, + "bucketName": "something", + "resourceName": "something" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.string.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.string.version.json new file mode 100644 index 00000000000..e108642e5f8 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/invalidRequest.string.version.json @@ -0,0 +1,11 @@ +{ + "version": "1", + "serviceConfiguration": { + "serviceName": "S3", + "permissions": { + "auth": ["READ"] + }, + "bucketName": "something", + "resourceName": "something" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/validAddStorageRequest.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/validAddStorageRequest.json new file mode 100644 index 00000000000..530fcc9ccf6 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/add/validAddStorageRequest.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "serviceConfiguration": { + "serviceName": "S3", + "permissions": { + "auth": ["READ", "CREATE_AND_UPDATE"], + "guest": ["READ"], + "groups": { + "myUserGroup": ["CREATE_AND_UPDATE"] + } + }, + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.bucketName.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.bucketName.json new file mode 100644 index 00000000000..e8cf28ea321 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.bucketName.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "serviceConfiguration": { + "serviceName": "S3" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.serviceConfiguration.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.serviceConfiguration.json new file mode 100644 index 00000000000..61a2092b1b7 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.serviceConfiguration.json @@ -0,0 +1,3 @@ +{ + "version": 1 +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.serviceName.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.serviceName.json new file mode 100644 index 00000000000..95a40c17da0 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.missing.serviceName.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "serviceConfiguration": { + "bucketName": "import-this-bucket" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.invalid.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.invalid.json new file mode 100644 index 00000000000..94bc97e3dab --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.invalid.json @@ -0,0 +1,3 @@ +{ + "version": 9999 +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.missing.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.missing.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.missing.json @@ -0,0 +1 @@ +{} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.string.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.string.json new file mode 100644 index 00000000000..16fb64037a3 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/invalidRequest.version.string.json @@ -0,0 +1,3 @@ +{ + "version": "1" +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/valid.importStorageRequest.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/valid.importStorageRequest.json new file mode 100644 index 00000000000..3c983ec5b9c --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/import/valid.importStorageRequest.json @@ -0,0 +1,7 @@ +{ + "version": 1, + "serviceConfiguration": { + "serviceName": "S3", + "bucketName": "import-this-bucket" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.invalid.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.invalid.version.json new file mode 100644 index 00000000000..750679c9bba --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.invalid.version.json @@ -0,0 +1,7 @@ +{ + "version": 9999, + "serviceConfiguration": { + "serviceName": "S3", + "resourceName": "something" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/validAddStorageRequest.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.resourceName.json similarity index 67% rename from packages/amplify-util-headless-input/src/__tests__/assets/validAddStorageRequest.json rename to packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.resourceName.json index 2962d16b5bb..489ab0142cc 100644 --- a/packages/amplify-util-headless-input/src/__tests__/assets/validAddStorageRequest.json +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.resourceName.json @@ -1,12 +1,12 @@ { "version": 1, "serviceConfiguration": { - "serviceName": "s3", + "serviceName": "S3", "permissions": { - "auth": ["READ", "CREATE", "UPDATE"], + "auth": ["READ", "CREATE_AND_UPDATE"], "guest": ["READ"], "groups": { - "myUserGroup": ["CREATE", "UPDATE"] + "myUserGroup": ["CREATE_AND_UPDATE"] } }, "bucketName": "something", diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.serviceConfiguration.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.serviceConfiguration.json new file mode 100644 index 00000000000..61a2092b1b7 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.serviceConfiguration.json @@ -0,0 +1,3 @@ +{ + "version": 1 +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.serviceName.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.serviceName.json new file mode 100644 index 00000000000..780cd882a1f --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.serviceName.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "serviceConfiguration": { + "permissions": { + "auth": ["READ", "CREATE_AND_UPDATE"], + "guest": ["READ"], + "groups": { + "myUserGroup": ["CREATE_AND_UPDATE"] + } + }, + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.version.json new file mode 100644 index 00000000000..cb11d56082b --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.missing.version.json @@ -0,0 +1,18 @@ +{ + "serviceConfiguration": { + "serviceName": "S3", + "permissions": { + "auth": ["READ", "CREATE_AND_UPDATE"], + "guest": ["READ"], + "groups": { + "myUserGroup": ["CREATE_AND_UPDATE"] + } + }, + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.string.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.string.version.json new file mode 100644 index 00000000000..b117c8c713b --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/invalidRequest.string.version.json @@ -0,0 +1,7 @@ +{ + "version": "1", + "serviceConfiguration": { + "serviceName": "S3", + "resourceName": "something" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/validRemoveStorageRequest.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/validRemoveStorageRequest.json new file mode 100644 index 00000000000..1bfaf85a287 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/remove/validRemoveStorageRequest.json @@ -0,0 +1,7 @@ +{ + "version": 1, + "serviceConfiguration": { + "serviceName": "S3", + "resourceName": "something" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.invalid.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.invalid.version.json new file mode 100644 index 00000000000..f888f8b0e0a --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.invalid.version.json @@ -0,0 +1,10 @@ +{ + "version": 9999, + "serviceModification": { + "serviceName": "S3", + "permissions": { + "auth": ["READ"] + }, + "resourceName": "something" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.permissions.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.permissions.json new file mode 100644 index 00000000000..4f89c61b6ce --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.permissions.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "serviceModification": { + "serviceName": "S3", + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.resourceName.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.resourceName.json new file mode 100644 index 00000000000..0d30af0dfe6 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.resourceName.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "serviceModification": { + "serviceName": "S3", + "permissions": { + "auth": ["READ", "CREATE_AND_UPDATE"], + "guest": ["READ"], + "groups": { + "myUserGroup": ["CREATE_AND_UPDATE"] + } + }, + "bucketName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.serviceModification.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.serviceModification.json new file mode 100644 index 00000000000..61a2092b1b7 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.serviceModification.json @@ -0,0 +1,3 @@ +{ + "version": 1 +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.serviceName.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.serviceName.json new file mode 100644 index 00000000000..d07ff39addb --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.serviceName.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "serviceModification": { + "permissions": { + "auth": ["READ", "CREATE_AND_UPDATE"], + "guest": ["READ"], + "groups": { + "myUserGroup": ["CREATE_AND_UPDATE"] + } + }, + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.version.json new file mode 100644 index 00000000000..dfc4732ba2e --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.missing.version.json @@ -0,0 +1,18 @@ +{ + "serviceModification": { + "serviceName": "S3", + "permissions": { + "auth": ["READ", "CREATE_AND_UPDATE"], + "guest": ["READ"], + "groups": { + "myUserGroup": ["CREATE_AND_UPDATE"] + } + }, + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "new", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.string.version.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.string.version.json new file mode 100644 index 00000000000..5919078d574 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/invalidRequest.string.version.json @@ -0,0 +1,10 @@ +{ + "version": "1", + "serviceModification": { + "serviceName": "S3", + "permissions": { + "auth": ["READ"] + }, + "resourceName": "something" + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/validUpdateStorageRequest.json b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/validUpdateStorageRequest.json new file mode 100644 index 00000000000..c04e4ab11cf --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/assets/storage/update/validUpdateStorageRequest.json @@ -0,0 +1,21 @@ +{ + "version": 1, + "serviceModification": { + "serviceName": "S3", + "permissions": { + "auth": ["READ", "CREATE_AND_UPDATE"], + "guest": ["READ"], + "groups": { + "myUserGroup1": ["CREATE_AND_UPDATE", "READ", "DELETE"], + "myUserGroup2": ["CREATE_AND_UPDATE", "READ"], + "myUserGroup3": ["CREATE_AND_UPDATE"] + } + }, + "bucketName": "something", + "resourceName": "something", + "lambdaTrigger": { + "mode": "existing", + "name": "triggerName" + } + } +} diff --git a/packages/amplify-util-headless-input/src/__tests__/auth/import/__snapshots__/index.test.ts.snap b/packages/amplify-util-headless-input/src/__tests__/auth/import/__snapshots__/index.test.ts.snap index efb6cb22edc..a527753f50f 100644 --- a/packages/amplify-util-headless-input/src/__tests__/auth/import/__snapshots__/index.test.ts.snap +++ b/packages/amplify-util-headless-input/src/__tests__/auth/import/__snapshots__/index.test.ts.snap @@ -2,7 +2,6 @@ exports[`validates import auth headless request with a resolved promise when a valid payload with identityPoolId is not supplied 1`] = ` Object { - "identityPoolId": "333", "nativeClientId": "222", "userPoolId": "123", "version": 1, diff --git a/packages/amplify-util-headless-input/src/__tests__/auth/import/index.test.ts b/packages/amplify-util-headless-input/src/__tests__/auth/import/index.test.ts index 2d55995a372..5b6a0e5b0be 100644 --- a/packages/amplify-util-headless-input/src/__tests__/auth/import/index.test.ts +++ b/packages/amplify-util-headless-input/src/__tests__/auth/import/index.test.ts @@ -8,8 +8,8 @@ describe('validates import auth headless request', () => { test.each([ ['identityPoolId is supplied', 'validRequest.json'], ['identityPoolId is not supplied', 'validRequest.identityPoolId.missing.json'], - ])('with a resolved promise when a valid payload with %s', async () => { - const rawRequest = fs.readFileSync(path.join(importAuthAssetsRoot, 'validRequest.json'), 'utf8'); + ])('with a resolved promise when a valid payload with %s', async (_, fileName: string) => { + const rawRequest = fs.readFileSync(path.join(importAuthAssetsRoot, fileName), 'utf8'); const result = await validateImportAuthRequest(rawRequest); expect(result).toMatchSnapshot(); }); diff --git a/packages/amplify-util-headless-input/src/__tests__/index.test.ts b/packages/amplify-util-headless-input/src/__tests__/index.test.ts deleted file mode 100644 index 28ac6814c26..00000000000 --- a/packages/amplify-util-headless-input/src/__tests__/index.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import fs from 'fs-extra'; -import path from 'path'; -import { validateAddStorageRequest } from '../index'; - -const assetRoot = path.resolve(path.join(__dirname, 'assets')); - -describe('validate add storage request', () => { - it('returns valid payload', async () => { - const rawRequest = fs.readFileSync(path.join(assetRoot, 'validAddStorageRequest.json'), 'utf8'); - const result = await validateAddStorageRequest(rawRequest); - expect(result).toMatchSnapshot(); - }); - - it('rejects promise when invalid payload', async () => { - const resultPromise = validateAddStorageRequest('garbage'); - expect(resultPromise).rejects.toBeTruthy(); - }); -}); diff --git a/packages/amplify-util-headless-input/src/__tests__/__snapshots__/index.test.ts.snap b/packages/amplify-util-headless-input/src/__tests__/storage/add/__snapshots__/index.test.ts.snap similarity index 81% rename from packages/amplify-util-headless-input/src/__tests__/__snapshots__/index.test.ts.snap rename to packages/amplify-util-headless-input/src/__tests__/storage/add/__snapshots__/index.test.ts.snap index df97c4fece2..7ad9c027538 100644 --- a/packages/amplify-util-headless-input/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/amplify-util-headless-input/src/__tests__/storage/add/__snapshots__/index.test.ts.snap @@ -11,20 +11,19 @@ Object { "permissions": Object { "auth": Array [ "READ", - "CREATE", - "UPDATE", + "CREATE_AND_UPDATE", ], "groups": Object { "myUserGroup": Array [ - "CREATE", - "UPDATE", + "CREATE_AND_UPDATE", ], }, "guest": Array [ "READ", ], }, - "serviceName": "s3", + "resourceName": "something", + "serviceName": "S3", }, "version": 1, } diff --git a/packages/amplify-util-headless-input/src/__tests__/storage/add/index.test.ts b/packages/amplify-util-headless-input/src/__tests__/storage/add/index.test.ts new file mode 100644 index 00000000000..1487f6455a5 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/storage/add/index.test.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { validateAddStorageRequest } from '../../../index'; + +const topLevelMissingProperty: ExpectedFunction = (missingProperty: string) => + `Data did not validate against the supplied schema. Underlying errors were [{\"keyword\":\"required\",\"dataPath\":\"\",\"schemaPath\":\"#/required\",\"params\":{\"missingProperty\":\"${missingProperty}\"},\"message\":\"should have required property '${missingProperty}'\"}]`; +const serviceConfigurationMissingProperty: ExpectedFunction = (missingProperty: string) => + `Data did not validate against the supplied schema. Underlying errors were [{\"keyword\":\"required\",\"dataPath\":\".serviceConfiguration\",\"schemaPath\":\"#/required\",\"params\":{\"missingProperty\":\"${missingProperty}\"},\"message\":\"should have required property '${missingProperty}'\"}]`; + +type ExpectedFunction = (missingProperty: string) => string; + +const addStorageAssetsPath = path.resolve(path.join(__dirname, '..', '..', 'assets', 'storage', 'add')); + +describe('validate add storage request', () => { + it('returns valid payload', async () => { + const rawRequest = fs.readFileSync(path.join(addStorageAssetsPath, 'validAddStorageRequest.json'), 'utf8'); + const result = await validateAddStorageRequest(rawRequest); + expect(result).toMatchSnapshot(); + }); +}); + +describe('rejects promise when invalid payload', () => { + it('rejects garbage', async () => { + const resultPromise = validateAddStorageRequest('garbage'); + await expect(resultPromise).rejects.toBeTruthy(); + }); + + test.each([ + [ + 'version', + 'invalidRequest.missing.version.json', + (missingProperty: string) => `data does not have a top level \"${missingProperty}\" field`, + ], + [ + 'version', + 'invalidRequest.string.version.json', + (missingProperty: string) => `data does not have a top level \"${missingProperty}\" field`, + ], + ['version', 'invalidRequest.invalid.version.json', (missingProperty: string) => `No schema found for ${missingProperty} 9999`], + ['serviceName', 'invalidRequest.missing.serviceName.json', serviceConfigurationMissingProperty], + ['serviceConfiguration', 'invalidRequest.missing.serviceConfiguration.json', topLevelMissingProperty], + ['permissions', 'invalidRequest.missing.permissions.json', serviceConfigurationMissingProperty], + ])( + 'with a rejected promise when the payload is missing %s', + async (missingProperty: string, fileName: string, expectedFn: ExpectedFunction) => { + const rawRequest = fs.readFileSync(path.join(addStorageAssetsPath, fileName), 'utf8'); + + await expect(validateAddStorageRequest(rawRequest)).rejects.toThrow(expectedFn(missingProperty)); + }, + ); +}); diff --git a/packages/amplify-util-headless-input/src/__tests__/storage/import/__snapshots__/index.test.ts.snap b/packages/amplify-util-headless-input/src/__tests__/storage/import/__snapshots__/index.test.ts.snap new file mode 100644 index 00000000000..8294c283e2d --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/storage/import/__snapshots__/index.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`validates import storage headless request valid payload passes validation 1`] = ` +Object { + "serviceConfiguration": Object { + "bucketName": "import-this-bucket", + "serviceName": "S3", + }, + "version": 1, +} +`; diff --git a/packages/amplify-util-headless-input/src/__tests__/storage/import/index.test.ts b/packages/amplify-util-headless-input/src/__tests__/storage/import/index.test.ts new file mode 100644 index 00000000000..109f3ae052e --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/storage/import/index.test.ts @@ -0,0 +1,49 @@ +import * as fs from 'fs-extra'; +import path from 'path'; +import { validateImportStorageRequest } from '../../..'; + +const importStorageAssetsRoot = path.resolve(path.join(__dirname, '../../assets/storage/import')); + +type ExpectedFunction = (missingProperty: string) => string; + +describe('validates import storage headless request', () => { + const topLevelMissingProperty: ExpectedFunction = (missingProperty: string) => + `Data did not validate against the supplied schema. Underlying errors were [{\"keyword\":\"required\",\"dataPath\":\"\",\"schemaPath\":\"#/required\",\"params\":{\"missingProperty\":\"${missingProperty}\"},\"message\":\"should have required property '${missingProperty}'\"}]`; + const serviceConfigurationMissingProperty: ExpectedFunction = (missingProperty: string) => + `Data did not validate against the supplied schema. Underlying errors were [{\"keyword\":\"required\",\"dataPath\":\".serviceConfiguration\",\"schemaPath\":\"#/definitions/ImportS3ServiceConfiguration/required\",\"params\":{\"missingProperty\":\"${missingProperty}\"},\"message\":\"should have required property '${missingProperty}'\"}]`; + + it('valid payload passes validation', async () => { + const rawRequest = fs.readFileSync(path.join(importStorageAssetsRoot, 'valid.importStorageRequest.json'), 'utf8'); + const result = await validateImportStorageRequest(rawRequest); + expect(result).toMatchSnapshot(); + }); + + it('non-json content rejects', async () => { + const resultPromise = validateImportStorageRequest('garbage'); + expect(resultPromise).rejects.toBeTruthy(); + }); + + test.each([ + [ + 'version', + 'invalidRequest.version.missing.json', + (missingProperty: string) => `data does not have a top level \"${missingProperty}\" field`, + ], + [ + 'version', + 'invalidRequest.version.string.json', + (missingProperty: string) => `data does not have a top level \"${missingProperty}\" field`, + ], + ['version', 'invalidRequest.version.invalid.json', (missingProperty: string) => `No schema found for ${missingProperty} 9999`], + ['serviceConfiguration', 'invalidRequest.missing.serviceConfiguration.json', topLevelMissingProperty], + ['serviceName', 'invalidRequest.missing.serviceName.json', serviceConfigurationMissingProperty], + ['bucketName', 'invalidRequest.missing.bucketName.json', serviceConfigurationMissingProperty], + ])( + 'with a rejected promise when the payload is missing %s', + async (missingProperty: string, fileName: string, expectedFn: ExpectedFunction) => { + const rawRequest = fs.readFileSync(path.join(importStorageAssetsRoot, fileName), 'utf8'); + + await expect(validateImportStorageRequest(rawRequest)).rejects.toThrow(expectedFn(missingProperty)); + }, + ); +}); diff --git a/packages/amplify-util-headless-input/src/__tests__/storage/remove/__snapshots__/index.test.ts.snap b/packages/amplify-util-headless-input/src/__tests__/storage/remove/__snapshots__/index.test.ts.snap new file mode 100644 index 00000000000..5be9bffaa49 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/storage/remove/__snapshots__/index.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`validate remove storage request returns valid payload 1`] = ` +Object { + "serviceConfiguration": Object { + "resourceName": "something", + "serviceName": "S3", + }, + "version": 1, +} +`; diff --git a/packages/amplify-util-headless-input/src/__tests__/storage/remove/index.test.ts b/packages/amplify-util-headless-input/src/__tests__/storage/remove/index.test.ts new file mode 100644 index 00000000000..f2b3454fc8d --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/storage/remove/index.test.ts @@ -0,0 +1,50 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { validateRemoveStorageRequest } from '../../../index'; + +const topLevelMissingProperty: ExpectedFunction = (missingProperty: string) => + `Data did not validate against the supplied schema. Underlying errors were [{\"keyword\":\"required\",\"dataPath\":\"\",\"schemaPath\":\"#/required\",\"params\":{\"missingProperty\":\"${missingProperty}\"},\"message\":\"should have required property '${missingProperty}'\"}]`; +const serviceConfigurationMissingProperty: ExpectedFunction = (missingProperty: string) => + `Data did not validate against the supplied schema. Underlying errors were [{\"keyword\":\"required\",\"dataPath\":\".serviceConfiguration\",\"schemaPath\":\"#/definitions/RemoveS3ServiceConfiguration/required\",\"params\":{\"missingProperty\":\"${missingProperty}\"},\"message\":\"should have required property '${missingProperty}'\"}]`; +type ExpectedFunction = (missingProperty: string) => string; + +const removeStorageAssetsPath = path.resolve(path.join(__dirname, '..', '..', 'assets', 'storage', 'remove')); + +describe('validate remove storage request', () => { + it('returns valid payload', async () => { + const rawRequest = fs.readFileSync(path.join(removeStorageAssetsPath, 'validRemoveStorageRequest.json'), 'utf8'); + const result = await validateRemoveStorageRequest(rawRequest); + expect(result).toMatchSnapshot(); + }); +}); + +describe('rejects promise when invalid payload', () => { + it('rejects garbage', async () => { + const resultPromise = validateRemoveStorageRequest('garbage'); + await expect(resultPromise).rejects.toBeTruthy(); + }); + + test.each([ + [ + 'version', + 'invalidRequest.missing.version.json', + (missingProperty: string) => `data does not have a top level \"${missingProperty}\" field`, + ], + [ + 'version', + 'invalidRequest.string.version.json', + (missingProperty: string) => `data does not have a top level \"${missingProperty}\" field`, + ], + ['version', 'invalidRequest.invalid.version.json', (missingProperty: string) => `No schema found for ${missingProperty} 9999`], + ['serviceConfiguration', 'invalidRequest.missing.serviceConfiguration.json', topLevelMissingProperty], + ['serviceName', 'invalidRequest.missing.serviceName.json', serviceConfigurationMissingProperty], + ['resourceName', 'invalidRequest.missing.resourceName.json', serviceConfigurationMissingProperty], + ])( + 'with a rejected promise when the payload is missing %s', + async (missingProperty: string, fileName: string, expectedFn: ExpectedFunction) => { + const rawRequest = fs.readFileSync(path.join(removeStorageAssetsPath, fileName), 'utf8'); + + await expect(validateRemoveStorageRequest(rawRequest)).rejects.toThrow(expectedFn(missingProperty)); + }, + ); +}); diff --git a/packages/amplify-util-headless-input/src/__tests__/storage/update/__snapshots__/index.test.ts.snap b/packages/amplify-util-headless-input/src/__tests__/storage/update/__snapshots__/index.test.ts.snap new file mode 100644 index 00000000000..1f07285751b --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/storage/update/__snapshots__/index.test.ts.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`validate update storage request returns valid payload 1`] = ` +Object { + "serviceModification": Object { + "bucketName": "something", + "lambdaTrigger": Object { + "mode": "existing", + "name": "triggerName", + }, + "permissions": Object { + "auth": Array [ + "READ", + "CREATE_AND_UPDATE", + ], + "groups": Object { + "myUserGroup1": Array [ + "CREATE_AND_UPDATE", + "READ", + "DELETE", + ], + "myUserGroup2": Array [ + "CREATE_AND_UPDATE", + "READ", + ], + "myUserGroup3": Array [ + "CREATE_AND_UPDATE", + ], + }, + "guest": Array [ + "READ", + ], + }, + "resourceName": "something", + "serviceName": "S3", + }, + "version": 1, +} +`; diff --git a/packages/amplify-util-headless-input/src/__tests__/storage/update/index.test.ts b/packages/amplify-util-headless-input/src/__tests__/storage/update/index.test.ts new file mode 100644 index 00000000000..5f4e28dde48 --- /dev/null +++ b/packages/amplify-util-headless-input/src/__tests__/storage/update/index.test.ts @@ -0,0 +1,52 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { validateUpdateStorageRequest } from '../../../index'; + +const topLevelMissingProperty: ExpectedFunction = (missingProperty: string) => + `Data did not validate against the supplied schema. Underlying errors were [{\"keyword\":\"required\",\"dataPath\":\"\",\"schemaPath\":\"#/required\",\"params\":{\"missingProperty\":\"${missingProperty}\"},\"message\":\"should have required property '${missingProperty}'\"}]`; +const serviceModificationMissingProperty: ExpectedFunction = (missingProperty: string) => + `Data did not validate against the supplied schema. Underlying errors were [{\"keyword\":\"required\",\"dataPath\":\".serviceModification\",\"schemaPath\":\"#/required\",\"params\":{\"missingProperty\":\"${missingProperty}\"},\"message\":\"should have required property '${missingProperty}'\"}]`; + +type ExpectedFunction = (missingProperty: string) => string; + +const updateStorageAssetsPath = path.resolve(path.join(__dirname, '..', '..', 'assets', 'storage', 'update')); + +describe('validate update storage request', () => { + it('returns valid payload', async () => { + const rawRequest = fs.readFileSync(path.join(updateStorageAssetsPath, 'validUpdateStorageRequest.json'), 'utf8'); + const result = await validateUpdateStorageRequest(rawRequest); + expect(result).toMatchSnapshot(); + }); +}); + +describe('rejects promise when invalid payload', () => { + it('rejects garbage', async () => { + const resultPromise = validateUpdateStorageRequest('garbage'); + await expect(resultPromise).rejects.toBeTruthy(); + }); + + test.each([ + [ + 'version', + 'invalidRequest.missing.version.json', + (missingProperty: string) => `data does not have a top level \"${missingProperty}\" field`, + ], + [ + 'version', + 'invalidRequest.string.version.json', + (missingProperty: string) => `data does not have a top level \"${missingProperty}\" field`, + ], + ['version', 'invalidRequest.invalid.version.json', (missingProperty: string) => `No schema found for ${missingProperty} 9999`], + ['serviceModification', 'invalidRequest.missing.serviceModification.json', topLevelMissingProperty], + ['serviceName', 'invalidRequest.missing.serviceName.json', serviceModificationMissingProperty], + ['resourceName', 'invalidRequest.missing.resourceName.json', serviceModificationMissingProperty], + ['permissions', 'invalidRequest.missing.permissions.json', serviceModificationMissingProperty], + ])( + 'with a rejected promise when the payload is missing %s', + async (missingProperty: string, fileName: string, expectedFn: ExpectedFunction) => { + const rawRequest = fs.readFileSync(path.join(updateStorageAssetsPath, fileName), 'utf8'); + + await expect(validateUpdateStorageRequest(rawRequest)).rejects.toThrow(expectedFn(missingProperty)); + }, + ); +}); diff --git a/packages/amplify-util-headless-input/src/index.ts b/packages/amplify-util-headless-input/src/index.ts index 2f9ddebb369..dde62f525d8 100644 --- a/packages/amplify-util-headless-input/src/index.ts +++ b/packages/amplify-util-headless-input/src/index.ts @@ -1,19 +1,29 @@ +import { + AddApiRequest, + AddAuthRequest, + AddStorageRequest, + ImportAuthRequest, + ImportStorageRequest, + RemoveStorageRequest, + UpdateApiRequest, + UpdateAuthRequest, + UpdateStorageRequest, +} from 'amplify-headless-interface'; import { HeadlessInputValidator } from './HeadlessInputValidator'; import { - addStorageRequestSchemaSupplier, addApiRequestSchemaSupplier, addAuthRequestSchemaSupplier, + addStorageRequestSchemaSupplier, + importAuthRequestSchemaSupplier, + importStorageRequestSchemaSupplier, + removeStorageRequestSchemaSupplier, updateApiRequestSchemaSupplier, updateAuthRequestSchemaSupplier, - importAuthRequestSchemaSupplier, + updateStorageRequestSchemaSupplier, } from './schemaSuppliers'; import { noopUpgradePipeline } from './upgradePipelines'; -import { AddStorageRequest, AddApiRequest, AddAuthRequest, UpdateAuthRequest, ImportAuthRequest, UpdateApiRequest } from 'amplify-headless-interface'; - -export const validateAddStorageRequest = (raw: string) => { - return new HeadlessInputValidator(addStorageRequestSchemaSupplier, noopUpgradePipeline).validate(raw); -}; +/* API */ export const validateAddApiRequest = (raw: string) => { return new HeadlessInputValidator(addApiRequestSchemaSupplier, noopUpgradePipeline).validate(raw); }; @@ -22,6 +32,7 @@ export const validateUpdateApiRequest = (raw: string) => { return new HeadlessInputValidator(updateApiRequestSchemaSupplier, noopUpgradePipeline).validate(raw); }; +/* Auth */ export const validateAddAuthRequest = (raw: string) => { return new HeadlessInputValidator(addAuthRequestSchemaSupplier, noopUpgradePipeline).validate(raw); }; @@ -33,3 +44,20 @@ export const validateUpdateAuthRequest = (raw: string) => { export const validateImportAuthRequest = (raw: string) => { return new HeadlessInputValidator(importAuthRequestSchemaSupplier, noopUpgradePipeline).validate(raw); }; + +/* Storage */ +export const validateAddStorageRequest = (raw: string) => { + return new HeadlessInputValidator(addStorageRequestSchemaSupplier, noopUpgradePipeline).validate(raw); +}; + +export const validateImportStorageRequest = (raw: string) => { + return new HeadlessInputValidator(importStorageRequestSchemaSupplier, noopUpgradePipeline).validate(raw); +}; + +export const validateRemoveStorageRequest = (raw: string) => { + return new HeadlessInputValidator(removeStorageRequestSchemaSupplier, noopUpgradePipeline).validate(raw); +}; + +export const validateUpdateStorageRequest = (raw: string) => { + return new HeadlessInputValidator(updateStorageRequestSchemaSupplier, noopUpgradePipeline).validate(raw); +}; diff --git a/packages/amplify-util-headless-input/src/schemaSuppliers.ts b/packages/amplify-util-headless-input/src/schemaSuppliers.ts index 83e0e0ae2ea..c615897c760 100644 --- a/packages/amplify-util-headless-input/src/schemaSuppliers.ts +++ b/packages/amplify-util-headless-input/src/schemaSuppliers.ts @@ -4,6 +4,18 @@ export const addStorageRequestSchemaSupplier: VersionedSchemaSupplier = version return getSchema('AddStorageRequest', 'storage', version); }; +export const updateStorageRequestSchemaSupplier: VersionedSchemaSupplier = version => { + return getSchema('UpdateStorageRequest', 'storage', version); +}; + +export const importStorageRequestSchemaSupplier: VersionedSchemaSupplier = version => { + return getSchema('ImportStorageRequest', 'storage', version); +}; + +export const removeStorageRequestSchemaSupplier: VersionedSchemaSupplier = version => { + return getSchema('RemoveStorageRequest', 'storage', version); +}; + export const addAuthRequestSchemaSupplier: VersionedSchemaSupplier = version => { return getSchema('AddAuthRequest', 'auth', version); }; diff --git a/packages/graphql-auth-transformer/src/ModelAuthTransformer.ts b/packages/graphql-auth-transformer/src/ModelAuthTransformer.ts index cf2fab44ac3..6e41670abd6 100644 --- a/packages/graphql-auth-transformer/src/ModelAuthTransformer.ts +++ b/packages/graphql-auth-transformer/src/ModelAuthTransformer.ts @@ -98,6 +98,7 @@ export type AppSyncAuthConfigurationEntry = { export type ApiKeyConfig = { description?: string; apiKeyExpirationDays: number; + apiKeyExpirationDate?: Date; }; export type UserPoolConfig = { userPoolId: string; @@ -1387,9 +1388,8 @@ operations will be generated by the CLI.`, // In create mutations, the dynamic group and ownership authorization checks // are done before calling PutItem. - const dynamicGroupAuthorizationExpression = this.resources.dynamicGroupAuthorizationExpressionForCreateOperations( - dynamicGroupAuthorizationRules, - ); + const dynamicGroupAuthorizationExpression = + this.resources.dynamicGroupAuthorizationExpressionForCreateOperations(dynamicGroupAuthorizationRules); const fieldIsList = (fieldName: string) => { const field = parent.fields.find(field => field.name.value === fieldName); if (field) { @@ -1547,9 +1547,8 @@ operations will be generated by the CLI.`, ]), ); - const throwIfNotStaticGroupAuthorizedOrAuthConditionIsEmpty = this.resources.throwIfNotStaticGroupAuthorizedOrAuthConditionIsEmpty( - field, - ); + const throwIfNotStaticGroupAuthorizedOrAuthConditionIsEmpty = + this.resources.throwIfNotStaticGroupAuthorizedOrAuthConditionIsEmpty(field); // If we've any modes to check, then add the authMode check code block // to the start of the resolver. diff --git a/packages/graphql-auth-transformer/src/__tests__/__snapshots__/SearchableAuthTransformer.test.ts.snap b/packages/graphql-auth-transformer/src/__tests__/__snapshots__/SearchableAuthTransformer.test.ts.snap index e7cb539695c..d96c4469eb8 100644 --- a/packages/graphql-auth-transformer/src/__tests__/__snapshots__/SearchableAuthTransformer.test.ts.snap +++ b/packages/graphql-auth-transformer/src/__tests__/__snapshots__/SearchableAuthTransformer.test.ts.snap @@ -15,7 +15,7 @@ enum ModelSortDirection { } type ModelPostConnection @aws_api_key @aws_iam { - items: [Post] + items: [Post!]! nextToken: String } @@ -198,7 +198,7 @@ input SearchablePostSortInput { } type SearchablePostConnection @aws_api_key @aws_iam { - items: [Post] + items: [Post!]! nextToken: String total: Int } diff --git a/packages/graphql-auth-transformer/src/resources.ts b/packages/graphql-auth-transformer/src/resources.ts index 6749af89196..f81640e94c2 100644 --- a/packages/graphql-auth-transformer/src/resources.ts +++ b/packages/graphql-auth-transformer/src/resources.ts @@ -109,9 +109,11 @@ export class ResourceFactory { expirationDays = apiKeyConfig.apiKeyExpirationDays; } // add delay expiration time is valid upon resource creation - let expirationDateInSeconds = 60 /* s */ * 60 /* m */ * 24 /* h */ * expirationDays; /* d */ - // Add a 2 minute time delay if set to 1 day: https://github.com/aws-amplify/amplify-cli/issues/4460 - if (expirationDays === 1) expirationDateInSeconds += 60 * 2; + let expirationDateInSeconds = 60 * 60 * 24 * expirationDays; // sec * min * hour * days + // Add a 30 minute time delay if set to 1 day: https://github.com/aws-amplify/amplify-cli/issues/4460 + // initially this was 2 minutes but with iterative deployments it is possible for deployments to take much longer than that + // if an iterative deployment takes longer than 30 mins, and API key expiration is set to 1 day, deployments may still fail + if (expirationDays === 1) expirationDateInSeconds += 60 * 30; const nowEpochTime = Math.floor(Date.now() / 1000); return new AppSync.ApiKey({ ApiId: Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'), diff --git a/packages/graphql-connection-transformer/src/__tests__/__snapshots__/ModelConnectionTransformer.test.ts.snap b/packages/graphql-connection-transformer/src/__tests__/__snapshots__/ModelConnectionTransformer.test.ts.snap index 9026dcc2d87..30d90141793 100644 --- a/packages/graphql-connection-transformer/src/__tests__/__snapshots__/ModelConnectionTransformer.test.ts.snap +++ b/packages/graphql-connection-transformer/src/__tests__/__snapshots__/ModelConnectionTransformer.test.ts.snap @@ -17,7 +17,7 @@ type Comment { } type ModelCommentConnection { - items: [Comment] + items: [Comment!]! nextToken: String } @@ -144,7 +144,7 @@ enum PRODUCT_TYPE { } type ModelCartItemConnection { - items: [CartItem] + items: [CartItem!]! nextToken: String } diff --git a/packages/graphql-connection-transformer/src/__tests__/__snapshots__/NewConnectionTransformer.test.ts.snap b/packages/graphql-connection-transformer/src/__tests__/__snapshots__/NewConnectionTransformer.test.ts.snap index 1b6888b99cd..ef07c688af7 100644 --- a/packages/graphql-connection-transformer/src/__tests__/__snapshots__/NewConnectionTransformer.test.ts.snap +++ b/packages/graphql-connection-transformer/src/__tests__/__snapshots__/NewConnectionTransformer.test.ts.snap @@ -42,7 +42,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String startedAt: AWSTimestamp } @@ -200,7 +200,7 @@ type Subscription { } type ModelPostEditorConnection { - items: [PostEditor] + items: [PostEditor!]! nextToken: String startedAt: AWSTimestamp } @@ -242,7 +242,7 @@ input ModelPostEditorConditionInput { } type ModelUserConnection { - items: [User] + items: [User!]! nextToken: String startedAt: AWSTimestamp } @@ -324,7 +324,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -499,7 +499,7 @@ input ModelPostEditorConditionInput { } type ModelUserConnection { - items: [User] + items: [User!]! nextToken: String } @@ -543,7 +543,7 @@ input ModelIDKeyConditionInput { } type ModelPostEditorConnection { - items: [PostEditor] + items: [PostEditor!]! nextToken: String } diff --git a/packages/graphql-dynamodb-transformer/src/__tests__/__snapshots__/DynamoDBModelTransformer.test.ts.snap b/packages/graphql-dynamodb-transformer/src/__tests__/__snapshots__/DynamoDBModelTransformer.test.ts.snap index cc9aa4d15d3..a0239a46f25 100644 --- a/packages/graphql-dynamodb-transformer/src/__tests__/__snapshots__/DynamoDBModelTransformer.test.ts.snap +++ b/packages/graphql-dynamodb-transformer/src/__tests__/__snapshots__/DynamoDBModelTransformer.test.ts.snap @@ -14,7 +14,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -224,7 +224,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -538,7 +538,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -855,7 +855,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -1164,7 +1164,7 @@ enum ModelSortDirection { } type ModelEntityConnection { - items: [Entity] + items: [Entity!]! nextToken: String } @@ -1283,7 +1283,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -1593,7 +1593,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -1703,7 +1703,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } diff --git a/packages/graphql-dynamodb-transformer/src/definitions.ts b/packages/graphql-dynamodb-transformer/src/definitions.ts index dbda601677a..253892dcd36 100644 --- a/packages/graphql-dynamodb-transformer/src/definitions.ts +++ b/packages/graphql-dynamodb-transformer/src/definitions.ts @@ -32,6 +32,7 @@ import { makeValueNode, withNamedNodeNamed, isListType, + makeNonNullType, } from 'graphql-transformer-common'; import { TransformerContext } from 'graphql-transformer-core'; import { getCreatedAtFieldName, getUpdatedAtFieldName } from './ModelDirectiveArgs'; @@ -737,7 +738,9 @@ export function makeAttributeTypeEnum(): EnumTypeDefinitionNode { export function makeModelConnectionType(typeName: string, isSync: Boolean = false): ObjectTypeExtensionNode { const connectionName = ModelResourceIDs.ModelConnectionTypeName(typeName); let connectionTypeExtension = blankObjectExtension(connectionName); - connectionTypeExtension = extensionWithFields(connectionTypeExtension, [makeField('items', [], makeListType(makeNamedType(typeName)))]); + connectionTypeExtension = extensionWithFields(connectionTypeExtension, [ + makeField('items', [], makeNonNullType(makeListType(makeNonNullType(makeNamedType(typeName))))), + ]); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [makeField('nextToken', [], makeNamedType('String'))]); if (isSync) { connectionTypeExtension = extensionWithFields(connectionTypeExtension, [makeField('startedAt', [], makeNamedType('AWSTimestamp'))]); diff --git a/packages/graphql-elasticsearch-transformer/src/SearchableModelTransformer.ts b/packages/graphql-elasticsearch-transformer/src/SearchableModelTransformer.ts index 48746de2b74..c31d67b813f 100644 --- a/packages/graphql-elasticsearch-transformer/src/SearchableModelTransformer.ts +++ b/packages/graphql-elasticsearch-transformer/src/SearchableModelTransformer.ts @@ -17,6 +17,7 @@ import { makeListType, makeInputValueDefinition, STANDARD_SCALARS, + makeNonNullType, } from 'graphql-transformer-common'; import { Expression, str } from 'graphql-mapping-template'; import { ResolverResourceIDs, SearchableResourceIDs, ModelResourceIDs, getBaseType, ResourceConstants } from 'graphql-transformer-common'; @@ -162,7 +163,7 @@ export class SearchableModelTransformer extends Transformer { // Create TableXConnection type with items and nextToken let connectionTypeExtension = blankObjectExtension(searchableXConnectionName); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [ - makeField('items', [], makeListType(makeNamedType(def.name.value))), + makeField('items', [], makeNonNullType(makeListType(makeNonNullType(makeNamedType(def.name.value))))), ]); connectionTypeExtension = extensionWithFields(connectionTypeExtension, [ makeField('nextToken', [], makeNamedType('String')), diff --git a/packages/graphql-elasticsearch-transformer/src/__tests__/__snapshots__/SearchableModelTransformer.test.ts.snap b/packages/graphql-elasticsearch-transformer/src/__tests__/__snapshots__/SearchableModelTransformer.test.ts.snap index b14fb844581..17fac764014 100644 --- a/packages/graphql-elasticsearch-transformer/src/__tests__/__snapshots__/SearchableModelTransformer.test.ts.snap +++ b/packages/graphql-elasticsearch-transformer/src/__tests__/__snapshots__/SearchableModelTransformer.test.ts.snap @@ -69,7 +69,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -257,7 +257,7 @@ input SearchablePostSortInput { } type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int } @@ -285,7 +285,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -396,7 +396,7 @@ type Subscription { } type ModelUserConnection { - items: [User] + items: [User!]! nextToken: String } @@ -509,7 +509,7 @@ input SearchablePostSortInput { } type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int } @@ -533,7 +533,7 @@ input SearchableUserSortInput { } type SearchableUserConnection { - items: [User] + items: [User!]! nextToken: String total: Int } @@ -554,7 +554,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -727,7 +727,7 @@ input SearchablePostSortInput { } type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int } @@ -748,7 +748,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -936,7 +936,7 @@ input SearchablePostSortInput { } type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int } @@ -957,7 +957,7 @@ enum ModelSortDirection { } type ModelPostConnection { - items: [Post] + items: [Post!]! nextToken: String } @@ -1145,7 +1145,7 @@ input SearchablePostSortInput { } type SearchablePostConnection { - items: [Post] + items: [Post!]! nextToken: String total: Int } diff --git a/packages/graphql-mapping-template/src/print.ts b/packages/graphql-mapping-template/src/print.ts index bd5a90f853c..b871264e156 100644 --- a/packages/graphql-mapping-template/src/print.ts +++ b/packages/graphql-mapping-template/src/print.ts @@ -115,7 +115,7 @@ function printReference(node: ReferenceNode): string { } function printQuietReference(node: QuietReferenceNode, indent: string = ''): string { - const val = typeof node.value === 'string' ? node.value : printExpr(node.value) + const val = typeof node.value === 'string' ? node.value : printExpr(node.value); return `${indent}$util.qr(${val})`; } diff --git a/packages/graphql-transformer-common/src/ModelResourceIDs.ts b/packages/graphql-transformer-common/src/ModelResourceIDs.ts index d8491308ef7..361fa250ba2 100644 --- a/packages/graphql-transformer-common/src/ModelResourceIDs.ts +++ b/packages/graphql-transformer-common/src/ModelResourceIDs.ts @@ -75,6 +75,12 @@ export class ModelResourceIDs { static ModelConnectionTypeName(typeName: string): string { return `Model${typeName}Connection`; } + static IsModelConnectionType(typeName: string): boolean { + return /^Model.*Connection$/.test(typeName); + } + static GetModelFromConnectionType(typeName: string): string { + return /(?<=Model)(.*)(?=Connection)/.exec(typeName)?.[0]; + } static ModelDeleteInputObjectName(typeName: string): string { return graphqlName('Delete' + toUpper(typeName) + 'Input'); } diff --git a/packages/graphql-transformer-common/src/definition.ts b/packages/graphql-transformer-common/src/definition.ts index e8b1e107e2b..942ceeb9beb 100644 --- a/packages/graphql-transformer-common/src/definition.ts +++ b/packages/graphql-transformer-common/src/definition.ts @@ -396,7 +396,7 @@ export function makeNonNullType(type: NamedTypeNode | ListTypeNode): NonNullType }; } -export function makeListType(type: TypeNode): TypeNode { +export function makeListType(type: TypeNode): ListTypeNode { return { kind: 'ListType', type, diff --git a/packages/graphql-transformer-core/src/__tests__/util/__snapshots__/amplifyUtils.test.ts.snap b/packages/graphql-transformer-core/src/__tests__/util/__snapshots__/amplifyUtils.test.ts.snap index e88c9b1aabf..3ffd983d87e 100644 --- a/packages/graphql-transformer-core/src/__tests__/util/__snapshots__/amplifyUtils.test.ts.snap +++ b/packages/graphql-transformer-core/src/__tests__/util/__snapshots__/amplifyUtils.test.ts.snap @@ -1,11 +1,38 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`get sanity check rules sanity check rule list when destructive changes flag is present and ff enabled 1`] = `Array []`; + +exports[`get sanity check rules sanity check rule list when destructive changes flag is present and ff enabled 2`] = ` +Array [ + "cantHaveMoreThan500ResourcesRule", +] +`; + +exports[`get sanity check rules sanity check rule list when destructive changes flag is present but ff not enabled 1`] = ` +Array [ + "cantEditKeySchemaRule", + "cantAddLSILaterRule", + "cantRemoveLSILater", + "cantEditLSIKeySchemaRule", + "cantEditGSIKeySchemaRule", + "cantAddAndRemoveGSIAtSameTimeRule", +] +`; + +exports[`get sanity check rules sanity check rule list when destructive changes flag is present but ff not enabled 2`] = ` +Array [ + "cantHaveMoreThan500ResourcesRule", + "cantMutateMultipleGSIAtUpdateTimeRule", +] +`; + exports[`get sanity check rules sanitycheck rule list when api is in update status and ff enabled 1`] = ` Array [ "cantEditKeySchemaRule", "cantAddLSILaterRule", "cantRemoveLSILater", "cantEditLSIKeySchemaRule", + "cantRemoveTableAfterCreation", ] `; @@ -23,6 +50,7 @@ Array [ "cantEditLSIKeySchemaRule", "cantEditGSIKeySchemaRule", "cantAddAndRemoveGSIAtSameTimeRule", + "cantRemoveTableAfterCreation", ] `; diff --git a/packages/graphql-transformer-core/src/__tests__/util/amplifyUtils.test.ts b/packages/graphql-transformer-core/src/__tests__/util/amplifyUtils.test.ts index 7d4034acbc3..40f681ea908 100644 --- a/packages/graphql-transformer-core/src/__tests__/util/amplifyUtils.test.ts +++ b/packages/graphql-transformer-core/src/__tests__/util/amplifyUtils.test.ts @@ -32,4 +32,24 @@ describe('get sanity check rules', () => { expect(diffRulesFn).toMatchSnapshot(); expect(projectRulesFn).toMatchSnapshot(); }); + + test('sanity check rule list when destructive changes flag is present and ff enabled', () => { + const ff_mock = new AmplifyCLIFeatureFlagAdapter(); + (FeatureFlags.getBoolean).mockReturnValue(true); + const sanityCheckRules: SanityCheckRules = getSanityCheckRules(false, ff_mock, true); + const diffRulesFn = sanityCheckRules.diffRules.map(func => func.name); + const projectRulesFn = sanityCheckRules.projectRules.map(func => func.name); + expect(diffRulesFn).toMatchSnapshot(); + expect(projectRulesFn).toMatchSnapshot(); + }); + + test('sanity check rule list when destructive changes flag is present but ff not enabled', () => { + const ff_mock = new AmplifyCLIFeatureFlagAdapter(); + (FeatureFlags.getBoolean).mockReturnValue(false); + const sanityCheckRules: SanityCheckRules = getSanityCheckRules(false, ff_mock, true); + const diffRulesFn = sanityCheckRules.diffRules.map(func => func.name); + const projectRulesFn = sanityCheckRules.projectRules.map(func => func.name); + expect(diffRulesFn).toMatchSnapshot(); + expect(projectRulesFn).toMatchSnapshot(); + }); }); diff --git a/packages/graphql-transformer-core/src/collectDirectives.ts b/packages/graphql-transformer-core/src/collectDirectives.ts index ee8ae2a16a4..4cd2c103b38 100644 --- a/packages/graphql-transformer-core/src/collectDirectives.ts +++ b/packages/graphql-transformer-core/src/collectDirectives.ts @@ -62,6 +62,9 @@ export function collectDirectivesByTypeNames(sdl: string) { } export function collectDirectivesByType(sdl: string): Object { + if (!sdl) { + return {}; + } const doc = parse(sdl); // defined types with directives list let types = {}; diff --git a/packages/graphql-transformer-core/src/errors.ts b/packages/graphql-transformer-core/src/errors.ts index cd3b97d03e6..b50f4ae577b 100644 --- a/packages/graphql-transformer-core/src/errors.ts +++ b/packages/graphql-transformer-core/src/errors.ts @@ -1,4 +1,5 @@ import { GraphQLError } from 'graphql'; +import * as os from 'os'; export class InvalidTransformerError extends Error { constructor(message: string) { @@ -41,23 +42,37 @@ export class TransformerContractError extends Error { } } +export class DestructiveMigrationError extends Error { + constructor(message: string, private removedModels: string[], private replacedModels: string[]) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + this.name = 'DestructiveMigrationError'; + const prependSpace = (str: string) => ` ${str}`; + const removedModelsList = this.removedModels.map(prependSpace).toString().trim(); + const replacedModelsList = this.replacedModels.map(prependSpace).toString().trim(); + if (removedModelsList && replacedModelsList) { + this.message = `${this.message}${os.EOL}This update will remove table(s) [${removedModelsList}] and will replace table(s) [${replacedModelsList}]`; + } else if (removedModelsList) { + this.message = `${this.message}${os.EOL}This update will remove table(s) [${removedModelsList}]`; + } else if (replacedModelsList) { + this.message = `${this.message}${os.EOL}This update will replace table(s) [${replacedModelsList}]`; + } + this.message = `${this.message}${os.EOL}ALL EXISTING DATA IN THESE TABLES WILL BE LOST!${os.EOL}If this is intended, rerun the command with '--allow-destructive-graphql-schema-updates'.`; + } + toString = () => this.message; +} + /** * Thrown by the sanity checker when a user is trying to make a migration that is known to not work. */ export class InvalidMigrationError extends Error { - fix: string; - cause: string; - constructor(message: string, cause: string, fix: string) { + constructor(message: string, public cause: string, public fix: string) { super(message); - Object.setPrototypeOf(this, InvalidMigrationError.prototype); + Object.setPrototypeOf(this, new.target.prototype); this.name = 'InvalidMigrationError'; - this.fix = fix; - this.cause = cause; } + toString = () => `${this.message}\nCause: ${this.cause}\nHow to fix: ${this.fix}`; } -InvalidMigrationError.prototype.toString = function() { - return `${this.message}\nCause: ${this.cause}\nHow to fix: ${this.fix}`; -}; export class InvalidGSIMigrationError extends InvalidMigrationError { fix: string; diff --git a/packages/graphql-transformer-core/src/util/amplifyUtils.ts b/packages/graphql-transformer-core/src/util/amplifyUtils.ts index 869fafc1914..436dfb6177f 100644 --- a/packages/graphql-transformer-core/src/util/amplifyUtils.ts +++ b/packages/graphql-transformer-core/src/util/amplifyUtils.ts @@ -10,16 +10,17 @@ import { writeConfig, TransformConfig, TransformMigrationConfig, loadProject, re import { FeatureFlagProvider } from '../FeatureFlags'; import { cantAddAndRemoveGSIAtSameTimeRule, - cantAddLSILaterRule, - cantRemoveLSILater, + getCantAddLSILaterRule, + getCantRemoveLSILater, cantEditGSIKeySchemaRule, - cantEditKeySchemaRule, - cantEditLSIKeySchemaRule, + getCantEditKeySchemaRule, + getCantEditLSIKeySchemaRule, cantHaveMoreThan500ResourcesRule, DiffRule, sanityCheckProject, ProjectRule, cantMutateMultipleGSIAtUpdateTimeRule, + cantRemoveTableAfterCreation, } from './sanity-check'; export const CLOUDFORMATION_FILE_NAME = 'cloudformation-template.json'; @@ -727,34 +728,48 @@ function getOrDefault(o: any, k: string, d: any) { return o[k] || d; } -export function getSanityCheckRules(isNewAppSyncAPI: boolean, ff: FeatureFlagProvider) { +export function getSanityCheckRules(isNewAppSyncAPI: boolean, ff: FeatureFlagProvider, allowDestructiveUpdates: boolean = false) { let diffRules: DiffRule[] = []; let projectRules: ProjectRule[] = []; // If we have iterative GSI upgrades enabled it means we only do sanity check on LSIs // as the other checks will be carried out as series of updates. if (!isNewAppSyncAPI) { - if (ff.getBoolean('enableIterativeGSIUpdates')) { - diffRules.push( - // LSI - cantEditKeySchemaRule, - cantAddLSILaterRule, - cantRemoveLSILater, - cantEditLSIKeySchemaRule, - ); + const iterativeUpdatesEnabled = ff.getBoolean('enableIterativeGSIUpdates'); + if (iterativeUpdatesEnabled) { + if (!allowDestructiveUpdates) { + diffRules.push( + // primary key rule + getCantEditKeySchemaRule(iterativeUpdatesEnabled), + + // LSI rules + getCantAddLSILaterRule(iterativeUpdatesEnabled), + getCantRemoveLSILater(iterativeUpdatesEnabled), + getCantEditLSIKeySchemaRule(iterativeUpdatesEnabled), + + // remove table rules + cantRemoveTableAfterCreation, + ); + } - // Project level rules + // Project level rule projectRules.push(cantHaveMoreThan500ResourcesRule); } else { diffRules.push( - // LSI - cantEditKeySchemaRule, - cantAddLSILaterRule, - cantRemoveLSILater, - cantEditLSIKeySchemaRule, - // GSI + // primary key rule + getCantEditKeySchemaRule(), + + // LSI rules + getCantAddLSILaterRule(), + getCantRemoveLSILater(), + getCantEditLSIKeySchemaRule(), + + // GSI rules cantEditGSIKeySchemaRule, cantAddAndRemoveGSIAtSameTimeRule, ); + if (!allowDestructiveUpdates) { + diffRules.push(cantRemoveTableAfterCreation); + } projectRules.push(cantHaveMoreThan500ResourcesRule, cantMutateMultipleGSIAtUpdateTimeRule); } diff --git a/packages/graphql-transformer-core/src/util/sanity-check.ts b/packages/graphql-transformer-core/src/util/sanity-check.ts index a043673b3ef..9de3dcadc9c 100644 --- a/packages/graphql-transformer-core/src/util/sanity-check.ts +++ b/packages/graphql-transformer-core/src/util/sanity-check.ts @@ -1,11 +1,11 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import _ from 'lodash'; -import { Template } from 'cloudform-types'; +import { Template, ResourceBase } from 'cloudform-types'; import { JSONUtilities } from 'amplify-cli-core'; import { diff as getDiffs, Diff as DeepDiff } from 'deep-diff'; import { readFromPath } from './fileUtils'; -import { InvalidMigrationError, InvalidGSIMigrationError } from '../errors'; +import { InvalidMigrationError, InvalidGSIMigrationError, DestructiveMigrationError } from '../errors'; import { TRANSFORM_CONFIG_FILE_NAME } from '..'; type Diff = DeepDiff; @@ -73,18 +73,29 @@ export const sanityCheckDiffs = ( * @param currentBuild The last deployed build. * @param nextBuild The next build. */ -export const cantEditKeySchemaRule = (diff: Diff): void => { - if (diff.kind === 'E' && diff.path.length === 8 && diff.path[5] === 'KeySchema') { - // diff.path = [ "stacks", "Todo.json", "Resources", "TodoTable", "Properties", "KeySchema", 0, "AttributeName"] - const stackName = path.basename(diff.path[1], '.json'); - const tableName = diff.path[3]; +export const getCantEditKeySchemaRule = (iterativeUpdatesEnabled: boolean = false) => { + const cantEditKeySchemaRule = (diff: Diff): void => { + if (diff.kind === 'E' && diff.path.length === 8 && diff.path[5] === 'KeySchema') { + // diff.path = [ "stacks", "Todo.json", "Resources", "TodoTable", "Properties", "KeySchema", 0, "AttributeName"] + const stackName = path.basename(diff.path[1], '.json'); + const tableName = diff.path[3]; - throw new InvalidMigrationError( - `Attempting to edit the key schema of the ${tableName} table in the ${stackName} stack. `, - 'Adding a primary @key directive to an existing @model. ', - 'Remove the @key directive or provide a name e.g @key(name: "ByStatus", fields: ["status"]).', - ); - } + if (iterativeUpdatesEnabled) { + throw new DestructiveMigrationError( + 'Editing the primary key of a model requires replacement of the underlying DynamoDB table.', + [], + [tableName], + ); + } + + throw new InvalidMigrationError( + `Attempting to edit the key schema of the ${tableName} table in the ${stackName} stack. `, + 'Adding a primary @key directive to an existing @model. ', + 'Remove the @key directive or provide a name e.g @key(name: "ByStatus", fields: ["status"]).', + ); + } + }; + return cantEditKeySchemaRule; }; /** @@ -94,24 +105,35 @@ export const cantEditKeySchemaRule = (diff: Diff): void => { * @param currentBuild The last deployed build. * @param nextBuild The next build. */ -export const cantAddLSILaterRule = (diff: Diff): void => { - if ( - // When adding a LSI to a table that has 0 LSIs. - (diff.kind === 'N' && diff.path.length === 6 && diff.path[5] === 'LocalSecondaryIndexes') || - // When adding a LSI to a table that already has at least one LSI. - (diff.kind === 'A' && diff.path.length === 6 && diff.path[5] === 'LocalSecondaryIndexes' && diff.item.kind === 'N') - ) { - // diff.path = [ "stacks", "Todo.json", "Resources", "TodoTable", "Properties", "LocalSecondaryIndexes" ] - const stackName = path.basename(diff.path[1], '.json'); - const tableName = diff.path[3]; +export const getCantAddLSILaterRule = (iterativeUpdatesEnabled: boolean = false) => { + const cantAddLSILaterRule = (diff: Diff): void => { + if ( + // When adding a LSI to a table that has 0 LSIs. + (diff.kind === 'N' && diff.path.length === 6 && diff.path[5] === 'LocalSecondaryIndexes') || + // When adding a LSI to a table that already has at least one LSI. + (diff.kind === 'A' && diff.path.length === 6 && diff.path[5] === 'LocalSecondaryIndexes' && diff.item.kind === 'N') + ) { + // diff.path = [ "stacks", "Todo.json", "Resources", "TodoTable", "Properties", "LocalSecondaryIndexes" ] + const stackName = path.basename(diff.path[1], '.json'); + const tableName = diff.path[3]; - throw new InvalidMigrationError( - `Attempting to add a local secondary index to the ${tableName} table in the ${stackName} stack. ` + - 'Local secondary indexes must be created when the table is created.', - "Adding a @key directive where the first field in 'fields' is the same as the first field in the 'fields' of the primary @key.", - "Change the first field in 'fields' such that a global secondary index is created or delete and recreate the model.", - ); - } + if (iterativeUpdatesEnabled) { + throw new DestructiveMigrationError( + 'Adding an LSI to a model requires replacement of the underlying DynamoDB table.', + [], + [tableName], + ); + } + + throw new InvalidMigrationError( + `Attempting to add a local secondary index to the ${tableName} table in the ${stackName} stack. ` + + 'Local secondary indexes must be created when the table is created.', + "Adding a @key directive where the first field in 'fields' is the same as the first field in the 'fields' of the primary @key.", + "Change the first field in 'fields' such that a global secondary index is created or delete and recreate the model.", + ); + } + }; + return cantAddLSILaterRule; }; /** @@ -295,65 +317,78 @@ export const cantMutateMultipleGSIAtUpdateTimeRule = (diffs: Diff[], currentBuil * @param currentBuild The last deployed build. * @param nextBuild The next build. */ -export const cantEditLSIKeySchemaRule = (diff: Diff, currentBuild: DiffableProject, nextBuild: DiffableProject): void => { - if ( - // ["stacks","Todo.json","Resources","TodoTable","Properties","LocalSecondaryIndexes",0,"KeySchema",0,"AttributeName"] - diff.kind === 'E' && - diff.path.length === 10 && - diff.path[5] === 'LocalSecondaryIndexes' && - diff.path[7] === 'KeySchema' - ) { - // This error is symptomatic of a change to the GSI array but does not necessarily imply a breaking change. - const pathToGSIs = diff.path.slice(0, 6); - const oldIndexes = _.get(currentBuild, pathToGSIs); - const newIndexes = _.get(nextBuild, pathToGSIs); - const oldIndexesDiffable = _.keyBy(oldIndexes, 'IndexName'); - const newIndexesDiffable = _.keyBy(newIndexes, 'IndexName'); - const innerDiffs = getDiffs(oldIndexesDiffable, newIndexesDiffable) || []; - - // We must look at this inner diff or else we could confuse a situation - // where the user adds a LSI to the beginning of the LocalSecondaryIndex list in CFN. - // We re-key the indexes list so we can determine if a change occurred to an index that - // already exists. - for (const innerDiff of innerDiffs) { - // path: ["AGSI","KeySchema",0,"AttributeName"] - if (innerDiff.kind === 'E' && innerDiff.path.length > 2 && innerDiff.path[1] === 'KeySchema') { - const indexName = innerDiff.path[0]; - const stackName = path.basename(diff.path[1], '.json'); - const tableName = diff.path[3]; - - throw new InvalidMigrationError( - `Attempting to edit the local secondary index ${indexName} on the ${tableName} table in the ${stackName} stack. `, - 'The key schema of a local secondary index cannot be changed after being deployed.', - 'When enabling new access patterns you should: 1. Add a new @key 2. run amplify push ' + - '3. Verify the new access pattern and remove the old @key.', - ); +export const getCantEditLSIKeySchemaRule = (iterativeUpdatesEnabled: boolean = false) => { + const cantEditLSIKeySchemaRule = (diff: Diff, currentBuild: DiffableProject, nextBuild: DiffableProject): void => { + if ( + // ["stacks","Todo.json","Resources","TodoTable","Properties","LocalSecondaryIndexes",0,"KeySchema",0,"AttributeName"] + diff.kind === 'E' && + diff.path.length === 10 && + diff.path[5] === 'LocalSecondaryIndexes' && + diff.path[7] === 'KeySchema' + ) { + // This error is symptomatic of a change to the GSI array but does not necessarily imply a breaking change. + const pathToGSIs = diff.path.slice(0, 6); + const oldIndexes = _.get(currentBuild, pathToGSIs); + const newIndexes = _.get(nextBuild, pathToGSIs); + const oldIndexesDiffable = _.keyBy(oldIndexes, 'IndexName'); + const newIndexesDiffable = _.keyBy(newIndexes, 'IndexName'); + const innerDiffs = getDiffs(oldIndexesDiffable, newIndexesDiffable) || []; + + // We must look at this inner diff or else we could confuse a situation + // where the user adds a LSI to the beginning of the LocalSecondaryIndex list in CFN. + // We re-key the indexes list so we can determine if a change occurred to an index that + // already exists. + for (const innerDiff of innerDiffs) { + // path: ["AGSI","KeySchema",0,"AttributeName"] + if (innerDiff.kind === 'E' && innerDiff.path.length > 2 && innerDiff.path[1] === 'KeySchema') { + const indexName = innerDiff.path[0]; + const stackName = path.basename(diff.path[1], '.json'); + const tableName = diff.path[3]; + + if (iterativeUpdatesEnabled) { + throw new DestructiveMigrationError('Editing an LSI requires replacement of the underlying DynamoDB table.', [], [tableName]); + } + + throw new InvalidMigrationError( + `Attempting to edit the local secondary index ${indexName} on the ${tableName} table in the ${stackName} stack. `, + 'The key schema of a local secondary index cannot be changed after being deployed.', + 'When enabling new access patterns you should: 1. Add a new @key 2. run amplify push ' + + '3. Verify the new access pattern and remove the old @key.', + ); + } } } - } + }; + return cantEditLSIKeySchemaRule; }; -export function cantRemoveLSILater(diff: Diff, currentBuild: DiffableProject, nextBuild: DiffableProject) { - const throwError = (stackName: string, tableName: string): void => { - throw new InvalidMigrationError( - `Attempting to remove a local secondary index on the ${tableName} table in the ${stackName} stack.`, - 'A local secondary index cannot be removed after deployment.', - 'In order to remove the local secondary index you need to delete or rename the table.', - ); +export const getCantRemoveLSILater = (iterativeUpdatesEnabled: boolean = false) => { + const cantRemoveLSILater = (diff: Diff, currentBuild: DiffableProject, nextBuild: DiffableProject) => { + const throwError = (stackName: string, tableName: string): void => { + if (iterativeUpdatesEnabled) { + throw new DestructiveMigrationError('Removing an LSI requires replacement of the underlying DynamoDB table.', [], [tableName]); + } + throw new InvalidMigrationError( + `Attempting to remove a local secondary index on the ${tableName} table in the ${stackName} stack.`, + 'A local secondary index cannot be removed after deployment.', + 'In order to remove the local secondary index you need to delete or rename the table.', + ); + }; + // if removing more than one lsi + if (diff.kind === 'D' && diff.lhs && diff.path.length === 6 && diff.path[5] === 'LocalSecondaryIndexes') { + const tableName = diff.path[3]; + const stackName = path.basename(diff.path[1], '.json'); + throwError(stackName, tableName); + } + // if removing one lsi + if (diff.kind === 'A' && diff.item.kind === 'D' && diff.path.length === 6 && diff.path[5] === 'LocalSecondaryIndexes') { + const tableName = diff.path[3]; + const stackName = path.basename(diff.path[1], '.json'); + throwError(stackName, tableName); + } }; - // if removing more than one lsi - if (diff.kind === 'D' && diff.lhs && diff.path.length === 6 && diff.path[5] === 'LocalSecondaryIndexes') { - const tableName = diff.path[3]; - const stackName = path.basename(diff.path[1], '.json'); - throwError(stackName, tableName); - } - // if removing one lsi - if (diff.kind === 'A' && diff.item.kind === 'D' && diff.path.length === 6 && diff.path[5] === 'LocalSecondaryIndexes') { - const tableName = diff.path[3]; - const stackName = path.basename(diff.path[1], '.json'); - throwError(stackName, tableName); - } -} + return cantRemoveLSILater; +}; export const cantHaveMoreThan500ResourcesRule = (diffs: Diff[], currentBuild: DiffableProject, nextBuild: DiffableProject): void => { const stackKeys = Object.keys(nextBuild.stacks); @@ -373,6 +408,25 @@ export const cantHaveMoreThan500ResourcesRule = (diffs: Diff[], currentBuild: Di } }; +export const cantRemoveTableAfterCreation = (_: Diff, currentBuild: DiffableProject, nextBuild: DiffableProject): void => { + const getNestedStackLogicalIds = (proj: DiffableProject) => + Object.entries(proj.root.Resources || []) + .filter(([_, meta]) => meta.Type === 'AWS::CloudFormation::Stack') + .map(([name]) => name); + const currentModels = getNestedStackLogicalIds(currentBuild); + const nextModels = getNestedStackLogicalIds(nextBuild); + const removedModels = currentModels + .filter(currModel => !nextModels.includes(currModel)) + .filter(stackLogicalId => stackLogicalId !== 'ConnectionStack'); + if (removedModels.length > 0) { + throw new DestructiveMigrationError( + 'Removing a model from the GraphQL schema will also remove the underlying DynamoDB table.', + removedModels, + [], + ); + } +}; + const loadDiffableProject = async (path: string, rootStackName: string): Promise => { const project = await readFromPath(path); const currentStacks = project.stacks || {}; diff --git a/packages/graphql-transformers-e2e-tests/package.json b/packages/graphql-transformers-e2e-tests/package.json index 36e665c2cc2..b7324a29e4b 100644 --- a/packages/graphql-transformers-e2e-tests/package.json +++ b/packages/graphql-transformers-e2e-tests/package.json @@ -35,6 +35,7 @@ "@aws-amplify/graphql-index-transformer": "0.4.0", "@aws-amplify/graphql-model-transformer": "0.6.4", "@aws-amplify/graphql-transformer-core": "0.9.2", + "@aws-amplify/graphql-transformer-interfaces": "1.10.0", "@types/node": "^12.12.6", "aws-amplify": "^4.2.8", "aws-appsync": "^4.1.1", diff --git a/packages/graphql-transformers-e2e-tests/src/IAMHelper.ts b/packages/graphql-transformers-e2e-tests/src/IAMHelper.ts index 85a488c8986..e5371a64138 100644 --- a/packages/graphql-transformers-e2e-tests/src/IAMHelper.ts +++ b/packages/graphql-transformers-e2e-tests/src/IAMHelper.ts @@ -8,6 +8,48 @@ export class IAMHelper { }); } + /** + * Creates auth and unauth roles + */ + async createRoles(authRoleName: string, unauthRoleName: string): Promise<{ authRole: IAM.Role; unauthRole: IAM.Role }> { + const authRole = await this.client + .createRole({ + RoleName: authRoleName, + AssumeRolePolicyDocument: `{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + }, + "Action": "sts:AssumeRoleWithWebIdentity" + } + ] + }`, + }) + .promise(); + const unauthRole = await this.client + .createRole({ + RoleName: unauthRoleName, + AssumeRolePolicyDocument: `{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + }, + "Action": "sts:AssumeRoleWithWebIdentity" + } + ] + }`, + }) + .promise(); + + return { authRole: authRole.Role, unauthRole: unauthRole.Role }; + } + async createLambdaExecutionRole(name: string) { return await this.client .createRole({ diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/AuthV2Transformer.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/AuthV2Transformer.e2e.test.ts new file mode 100644 index 00000000000..5721d076abd --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/AuthV2Transformer.e2e.test.ts @@ -0,0 +1,3329 @@ +import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { HasOneTransformer } from '@aws-amplify/graphql-relational-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { S3Client } from '../S3Client'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { default as CognitoClient } from 'aws-sdk/clients/cognitoidentityserviceprovider'; +import { default as S3 } from 'aws-sdk/clients/s3'; +import moment from 'moment'; +import { + createUserPool, + createUserPoolClient, + configureAmplify, + addUserToGroup, + authenticateUser, + createGroup, + signupUser, +} from '../cognitoUtils'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { GraphQLClient } from '../GraphQLClient'; + +jest.setTimeout(2000000); + +describe('@model with @auth', () => { + // setup clients + const cf = new CloudFormationClient('us-west-2'); + const customS3Client = new S3Client('us-west-2'); + const cognitoClient = new CognitoClient({ apiVersion: '2016-04-19', region: 'us-west-2' }); + const awsS3Client = new S3({ region: 'us-west-2' }); + + // stack info + const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); + const STACK_NAME = `AuthV2TransformerTests-${BUILD_TIMESTAMP}`; + const BUCKET_NAME = `appsync-auth-v2-transformer-test-bucket-${BUILD_TIMESTAMP}`; + const LOCAL_FS_BUILD_DIR = '/tmp/authv2_transformer_tests/'; + const S3_ROOT_DIR_KEY = 'deployments'; + let USER_POOL_ID: string; + let GRAPHQL_ENDPOINT: string; + /** + * Client 1 is logged in and is a member of the Admin group. + */ + let GRAPHQL_CLIENT_1: GraphQLClient; + + /** + * Client 1 is logged in and is a member of the Admin group via an access token. + */ + let GRAPHQL_CLIENT_1_ACCESS: GraphQLClient; + + /** + * Client 2 is logged in and is a member of the Devs group. + */ + let GRAPHQL_CLIENT_2: GraphQLClient; + + /** + * Client 3 is logged in and is a member of the Devs-Admin group via an access token. + */ + let GRAPHQL_CLIENT_3: GraphQLClient; + + // env info + const USERNAME1 = 'user1@test.com'; + const USERNAME2 = 'user2@test.com'; + const USERNAME3 = 'user3@test.com'; + const TMP_PASSWORD = 'Password123!'; + const REAL_PASSWORD = 'Password1234!'; + + const ADMIN_GROUP_NAME = 'Admin'; + const DEVS_GROUP_NAME = 'Devs'; + const DEVS_ADMIN_GROUP_NAME = 'Devs-Admin'; + const PARTICIPANT_GROUP_NAME = 'Participant'; + const WATCHER_GROUP_NAME = 'Watcher'; + + function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; + } + beforeAll(async () => { + const validSchema = ` + type Post @model @auth(rules: [{ allow: owner }]) { + id: ID! + title: String! + createdAt: AWSDateTime + updatedAt: AWSDateTime + owner: String + } + type Salary + @model + @auth(rules: [{ allow: owner }, { allow: groups, groups: ["Admin"] }]) { + id: ID! + wage: Int + owner: String + } + type AdminNote + @model + @auth( + rules: [{ allow: groups, groups: ["Admin"], groupClaim: "cognito:groups" }] + ) { + id: ID! + content: String! + } + type ManyGroupProtected + @model + @auth(rules: [{ allow: groups, groupsField: "groups" }]) { + id: ID! + value: Int + groups: [String] + } + type SingleGroupProtected + @model + @auth(rules: [{ allow: groups, groupsField: "group" }]) { + id: ID! + value: Int + group: String + } + type PWProtected + @auth( + rules: [ + { + allow: groups + groups: ["Devs"] + } + { + allow: groups + groupsField: "participants" + operations: [read, update, delete] + } + { allow: groups, groupsField: "watchers", operations: [read] } + ] + ) + @model { + id: ID! + content: String! + participants: String + watchers: String + } + type AllThree + @auth( + rules: [ + { allow: owner, identityClaim: "username" } + { allow: owner, ownerField: "editors", identityClaim: "cognito:username" } + { allow: groups, groups: ["Admin", "Execs"] } + { allow: groups, groupsField: "groups" } + { allow: groups, groupsField: "alternativeGroup" } + ] + ) + @model { + id: ID! + owner: String + editors: [String] + groups: [String] + alternativeGroup: String + } + # The owner should always start with https://cognito-idp + type TestIdentity + @model + @auth(rules: [{ allow: owner, identityClaim: "iss" }]) { + id: ID! + title: String! + owner: String + } + type OwnerReadProtected + @model + @auth(rules: [{ allow: groups, groups: ["Admin"]},{ allow: owner, operations: [read] }]) { + id: ID! @primaryKey(sortKeyFields: ["sk"]) + sk: String! + content: String + owner: String + } + type OwnerCreateUpdateDeleteProtected + @model + @auth(rules: [{ allow: owner, operations: [create, update, delete] }]) { + id: ID! + content: String + owner: String + } + type Performance + @model + @auth( + rules: [ + { allow: groups, groups: ["Admin"] } + { allow: private, operations: [read] } + ] + ) { + id: ID + performer: String! + description: String! + time: AWSDateTime + stage: Stage @hasOne + } + type Stage + @model + @auth( + rules: [ + { allow: groups, groups: ["Admin"] } + { allow: private, operations: [read] } + ] + ) { + id: ID! + name: String! + }`; + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + throw Error(`Could not create bucket: ${e}`); + } + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }; + const transformer = new GraphQLTransform({ + authConfig, + transformers: [ + new ModelTransformer(), + new PrimaryKeyTransformer(), + new IndexTransformer(), + new HasOneTransformer(), + new AuthTransformer({ + authConfig, + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const userPoolResponse = await createUserPool(cognitoClient, `UserPool${STACK_NAME}`); + USER_POOL_ID = userPoolResponse.UserPool.Id; + const userPoolClientResponse = await createUserPoolClient(cognitoClient, USER_POOL_ID, `UserPool${STACK_NAME}`); + const userPoolClientId = userPoolClientResponse.UserPoolClient.ClientId; + try { + const out = transformer.transform(validSchema); + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + { AuthCognitoUserPoolId: USER_POOL_ID }, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + // Wait for any propagation to avoid random + // "The security token included in the request is invalid" errors + await new Promise(res => setTimeout(() => res(), 5000)); + + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const getApiKey = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIApiKeyOutput); + GRAPHQL_ENDPOINT = getApiEndpoint(finishedStack.Outputs); + + const apiKey = getApiKey(finishedStack.Outputs); + expect(apiKey).not.toBeTruthy(); + + // Verify we have all the details + expect(GRAPHQL_ENDPOINT).toBeTruthy(); + expect(USER_POOL_ID).toBeTruthy(); + expect(userPoolClientId).toBeTruthy(); + + // Configure Amplify, create users, and sign in + configureAmplify(USER_POOL_ID, userPoolClientId); + + await signupUser(USER_POOL_ID, USERNAME1, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME2, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME3, TMP_PASSWORD); + await createGroup(USER_POOL_ID, ADMIN_GROUP_NAME); + await createGroup(USER_POOL_ID, PARTICIPANT_GROUP_NAME); + await createGroup(USER_POOL_ID, WATCHER_GROUP_NAME); + await createGroup(USER_POOL_ID, DEVS_GROUP_NAME); + await createGroup(USER_POOL_ID, DEVS_ADMIN_GROUP_NAME); + await addUserToGroup(ADMIN_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(PARTICIPANT_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(WATCHER_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(DEVS_GROUP_NAME, USERNAME2, USER_POOL_ID); + await addUserToGroup(DEVS_ADMIN_GROUP_NAME, USERNAME3, USER_POOL_ID); + const authResAfterGroup: any = await authenticateUser(USERNAME1, TMP_PASSWORD, REAL_PASSWORD); + + const idToken = authResAfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_1 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken }); + + const accessToken = authResAfterGroup.getAccessToken().getJwtToken(); + GRAPHQL_CLIENT_1_ACCESS = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: accessToken }); + + const authRes2AfterGroup: any = await authenticateUser(USERNAME2, TMP_PASSWORD, REAL_PASSWORD); + const idToken2 = authRes2AfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_2 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken2 }); + + const authRes3AfterGroup: any = await authenticateUser(USERNAME3, TMP_PASSWORD, REAL_PASSWORD); + const idToken3 = authRes3AfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_3 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken3 }); + } catch (e) { + console.error(`Could not setup transformer ${e}`); + expect(true).toBe(false); + } + }); + + afterAll(async () => { + await cleanupStackAfterTest(BUCKET_NAME, STACK_NAME, cf, { cognitoClient, userPoolId: USER_POOL_ID }); + }); + + /** + * Test queries below + */ + test('Test createPost mutation', async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(response.data.createPost.id).toBeDefined(); + expect(response.data.createPost.title).toEqual('Hello, World!'); + expect(response.data.createPost.createdAt).toBeDefined(); + expect(response.data.createPost.updatedAt).toBeDefined(); + expect(response.data.createPost.owner).toEqual(USERNAME1); + + const response2 = await GRAPHQL_CLIENT_1_ACCESS.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(response2.data.createPost.id).toBeDefined(); + expect(response2.data.createPost.title).toEqual('Hello, World!'); + expect(response2.data.createPost.createdAt).toBeDefined(); + expect(response2.data.createPost.updatedAt).toBeDefined(); + expect(response2.data.createPost.owner).toEqual(USERNAME1); + }); + + test('Test getPost query when authorized', async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(response.data.createPost.id).toBeDefined(); + expect(response.data.createPost.title).toEqual('Hello, World!'); + expect(response.data.createPost.createdAt).toBeDefined(); + expect(response.data.createPost.updatedAt).toBeDefined(); + expect(response.data.createPost.owner).toEqual(USERNAME1); + const getResponse = await GRAPHQL_CLIENT_1.query( + `query { + getPost(id: "${response.data.createPost.id}") { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(getResponse.data.getPost.id).toBeDefined(); + expect(getResponse.data.getPost.title).toEqual('Hello, World!'); + expect(getResponse.data.getPost.createdAt).toBeDefined(); + expect(getResponse.data.getPost.updatedAt).toBeDefined(); + expect(getResponse.data.getPost.owner).toEqual(USERNAME1); + + const getResponseAccess = await GRAPHQL_CLIENT_1_ACCESS.query( + `query { + getPost(id: "${response.data.createPost.id}") { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(getResponseAccess.data.getPost.id).toBeDefined(); + expect(getResponseAccess.data.getPost.title).toEqual('Hello, World!'); + expect(getResponseAccess.data.getPost.createdAt).toBeDefined(); + expect(getResponseAccess.data.getPost.updatedAt).toBeDefined(); + expect(getResponseAccess.data.getPost.owner).toEqual(USERNAME1); + }); + + test('Test getPost query when not authorized', async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(response.data.createPost.id).toBeDefined(); + expect(response.data.createPost.title).toEqual('Hello, World!'); + expect(response.data.createPost.createdAt).toBeDefined(); + expect(response.data.createPost.updatedAt).toBeDefined(); + expect(response.data.createPost.owner).toBeDefined(); + const getResponse = await GRAPHQL_CLIENT_2.query( + `query { + getPost(id: "${response.data.createPost.id}") { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(getResponse.data.getPost).toEqual(null); + expect(getResponse.errors.length).toEqual(1); + expect((getResponse.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test('Test updatePost mutation when authorized', async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(response.data.createPost.id).toBeDefined(); + expect(response.data.createPost.title).toEqual('Hello, World!'); + expect(response.data.createPost.createdAt).toBeDefined(); + expect(response.data.createPost.updatedAt).toBeDefined(); + expect(response.data.createPost.owner).toEqual(USERNAME1); + const updateResponse = await GRAPHQL_CLIENT_1.query( + `mutation { + updatePost(input: { id: "${response.data.createPost.id}", title: "Bye, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(updateResponse.data.updatePost.id).toEqual(response.data.createPost.id); + expect(updateResponse.data.updatePost.title).toEqual('Bye, World!'); + expect(updateResponse.data.updatePost.updatedAt > response.data.createPost.updatedAt).toEqual(true); + + const updateResponseAccess = await GRAPHQL_CLIENT_1_ACCESS.query( + `mutation { + updatePost(input: { id: "${response.data.createPost.id}", title: "Bye, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(updateResponseAccess.data.updatePost.id).toEqual(response.data.createPost.id); + expect(updateResponseAccess.data.updatePost.title).toEqual('Bye, World!'); + expect(updateResponseAccess.data.updatePost.updatedAt > response.data.createPost.updatedAt).toEqual(true); + }); + + test('Test updatePost mutation when not authorized', async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(response.data.createPost.id).toBeDefined(); + expect(response.data.createPost.title).toEqual('Hello, World!'); + expect(response.data.createPost.createdAt).toBeDefined(); + expect(response.data.createPost.updatedAt).toBeDefined(); + expect(response.data.createPost.owner).toBeDefined(); + const updateResponse = await GRAPHQL_CLIENT_2.query( + `mutation { + updatePost(input: { id: "${response.data.createPost.id}", title: "Bye, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(updateResponse.data.updatePost).toEqual(null); + expect(updateResponse.errors.length).toEqual(1); + expect((updateResponse.errors[0] as any).data).toBeNull(); + expect((updateResponse.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test('Test deletePost mutation when authorized', async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(response.data.createPost.id).toBeDefined(); + expect(response.data.createPost.title).toEqual('Hello, World!'); + expect(response.data.createPost.createdAt).toBeDefined(); + expect(response.data.createPost.updatedAt).toBeDefined(); + expect(response.data.createPost.owner).toEqual(USERNAME1); + const deleteResponse = await GRAPHQL_CLIENT_1.query( + `mutation { + deletePost(input: { id: "${response.data.createPost.id}" }) { + id + } + }`, + {}, + ); + expect(deleteResponse.data.deletePost.id).toEqual(response.data.createPost.id); + + const responseAccess = await GRAPHQL_CLIENT_1_ACCESS.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(responseAccess.data.createPost.id).toBeDefined(); + expect(responseAccess.data.createPost.title).toEqual('Hello, World!'); + expect(responseAccess.data.createPost.createdAt).toBeDefined(); + expect(responseAccess.data.createPost.updatedAt).toBeDefined(); + expect(responseAccess.data.createPost.owner).toEqual(USERNAME1); + const deleteResponseAccess = await GRAPHQL_CLIENT_1_ACCESS.query( + `mutation { + deletePost(input: { id: "${responseAccess.data.createPost.id}" }) { + id + } + }`, + {}, + ); + expect(deleteResponseAccess.data.deletePost.id).toEqual(responseAccess.data.createPost.id); + }); + + test('Test deletePost mutation when not authorized', async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "Hello, World!" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(response.data.createPost.id).toBeDefined(); + expect(response.data.createPost.title).toEqual('Hello, World!'); + expect(response.data.createPost.createdAt).toBeDefined(); + expect(response.data.createPost.updatedAt).toBeDefined(); + expect(response.data.createPost.owner).toEqual(USERNAME1); + const deleteResponse = await GRAPHQL_CLIENT_2.query( + `mutation { + deletePost(input: { id: "${response.data.createPost.id}" }) { + id + } + }`, + {}, + ); + expect(deleteResponse.data.deletePost).toEqual(null); + expect(deleteResponse.errors.length).toEqual(1); + expect((deleteResponse.errors[0] as any).data).toBeNull(); + expect((deleteResponse.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test('Test listPosts query when authorized', async () => { + const firstPost = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "testing list" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + expect(firstPost.data.createPost.id).toBeDefined(); + expect(firstPost.data.createPost.title).toEqual('testing list'); + expect(firstPost.data.createPost.createdAt).toBeDefined(); + expect(firstPost.data.createPost.updatedAt).toBeDefined(); + expect(firstPost.data.createPost.owner).toEqual(USERNAME1); + await GRAPHQL_CLIENT_2.query( + `mutation { + createPost(input: { title: "testing list" }) { + id + title + createdAt + updatedAt + owner + } + }`, + {}, + ); + // There are two posts but only 1 created by me. + const listResponse = await GRAPHQL_CLIENT_1.query( + `query { + listPosts(filter: { title: { eq: "testing list" } }, limit: 25) { + items { + id + } + } + }`, + {}, + ); + expect(listResponse.data.listPosts.items.length).toEqual(1); + + const listResponseAccess = await GRAPHQL_CLIENT_1_ACCESS.query( + `query { + listPosts(filter: { title: { eq: "testing list" } }, limit: 25) { + items { + id + } + } + }`, + {}, + ); + expect(listResponseAccess.data.listPosts.items.length).toEqual(1); + }); + + /** + * Static Group Auth + */ + test(`Test createSalary w/ Admin group protection authorized`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createSalary(input: { wage: 10 }) { + id + wage + } + } + `, + {}, + ); + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(10); + }); + + test(`Test update my own salary without admin permission`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createSalary(input: { wage: 10 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.wage).toEqual(10); + const req2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + updateSalary(input: { id: "${req.data.createSalary.id}", wage: 14 }) { + id + wage + } + } + `, + {}, + ); + + expect(req2.data.updateSalary.wage).toEqual(14); + }); + + test(`Test updating someone else's salary as an admin`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createSalary(input: { wage: 11 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(11); + const req2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + updateSalary(input: { id: "${req.data.createSalary.id}", wage: 12 }) { + id + wage + } + } + `, + {}, + ); + + expect(req2.data.updateSalary.id).toEqual(req.data.createSalary.id); + expect(req2.data.updateSalary.wage).toEqual(12); + }); + + test(`Test updating someone else's salary when I am not admin.`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createSalary(input: { wage: 13 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(13); + const req2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + updateSalary(input: { id: "${req.data.createSalary.id}", wage: 14 }) { + id + wage + } + } + `, + {}, + ); + expect(req2.data.updateSalary).toEqual(null); + expect(req2.errors.length).toEqual(1); + expect((req2.errors[0] as any).data).toBeNull(); + expect((req2.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test(`Test deleteSalary w/ Admin group protection authorized`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createSalary(input: { wage: 15 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(15); + const req2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteSalary(input: { id: "${req.data.createSalary.id}" }) { + id + wage + } + } + `, + {}, + ); + + expect(req2.data.deleteSalary.id).toEqual(req.data.createSalary.id); + expect(req2.data.deleteSalary.wage).toEqual(15); + }); + + test(`Test deleteSalary w/ Admin group protection not authorized`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createSalary(input: { wage: 16 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(16); + const req2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + deleteSalary(input: { id: "${req.data.createSalary.id}" }) { + id + wage + } + } + `, + {}, + ); + expect(req2.data.deleteSalary).toEqual(null); + expect(req2.errors.length).toEqual(1); + expect((req2.errors[0] as any).data).toBeNull(); + expect((req2.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test(`Test and Admin can get a salary created by any user`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createSalary(input: { wage: 15 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(15); + const req2 = await GRAPHQL_CLIENT_1.query( + ` + query { + getSalary(id: "${req.data.createSalary.id}") { + id + wage + } + } + `, + {}, + ); + expect(req2.data.getSalary.id).toEqual(req.data.createSalary.id); + expect(req2.data.getSalary.wage).toEqual(15); + }); + + test(`Test owner can create and get a salary when not admin`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createSalary(input: { wage: 15 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(15); + const req2 = await GRAPHQL_CLIENT_2.query( + ` + query { + getSalary(id: "${req.data.createSalary.id}") { + id + wage + } + } + `, + {}, + ); + expect(req2.data.getSalary.id).toEqual(req.data.createSalary.id); + expect(req2.data.getSalary.wage).toEqual(15); + }); + + test(`Test getSalary w/ Admin group protection not authorized`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createSalary(input: { wage: 16 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(16); + const req2 = await GRAPHQL_CLIENT_2.query( + ` + query { + getSalary(id: "${req.data.createSalary.id}") { + id + wage + } + } + `, + {}, + ); + expect(req2.data.getSalary).toEqual(null); + expect(req2.errors.length).toEqual(1); + expect((req2.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test(`Test listSalaries w/ Admin group protection authorized`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createSalary(input: { wage: 101 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(101); + const req2 = await GRAPHQL_CLIENT_1.query( + ` + query { + listSalaries(filter: { wage: { eq: 101 }}) { + items { + id + wage + } + } + } + `, + {}, + ); + expect(req2.data.listSalaries.items.length).toEqual(1); + expect(req2.data.listSalaries.items[0].id).toEqual(req.data.createSalary.id); + expect(req2.data.listSalaries.items[0].wage).toEqual(101); + }); + + test(`Test listSalaries w/ Admin group protection not authorized`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createSalary(input: { wage: 102 }) { + id + wage + } + } + `, + {}, + ); + + expect(req.data.createSalary.id).toBeDefined(); + expect(req.data.createSalary.wage).toEqual(102); + const req2 = await GRAPHQL_CLIENT_2.query( + ` + query { + listSalaries(filter: { wage: { eq: 102 }}) { + items { + id + wage + } + } + } + `, + {}, + ); + expect(req2.data.listSalaries.items).toEqual([]); + }); + + /** + * Dynamic Group Auth + */ + test(`Test createManyGroupProtected w/ dynamic group protection authorized`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createManyGroupProtected(input: { value: 10, groups: ["Admin"] }) { + id + value + groups + } + } + `, + {}, + ); + + expect(req.data.createManyGroupProtected.id).toBeDefined(); + expect(req.data.createManyGroupProtected.value).toEqual(10); + expect(req.data.createManyGroupProtected.groups).toEqual(['Admin']); + }); + + test(`Test createManyGroupProtected w/ dynamic group protection when not authorized`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createManyGroupProtected(input: { value: 10, groups: ["Admin"] }) { + id + value + groups + } + } + `, + {}, + ); + + expect(req.data.createManyGroupProtected).toEqual(null); + expect(req.errors.length).toEqual(1); + expect((req.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test(`Test updateSingleGroupProtected when user is not authorized but has a group that is a substring of the allowed group`, async () => { + const req = await GRAPHQL_CLIENT_3.query( + `mutation { + createSingleGroupProtected(input: { value: 11, group: "Devs-Admin" }) { + id + value + group + } + } + `, + {}, + ); + + const req2 = await GRAPHQL_CLIENT_2.query( + `mutation { + updateSingleGroupProtected(input: {id: "${req.data.createSingleGroupProtected.id}", value: 5 }) { + id + value + group + } + } + `, + {}, + ); + + const req3 = await GRAPHQL_CLIENT_3.query( + `query { + getSingleGroupProtected(id: "${req.data.createSingleGroupProtected.id}") { + id + value + group + } + } + `, + {}, + ); + + expect(req.data.createSingleGroupProtected.value).toEqual(11); + expect(req.data.createSingleGroupProtected.value).toEqual(req3.data.getSingleGroupProtected.value); + expect((req2.errors[0] as any).data).toBeNull(); + expect((req2.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test(`Test createSingleGroupProtected w/ dynamic group protection authorized`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createSingleGroupProtected(input: { value: 10, group: "Admin" }) { + id + value + group + } + } + `, + {}, + ); + + expect(req.data.createSingleGroupProtected.id).toBeDefined(); + expect(req.data.createSingleGroupProtected.value).toEqual(10); + expect(req.data.createSingleGroupProtected.group).toEqual('Admin'); + }); + + test(`Test createSingleGroupProtected w/ dynamic group protection when not authorized`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createSingleGroupProtected(input: { value: 10, group: "Admin" }) { + id + value + group + } + } + `, + {}, + ); + + expect(req.data.createSingleGroupProtected).toEqual(null); + expect(req.errors.length).toEqual(1); + expect((req.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test(`Test listPWProtecteds when the user is authorized.`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createPWProtected(input: { content: "Foobie", participants: "${PARTICIPANT_GROUP_NAME}", watchers: "${WATCHER_GROUP_NAME}" }) { + id + content + participants + watchers + } + } + `, + {}, + ); + + expect(req.data.createPWProtected).toBeTruthy(); + + const uReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + updatePWProtected(input: { id: "${req.data.createPWProtected.id}", content: "Foobie2" }) { + id + content + participants + watchers + } + } + `, + {}, + ); + expect(uReq.data.updatePWProtected).toBeTruthy(); + + const req2 = await GRAPHQL_CLIENT_1.query( + ` + query { + listPWProtecteds { + items { + id + content + participants + watchers + } + nextToken + } + } + `, + {}, + ); + expect(req2.data.listPWProtecteds.items.length).toEqual(1); + expect(req2.data.listPWProtecteds.items[0].id).toEqual(req.data.createPWProtected.id); + expect(req2.data.listPWProtecteds.items[0].content).toEqual('Foobie2'); + + const req3 = await GRAPHQL_CLIENT_1.query( + ` + query { + getPWProtected(id: "${req.data.createPWProtected.id}") { + id + content + participants + watchers + } + } + `, + {}, + ); + + expect(req3.data.getPWProtected).toBeTruthy(); + + const dReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deletePWProtected(input: { id: "${req.data.createPWProtected.id}" }) { + id + content + participants + watchers + } + } + `, + {}, + ); + expect(dReq.data.deletePWProtected).toBeTruthy(); + }); + + test(`Test listPWProtecteds when groups is null in dynamodb.`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + `mutation { + createPWProtected(input: { content: "Foobie" }) { + id + content + participants + watchers + } + } + `, + {}, + ); + + expect(req.data.createPWProtected).toBeTruthy(); + + const req2 = await GRAPHQL_CLIENT_1.query( + ` + query { + listPWProtecteds { + items { + id + content + participants + watchers + } + nextToken + } + } + `, + {}, + ); + expect(req2.data.listPWProtecteds.items.length).toEqual(0); + + const req3 = await GRAPHQL_CLIENT_1.query( + ` + query { + getPWProtected(id: "${req.data.createPWProtected.id}") { + id + content + participants + watchers + } + } + `, + {}, + ); + + expect(req3.data.getPWProtected).toEqual(null); + expect(req3.errors.length).toEqual(1); + expect((req3.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + test(`Test Protecteds when the user is not authorized.`, async () => { + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createPWProtected(input: { content: "Barbie", participants: "${PARTICIPANT_GROUP_NAME}", watchers: "${WATCHER_GROUP_NAME}" }) { + id + content + participants + watchers + } + } + `, + {}, + ); + + expect(req.data.createPWProtected).toBeTruthy(); + + const req2 = await GRAPHQL_CLIENT_3.query( + ` + query { + listPWProtecteds { + items { + id + content + participants + watchers + } + nextToken + } + } + `, + {}, + ); + + expect(req2.data.listPWProtecteds.items.length).toEqual(0); + expect(req2.data.listPWProtecteds.nextToken).toBeNull(); + + const uReq = await GRAPHQL_CLIENT_3.query( + ` + mutation { + updatePWProtected(input: { id: "${req.data.createPWProtected.id}", content: "Foobie2" }) { + id + content + participants + watchers + } + } + `, + {}, + ); + expect(uReq.data.updatePWProtected).toBeNull(); + + const req3 = await GRAPHQL_CLIENT_3.query( + ` + query { + getPWProtected(id: "${req.data.createPWProtected.id}") { + id + content + participants + watchers + } + } + `, + {}, + ); + + expect(req3.data.getPWProtected).toBeNull(); + + const dReq = await GRAPHQL_CLIENT_3.query( + ` + mutation { + deletePWProtected(input: { id: "${req.data.createPWProtected.id}" }) { + id + content + participants + watchers + } + } + `, + {}, + ); + expect(dReq.data.deletePWProtected).toBeNull(); + + // The record should still exist after delete. + const getReq = await GRAPHQL_CLIENT_1.query( + ` + query { + getPWProtected(id: "${req.data.createPWProtected.id}") { + id + content + participants + watchers + } + } + `, + {}, + ); + expect(getReq.data.getPWProtected).toBeTruthy(); + }); + + test(`Test creating, updating, and deleting an admin note as an admin`, async () => { + const req = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAdminNote(input: { content: "Hello" }) { + id + content + } + } + `, + {}, + ); + + expect(req.data.createAdminNote.id).toBeDefined(); + expect(req.data.createAdminNote.content).toEqual('Hello'); + const req2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + updateAdminNote(input: { id: "${req.data.createAdminNote.id}", content: "Hello 2" }) { + id + content + } + } + `, + {}, + ); + + expect(req2.data.updateAdminNote.id).toEqual(req.data.createAdminNote.id); + expect(req2.data.updateAdminNote.content).toEqual('Hello 2'); + const req3 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAdminNote(input: { id: "${req.data.createAdminNote.id}" }) { + id + content + } + } + `, + {}, + ); + + expect(req3.data.deleteAdminNote.id).toEqual(req.data.createAdminNote.id); + expect(req3.data.deleteAdminNote.content).toEqual('Hello 2'); + }); + + test(`Test creating, updating, and deleting an admin note as a non admin`, async () => { + const adminReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAdminNote(input: { content: "Hello" }) { + id + content + } + } + `, + {}, + ); + expect(adminReq.data.createAdminNote.id).toBeDefined(); + expect(adminReq.data.createAdminNote.content).toEqual('Hello'); + + const req = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAdminNote(input: { content: "Hello" }) { + id + content + } + } + `, + {}, + ); + + expect(req.errors.length).toEqual(1); + expect((req.errors[0] as any).errorType).toEqual('Unauthorized'); + + const req2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + updateAdminNote(input: { id: "${adminReq.data.createAdminNote.id}", content: "Hello 2" }) { + id + content + } + } + `, + {}, + ); + + expect(req2.errors.length).toEqual(1); + expect((req2.errors[0] as any).errorType).toEqual('Unauthorized'); + + const req3 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + deleteAdminNote(input: { id: "${adminReq.data.createAdminNote.id}" }) { + id + content + } + } + `, + {}, + ); + + expect(req3.errors.length).toEqual(1); + expect((req3.errors[0] as any).errorType).toEqual('Unauthorized'); + }); + + /** + * Get Query Tests + */ + + test(`Test getAllThree as admin.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: "user2@test.com" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + + const fetchOwnedBy2AsAdmin = await GRAPHQL_CLIENT_1.query( + ` + query { + getAllThree(id: "${ownedBy2.data.createAllThree.id}") { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(fetchOwnedBy2AsAdmin.data.getAllThree).toBeTruthy(); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test getAllThree as owner.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: "user2@test.com" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + + const fetchOwnedBy2AsOwner = await GRAPHQL_CLIENT_2.query( + ` + query { + getAllThree(id: "${ownedBy2.data.createAllThree.id}") { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(fetchOwnedBy2AsOwner.data.getAllThree).toBeTruthy(); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test getAllThree as one of a set of editors.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + editors: ["user2@test.com"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + + const fetchOwnedBy2AsEditor = await GRAPHQL_CLIENT_2.query( + ` + query { + getAllThree(id: "${ownedBy2.data.createAllThree.id}") { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(fetchOwnedBy2AsEditor.data.getAllThree).toBeTruthy(); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test getAllThree as a member of a dynamic group.`, async () => { + const ownedByAdmins = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + groups: ["Devs"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdmins.data.createAllThree).toBeTruthy(); + + const fetchOwnedByAdminsAsAdmin = await GRAPHQL_CLIENT_2.query( + ` + query { + getAllThree(id: "${ownedByAdmins.data.createAllThree.id}") { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(fetchOwnedByAdminsAsAdmin.data.getAllThree).toBeTruthy(); + + const fetchOwnedByAdminsAsNonAdmin = await GRAPHQL_CLIENT_3.query( + ` + query { + getAllThree(id: "${ownedByAdmins.data.createAllThree.id}") { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(fetchOwnedByAdminsAsNonAdmin.errors.length).toEqual(1); + expect((fetchOwnedByAdminsAsNonAdmin.errors[0] as any).errorType).toEqual('Unauthorized'); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByAdmins.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedByAdmins.data.createAllThree.id); + }); + + test(`Test getAllThree as a member of the alternative group.`, async () => { + const ownedByAdmins = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdmins.data.createAllThree).toBeTruthy(); + + const fetchOwnedByAdminsAsAdmin = await GRAPHQL_CLIENT_2.query( + ` + query { + getAllThree(id: "${ownedByAdmins.data.createAllThree.id}") { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(fetchOwnedByAdminsAsAdmin.data.getAllThree).toBeTruthy(); + + const fetchOwnedByAdminsAsNonAdmin = await GRAPHQL_CLIENT_3.query( + ` + query { + getAllThree(id: "${ownedByAdmins.data.createAllThree.id}") { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(fetchOwnedByAdminsAsNonAdmin.errors.length).toEqual(1); + expect((fetchOwnedByAdminsAsNonAdmin.errors[0] as any).errorType).toEqual('Unauthorized'); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByAdmins.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedByAdmins.data.createAllThree.id); + }); + + /** + * List Query Tests + */ + + test(`Test listAllThrees as admin.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: "user2@test.com" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + + const fetchOwnedBy2AsAdmin = await GRAPHQL_CLIENT_1.query( + ` + query { + listAllThrees { + items { + id + owner + editors + groups + alternativeGroup + } + } + } + `, + {}, + ); + expect(fetchOwnedBy2AsAdmin.data.listAllThrees.items).toHaveLength(1); + expect(fetchOwnedBy2AsAdmin.data.listAllThrees.items[0].id).toEqual(ownedBy2.data.createAllThree.id); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test listAllThrees as owner.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: "user2@test.com" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + + const fetchOwnedBy2AsOwner = await GRAPHQL_CLIENT_2.query( + ` + query { + listAllThrees { + items { + id + owner + editors + groups + alternativeGroup + } + } + } + `, + {}, + ); + expect(fetchOwnedBy2AsOwner.data.listAllThrees.items).toHaveLength(1); + expect(fetchOwnedBy2AsOwner.data.listAllThrees.items[0].id).toEqual(ownedBy2.data.createAllThree.id); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test listAllThrees as one of a set of editors.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + editors: ["user2@test.com"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + + const fetchOwnedBy2AsEditor = await GRAPHQL_CLIENT_2.query( + ` + query { + listAllThrees { + items { + id + owner + editors + groups + alternativeGroup + } + } + } + `, + {}, + ); + expect(fetchOwnedBy2AsEditor.data.listAllThrees.items).toHaveLength(1); + expect(fetchOwnedBy2AsEditor.data.listAllThrees.items[0].id).toEqual(ownedBy2.data.createAllThree.id); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test listAllThrees as a member of a dynamic group.`, async () => { + const ownedByAdmins = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + groups: ["Devs"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdmins.data.createAllThree).toBeTruthy(); + + const fetchOwnedByAdminsAsAdmin = await GRAPHQL_CLIENT_2.query( + ` + query { + listAllThrees { + items { + id + owner + editors + groups + alternativeGroup + } + } + } + `, + {}, + ); + expect(fetchOwnedByAdminsAsAdmin.data.listAllThrees.items).toHaveLength(1); + expect(fetchOwnedByAdminsAsAdmin.data.listAllThrees.items[0].id).toEqual(ownedByAdmins.data.createAllThree.id); + + const fetchOwnedByAdminsAsNonAdmin = await GRAPHQL_CLIENT_3.query( + ` + query { + listAllThrees { + items { + id + owner + editors + groups + alternativeGroup + } + } + } + `, + {}, + ); + expect(fetchOwnedByAdminsAsNonAdmin.data.listAllThrees.items).toHaveLength(0); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByAdmins.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedByAdmins.data.createAllThree.id); + }); + + test(`Test getAllThree as a member of the alternative group.`, async () => { + const ownedByAdmins = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdmins.data.createAllThree).toBeTruthy(); + + const fetchOwnedByAdminsAsAdmin = await GRAPHQL_CLIENT_2.query( + ` + query { + listAllThrees { + items { + id + owner + editors + groups + alternativeGroup + } + } + } + `, + {}, + ); + expect(fetchOwnedByAdminsAsAdmin.data.listAllThrees.items).toHaveLength(1); + expect(fetchOwnedByAdminsAsAdmin.data.listAllThrees.items[0].id).toEqual(ownedByAdmins.data.createAllThree.id); + + const fetchOwnedByAdminsAsNonAdmin = await GRAPHQL_CLIENT_3.query( + ` + query { + listAllThrees { + items { + id + owner + editors + groups + alternativeGroup + } + } + } + `, + {}, + ); + expect(fetchOwnedByAdminsAsNonAdmin.data.listAllThrees.items).toHaveLength(0); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByAdmins.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedByAdmins.data.createAllThree.id); + }); + + /** + * Create Mutation Tests + */ + + test(`Test createAllThree as admin.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: "user2@test.com" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + // set by input + expect(ownedBy2.data.createAllThree.owner).toEqual('user2@test.com'); + // not auto filled as the admin condition was met + expect(ownedBy2.data.createAllThree.editors).toBeNull(); + expect(ownedBy2.data.createAllThree.groups).toBeNull(); + expect(ownedBy2.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedBy2NoEditors = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: "user2@test.com", + editors: [] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2NoEditors.data.createAllThree).toBeTruthy(); + // set by input + expect(ownedBy2NoEditors.data.createAllThree.owner).toEqual('user2@test.com'); + // set by input + expect(ownedBy2NoEditors.data.createAllThree.editors).toHaveLength(0); + expect(ownedBy2NoEditors.data.createAllThree.groups).toBeNull(); + expect(ownedBy2NoEditors.data.createAllThree.alternativeGroup).toBeNull(); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + + const deleteReq2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2NoEditors.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq2.data.deleteAllThree.id).toEqual(ownedBy2NoEditors.data.createAllThree.id); + }); + + test(`Test createAllThree as owner.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + owner: "user2@test.com", + editors: [] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + expect(ownedBy2.data.createAllThree.owner).toEqual('user2@test.com'); + expect(ownedBy2.data.createAllThree.editors).toHaveLength(0); + expect(ownedBy2.data.createAllThree.groups).toBeNull(); + expect(ownedBy2.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedBy1 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + owner: "user1@test.com", + editors: [] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy1.errors.length).toEqual(1); + expect((ownedBy1.errors[0] as any).errorType).toEqual('Unauthorized'); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test createAllThree as one of a set of editors.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: ["user2@test.com"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + expect(ownedBy2.data.createAllThree.owner).toBeNull(); + expect(ownedBy2.data.createAllThree.editors[0]).toEqual('user2@test.com'); + expect(ownedBy2.data.createAllThree.groups).toBeNull(); + expect(ownedBy2.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedBy2WithDefaultOwner = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + editors: ["user2@test.com"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2WithDefaultOwner.data.createAllThree).toBeTruthy(); + expect(ownedBy2WithDefaultOwner.data.createAllThree.owner).toEqual('user2@test.com'); + expect(ownedBy2WithDefaultOwner.data.createAllThree.editors[0]).toEqual('user2@test.com'); + expect(ownedBy2WithDefaultOwner.data.createAllThree.groups).toBeNull(); + expect(ownedBy2WithDefaultOwner.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedByEditorsUnauthed = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: ["user1@test.com"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByEditorsUnauthed.errors.length).toEqual(1); + expect((ownedByEditorsUnauthed.errors[0] as any).errorType).toEqual('Unauthorized'); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + + const deleteReq2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2WithDefaultOwner.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq2.data.deleteAllThree.id).toEqual(ownedBy2WithDefaultOwner.data.createAllThree.id); + }); + + test(`Test createAllThree as a member of a dynamic group.`, async () => { + const ownedByDevs = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: [], + groups: ["Devs"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByDevs.data.createAllThree).toBeTruthy(); + expect(ownedByDevs.data.createAllThree.owner).toBeNull(); + expect(ownedByDevs.data.createAllThree.editors).toHaveLength(0); + expect(ownedByDevs.data.createAllThree.groups[0]).toEqual('Devs'); + expect(ownedByDevs.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedByAdminsUnauthed = await GRAPHQL_CLIENT_3.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: [], + groups: ["Devs"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdminsUnauthed.errors.length).toEqual(1); + expect((ownedByAdminsUnauthed.errors[0] as any).errorType).toEqual('Unauthorized'); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByDevs.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedByDevs.data.createAllThree.id); + }); + + test(`Test createAllThree as a member of the alternative group.`, async () => { + const ownedByAdmins = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: [], + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdmins.data.createAllThree).toBeTruthy(); + expect(ownedByAdmins.data.createAllThree.owner).toBeNull(); + expect(ownedByAdmins.data.createAllThree.editors).toHaveLength(0); + expect(ownedByAdmins.data.createAllThree.alternativeGroup).toEqual('Devs'); + + const ownedByAdminsUnauthed = await GRAPHQL_CLIENT_3.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: [], + alternativeGroup: "Admin" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdminsUnauthed.errors.length).toEqual(1); + expect((ownedByAdminsUnauthed.errors[0] as any).errorType).toEqual('Unauthorized'); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByAdmins.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedByAdmins.data.createAllThree.id); + }); + + /** + * Update Mutation Tests + */ + + test(`Test updateAllThree and deleteAllThree as admin.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + editors: [] + owner: "user2@test.com" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + // set by input + expect(ownedBy2.data.createAllThree.owner).toEqual('user2@test.com'); + // auto filled as logged in user. + expect(ownedBy2.data.createAllThree.editors).toHaveLength(0); + expect(ownedBy2.data.createAllThree.groups).toBeNull(); + expect(ownedBy2.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedByTwoUpdate = await GRAPHQL_CLIENT_1.query( + ` + mutation { + updateAllThree(input: { + id: "${ownedBy2.data.createAllThree.id}", + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByTwoUpdate.data.updateAllThree).toBeTruthy(); + // set by input + expect(ownedByTwoUpdate.data.updateAllThree.owner).toEqual('user2@test.com'); + // set by input + expect(ownedByTwoUpdate.data.updateAllThree.editors).toHaveLength(0); + expect(ownedByTwoUpdate.data.updateAllThree.groups).toBeNull(); + expect(ownedByTwoUpdate.data.updateAllThree.alternativeGroup).toEqual('Devs'); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test updateAllThree and deleteAllThree as owner.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + owner: "user2@test.com", + editors: [] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + expect(ownedBy2.data.createAllThree.owner).toEqual('user2@test.com'); + expect(ownedBy2.data.createAllThree.editors).toHaveLength(0); + expect(ownedBy2.data.createAllThree.groups).toBeNull(); + expect(ownedBy2.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedBy2Update = await GRAPHQL_CLIENT_2.query( + ` + mutation { + updateAllThree(input: { + id: "${ownedBy2.data.createAllThree.id}", + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2Update.data.updateAllThree).toBeTruthy(); + // set by input + expect(ownedBy2Update.data.updateAllThree.owner).toEqual('user2@test.com'); + // set by input + expect(ownedBy2Update.data.updateAllThree.editors).toHaveLength(0); + expect(ownedBy2Update.data.updateAllThree.groups).toBeNull(); + expect(ownedBy2Update.data.updateAllThree.alternativeGroup).toEqual('Devs'); + + const deleteReq = await GRAPHQL_CLIENT_2.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test updateAllThree and deleteAllThree as one of a set of editors.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: ["user2@test.com"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedBy2.data.createAllThree).toBeTruthy(); + expect(ownedBy2.data.createAllThree.owner).toBeNull(); + expect(ownedBy2.data.createAllThree.editors[0]).toEqual('user2@test.com'); + expect(ownedBy2.data.createAllThree.groups).toBeNull(); + expect(ownedBy2.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedByUpdate = await GRAPHQL_CLIENT_2.query( + ` + mutation { + updateAllThree(input: { + id: "${ownedBy2.data.createAllThree.id}", + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByUpdate.data.updateAllThree).toBeTruthy(); + // set by input + expect(ownedByUpdate.data.updateAllThree.owner).toBeNull(); + // set by input + expect(ownedByUpdate.data.updateAllThree.editors[0]).toEqual('user2@test.com'); + expect(ownedByUpdate.data.updateAllThree.groups).toBeNull(); + expect(ownedByUpdate.data.updateAllThree.alternativeGroup).toEqual('Devs'); + + const deleteReq = await GRAPHQL_CLIENT_2.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedBy2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedBy2.data.createAllThree.id); + }); + + test(`Test updateAllThree and deleteAllThree as a member of a dynamic group.`, async () => { + const ownedByDevs = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: [], + groups: ["Devs"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByDevs.data.createAllThree).toBeTruthy(); + expect(ownedByDevs.data.createAllThree.owner).toBeNull(); + expect(ownedByDevs.data.createAllThree.editors).toHaveLength(0); + expect(ownedByDevs.data.createAllThree.groups[0]).toEqual('Devs'); + expect(ownedByDevs.data.createAllThree.alternativeGroup).toBeNull(); + + const ownedByUpdate = await GRAPHQL_CLIENT_2.query( + ` + mutation { + updateAllThree(input: { + id: "${ownedByDevs.data.createAllThree.id}", + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByUpdate.data.updateAllThree).toBeTruthy(); + // set by input + expect(ownedByUpdate.data.updateAllThree.owner).toBeNull(); + // set by input + expect(ownedByUpdate.data.updateAllThree.editors).toHaveLength(0); + expect(ownedByUpdate.data.updateAllThree.groups[0]).toEqual('Devs'); + expect(ownedByUpdate.data.updateAllThree.alternativeGroup).toEqual('Devs'); + + const ownedByAdminsUnauthed = await GRAPHQL_CLIENT_3.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: [], + groups: ["Devs"] + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdminsUnauthed.errors.length).toEqual(1); + expect((ownedByAdminsUnauthed.errors[0] as any).errorType).toEqual('Unauthorized'); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByDevs.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedByDevs.data.createAllThree.id); + }); + + test(`Test updateAllThree and deleteAllThree as a member of the alternative group.`, async () => { + const ownedByDevs = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: [], + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByDevs.data.createAllThree).toBeTruthy(); + expect(ownedByDevs.data.createAllThree.owner).toBeNull(); + expect(ownedByDevs.data.createAllThree.editors).toHaveLength(0); + expect(ownedByDevs.data.createAllThree.alternativeGroup).toEqual('Devs'); + + const ownedByUpdate = await GRAPHQL_CLIENT_2.query( + ` + mutation { + updateAllThree(input: { + id: "${ownedByDevs.data.createAllThree.id}", + alternativeGroup: "Admin" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByUpdate.data.updateAllThree).toBeTruthy(); + // set by input + expect(ownedByUpdate.data.updateAllThree.owner).toBeNull(); + // set by input + expect(ownedByUpdate.data.updateAllThree.editors).toHaveLength(0); + expect(ownedByUpdate.data.updateAllThree.groups).toBeNull(); + expect(ownedByUpdate.data.updateAllThree.alternativeGroup).toEqual('Admin'); + + const ownedByAdminsUnauthed = await GRAPHQL_CLIENT_2.query( + ` + mutation { + updateAllThree(input: { + id: "${ownedByDevs.data.createAllThree.id}", + alternativeGroup: "Dev" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByAdminsUnauthed.errors.length).toEqual(1); + expect((ownedByAdminsUnauthed.errors[0] as any).data).toBeNull(); + expect((ownedByAdminsUnauthed.errors[0] as any).errorType).toEqual('Unauthorized'); + + const ownedByDevs2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createAllThree(input: { + owner: null, + editors: [], + alternativeGroup: "Devs" + }) { + id + owner + editors + groups + alternativeGroup + } + } + `, + {}, + ); + expect(ownedByDevs2.data.createAllThree).toBeTruthy(); + expect(ownedByDevs2.data.createAllThree.owner).toBeNull(); + expect(ownedByDevs2.data.createAllThree.editors).toHaveLength(0); + expect(ownedByDevs2.data.createAllThree.alternativeGroup).toEqual('Devs'); + + const deleteReq2 = await GRAPHQL_CLIENT_2.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByDevs2.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq2.data.deleteAllThree.id).toEqual(ownedByDevs2.data.createAllThree.id); + + const deleteReq = await GRAPHQL_CLIENT_1.query( + ` + mutation { + deleteAllThree(input: { id: "${ownedByDevs.data.createAllThree.id}" }) { + id + } + } + `, + {}, + ); + expect(deleteReq.data.deleteAllThree.id).toEqual(ownedByDevs.data.createAllThree.id); + }); + + test(`Test createTestIdentity as admin.`, async () => { + const ownedBy2 = await GRAPHQL_CLIENT_1.query( + ` + mutation { + createTestIdentity(input: { + title: "Test title" + }) { + id + title + owner + } + } + `, + {}, + ); + expect(ownedBy2.data.createTestIdentity).toBeTruthy(); + expect(ownedBy2.data.createTestIdentity.title).toEqual('Test title'); + expect(ownedBy2.data.createTestIdentity.owner.slice(0, 19)).toEqual('https://cognito-idp'); + + // user 2 should be able to update because they share the same issuer. + const update = await GRAPHQL_CLIENT_3.query( + ` + mutation { + updateTestIdentity(input: { + id: "${ownedBy2.data.createTestIdentity.id}", + title: "Test title update" + }) { + id + title + owner + } + } + `, + {}, + ); + expect(update.data.updateTestIdentity).toBeTruthy(); + expect(update.data.updateTestIdentity.title).toEqual('Test title update'); + expect(update.data.updateTestIdentity.owner.slice(0, 19)).toEqual('https://cognito-idp'); + + // user 2 should be able to get because they share the same issuer. + const getReq = await GRAPHQL_CLIENT_3.query( + ` + query { + getTestIdentity(id: "${ownedBy2.data.createTestIdentity.id}") { + id + title + owner + } + } + `, + {}, + ); + expect(getReq.data.getTestIdentity).toBeTruthy(); + expect(getReq.data.getTestIdentity.title).toEqual('Test title update'); + expect(getReq.data.getTestIdentity.owner.slice(0, 19)).toEqual('https://cognito-idp'); + + const listResponse = await GRAPHQL_CLIENT_3.query( + `query { + listTestIdentities(filter: { title: { eq: "Test title update" } }, limit: 100) { + items { + id + title + owner + } + } + }`, + {}, + ); + const relevantPost = listResponse.data.listTestIdentities.items.find(p => p.id === getReq.data.getTestIdentity.id); + expect(relevantPost).toBeTruthy(); + expect(relevantPost.title).toEqual('Test title update'); + expect(relevantPost.owner.slice(0, 19)).toEqual('https://cognito-idp'); + + // user 2 should be able to delete because they share the same issuer. + const delReq = await GRAPHQL_CLIENT_3.query( + ` + mutation { + deleteTestIdentity(input: { + id: "${ownedBy2.data.createTestIdentity.id}" + }) { + id + title + owner + } + } + `, + {}, + ); + expect(delReq.data.deleteTestIdentity).toBeTruthy(); + expect(delReq.data.deleteTestIdentity.title).toEqual('Test title update'); + expect(delReq.data.deleteTestIdentity.owner.slice(0, 19)).toEqual('https://cognito-idp'); + }); + + /** + * Test 'operations' argument + */ + test("Test get and list with 'read' operation set", async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createNoOwner: createOwnerReadProtected(input: { id: "1", sk: "1", content: "Hello, World! - No Owner" }) { + id + content + owner + } + createOwnerReadProtected(input: { id: "1", sk: "2", content: "Hello, World!", owner: "${USERNAME2}" }) { + id + content + owner + } + createNoOwner2: createOwnerReadProtected(input: { id: "1", sk: "3", content: "Hello, World! - No Owner 2" }) { + id + content + owner + } + }`, + {}, + ); + + expect(response.data.createOwnerReadProtected.id).toBeDefined(); + expect(response.data.createOwnerReadProtected.content).toEqual('Hello, World!'); + expect(response.data.createOwnerReadProtected.owner).toEqual(USERNAME2); + + const response2 = await GRAPHQL_CLIENT_3.query( + `query { + getOwnerReadProtected(id: "${response.data.createOwnerReadProtected.id}", sk:"2") { + id content owner + } + }`, + {}, + ); + expect(response2.data.getOwnerReadProtected).toBeNull(); + expect(response2.errors).toHaveLength(1); + + const response3 = await GRAPHQL_CLIENT_2.query( + `query { + getOwnerReadProtected(id: "${response.data.createOwnerReadProtected.id}", sk:"2") { + id content owner + } + }`, + {}, + ); + expect(response3.data.getOwnerReadProtected.id).toBeDefined(); + expect(response3.data.getOwnerReadProtected.content).toEqual('Hello, World!'); + expect(response3.data.getOwnerReadProtected.owner).toEqual(USERNAME2); + + const response4 = await GRAPHQL_CLIENT_2.query( + `query { + listOwnerReadProtecteds(id: "1") { + items { + id content owner + } + } + }`, + {}, + ); + expect(response4.data.listOwnerReadProtecteds.items.length).toEqual(1); + + const response5 = await GRAPHQL_CLIENT_3.query( + `query { + listOwnerReadProtecteds { + items { + id content owner + } + } + }`, + {}, + ); + expect(response5.data.listOwnerReadProtecteds.items).toHaveLength(0); + }); + + test("Test createOwnerCreateUpdateDeleteProtected with 'create' operation set", async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createOwnerCreateUpdateDeleteProtected(input: { content: "Hello, World!", owner: "${USERNAME1}" }) { + id + content + owner + } + }`, + {}, + ); + expect(response.data.createOwnerCreateUpdateDeleteProtected.id).toBeDefined(); + expect(response.data.createOwnerCreateUpdateDeleteProtected.content).toEqual('Hello, World!'); + expect(response.data.createOwnerCreateUpdateDeleteProtected.owner).toEqual(USERNAME1); + + const response2 = await GRAPHQL_CLIENT_1.query( + `mutation { + createOwnerCreateUpdateDeleteProtected(input: { content: "Hello, World!", owner: "${USERNAME2}" }) { + id + content + owner + } + }`, + {}, + ); + expect(response2.data.createOwnerCreateUpdateDeleteProtected).toBeNull(); + expect(response2.errors).toHaveLength(1); + }); + + test("Test updateOwnerCreateUpdateDeleteProtected with 'update' operation set", async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createOwnerCreateUpdateDeleteProtected(input: { content: "Hello, World!", owner: "${USERNAME1}" }) { + id + content + owner + } + }`, + {}, + ); + expect(response.data.createOwnerCreateUpdateDeleteProtected.id).toBeDefined(); + expect(response.data.createOwnerCreateUpdateDeleteProtected.content).toEqual('Hello, World!'); + expect(response.data.createOwnerCreateUpdateDeleteProtected.owner).toEqual(USERNAME1); + + const response2 = await GRAPHQL_CLIENT_2.query( + `mutation { + updateOwnerCreateUpdateDeleteProtected( + input: { + id: "${response.data.createOwnerCreateUpdateDeleteProtected.id}", + content: "Bye, World!" + } + ) { + id + content + owner + } + }`, + {}, + ); + expect(response2.data.updateOwnerCreateUpdateDeleteProtected).toBeNull(); + expect(response2.errors).toHaveLength(1); + + const response3 = await GRAPHQL_CLIENT_1.query( + `mutation { + updateOwnerCreateUpdateDeleteProtected( + input: { + id: "${response.data.createOwnerCreateUpdateDeleteProtected.id}", + content: "Bye, World!" + } + ) { + id + content + owner + } + }`, + {}, + ); + expect(response3.data.updateOwnerCreateUpdateDeleteProtected.id).toBeDefined(); + expect(response3.data.updateOwnerCreateUpdateDeleteProtected.content).toEqual('Bye, World!'); + expect(response3.data.updateOwnerCreateUpdateDeleteProtected.owner).toEqual(USERNAME1); + }); + + test("Test deleteOwnerCreateUpdateDeleteProtected with 'update' operation set", async () => { + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createOwnerCreateUpdateDeleteProtected(input: { content: "Hello, World!", owner: "${USERNAME1}" }) { + id + content + owner + } + }`, + {}, + ); + expect(response.data.createOwnerCreateUpdateDeleteProtected.id).toBeDefined(); + expect(response.data.createOwnerCreateUpdateDeleteProtected.content).toEqual('Hello, World!'); + expect(response.data.createOwnerCreateUpdateDeleteProtected.owner).toEqual(USERNAME1); + + const response2 = await GRAPHQL_CLIENT_2.query( + `mutation { + deleteOwnerCreateUpdateDeleteProtected( + input: { + id: "${response.data.createOwnerCreateUpdateDeleteProtected.id}" + } + ) { + id + content + owner + } + }`, + {}, + ); + expect(response2.data.deleteOwnerCreateUpdateDeleteProtected).toBeNull(); + expect(response2.errors).toHaveLength(1); + + const response3 = await GRAPHQL_CLIENT_1.query( + `mutation { + deleteOwnerCreateUpdateDeleteProtected( + input: { + id: "${response.data.createOwnerCreateUpdateDeleteProtected.id}" + } + ) { + id + content + owner + } + }`, + {}, + ); + expect(response3.data.deleteOwnerCreateUpdateDeleteProtected.id).toBeDefined(); + expect(response3.data.deleteOwnerCreateUpdateDeleteProtected.content).toEqual('Hello, World!'); + expect(response3.data.deleteOwnerCreateUpdateDeleteProtected.owner).toEqual(USERNAME1); + }); + + test('Test allow private combined with groups as Admin and non-admin users', async () => { + const create = `mutation { + p1: createPerformance(input: { + id: "P1" + performer: "Perf #1" + description: "Description" + time: "2019-11-11T00:00:00Z" + performanceStageId: "S1" + }) { + id + } + + p2: createPerformance(input: { + id: "P2" + performer: "Perf #2" + description: "Description" + time: "2019-11-11T00:00:00Z" + performanceStageId: "S1" + }) { + id + } + + s1: createStage(input: { + id: "S1" + name: "Stage #1" + }) { + id + } + } + `; + + // Create as non-admin user, should fail + const response1 = await GRAPHQL_CLIENT_3.query(create, {}); + + expect(response1.data.p1).toBeNull(); + expect(response1.data.p2).toBeNull(); + expect(response1.data.s1).toBeNull(); + expect(response1.errors.length).toEqual(3); + expect((response1.errors[0] as any).errorType).toEqual('Unauthorized'); + expect((response1.errors[1] as any).errorType).toEqual('Unauthorized'); + expect((response1.errors[2] as any).errorType).toEqual('Unauthorized'); + + // Create as Admin, should succeed + const response2 = await GRAPHQL_CLIENT_1.query(create, {}); + + expect(response2.data.p1.id).toEqual('P1'); + expect(response2.data.p2.id).toEqual('P2'); + expect(response2.data.s1.id).toEqual('S1'); + + const update = `mutation { + updatePerformance(input: { + id: "P1" + performer: "Best Perf #1" + }) { + id + performer + } + } + `; + + // Update as non-admin user, should fail + const response3 = await GRAPHQL_CLIENT_3.query(update, {}); + + expect(response3.data.updatePerformance).toBeNull(); + expect(response3.errors.length).toEqual(1); + expect((response3.errors[0] as any).errorType).toEqual('Unauthorized'); + + // Update as Admin, should succeed + const response4 = await GRAPHQL_CLIENT_1.query(update, {}); + + expect(response4.data.updatePerformance.id).toEqual('P1'); + expect(response4.data.updatePerformance.performer).toEqual('Best Perf #1'); + + // List as non-admin and Admin user as well, should succeed + const list = `query List { + listPerformances { + items { + id + performer + description + time + stage { + name + } + } + } + } + `; + + const response5 = await GRAPHQL_CLIENT_3.query(list, {}); + + expect(response5.data.listPerformances).toBeDefined(); + expect(response5.data.listPerformances.items).toBeDefined(); + expect(response5.data.listPerformances.items.length).toEqual(2); + expect(response5.data.listPerformances.items[0].id).toEqual('P2'); + expect(response5.data.listPerformances.items[0].performer).toEqual('Perf #2'); + expect(response5.data.listPerformances.items[0].stage).toBeDefined(); + expect(response5.data.listPerformances.items[0].stage.name).toEqual('Stage #1'); + expect(response5.data.listPerformances.items[1].id).toEqual('P1'); + expect(response5.data.listPerformances.items[1].performer).toEqual('Best Perf #1'); + expect(response5.data.listPerformances.items[1].stage).toBeDefined(); + expect(response5.data.listPerformances.items[1].stage.name).toEqual('Stage #1'); + + const response6 = await GRAPHQL_CLIENT_1.query(list, {}); + + expect(response6.data.listPerformances).toBeDefined(); + expect(response6.data.listPerformances.items).toBeDefined(); + expect(response6.data.listPerformances.items.length).toEqual(2); + expect(response6.data.listPerformances.items[0].id).toEqual('P2'); + expect(response6.data.listPerformances.items[0].performer).toEqual('Perf #2'); + expect(response6.data.listPerformances.items[0].stage).toBeDefined(); + expect(response6.data.listPerformances.items[0].stage.name).toEqual('Stage #1'); + expect(response6.data.listPerformances.items[1].id).toEqual('P1'); + expect(response6.data.listPerformances.items[1].performer).toEqual('Best Perf #1'); + expect(response6.data.listPerformances.items[1].stage.name).toEqual('Stage #1'); + expect(response6.data.listPerformances.items[1].stage).toBeDefined(); + + // Get as non-admin and Admin user as well, should succeed + const get = `query Get { + getPerformance(id: "P1") { + id + performer + description + time + stage { + name + } + } + } + `; + + const response7 = await GRAPHQL_CLIENT_3.query(get, {}); + + expect(response7.data.getPerformance).toBeDefined(); + expect(response7.data.getPerformance.id).toEqual('P1'); + expect(response7.data.getPerformance.performer).toEqual('Best Perf #1'); + expect(response7.data.getPerformance.stage).toBeDefined(); + expect(response7.data.getPerformance.stage.name).toEqual('Stage #1'); + + const response8 = await GRAPHQL_CLIENT_1.query(get, {}); + + expect(response8.data.getPerformance).toBeDefined(); + expect(response8.data.getPerformance.id).toEqual('P1'); + expect(response8.data.getPerformance.performer).toEqual('Best Perf #1'); + expect(response8.data.getPerformance.stage).toBeDefined(); + expect(response8.data.getPerformance.stage.name).toEqual('Stage #1'); + + const deleteMutation = `mutation { + deletePerformance(input: { + id: "P1" + }) { + id + } + } + `; + + // Delete as non-admin user, should fail + const response9 = await GRAPHQL_CLIENT_3.query(deleteMutation, {}); + + expect(response9.data.deletePerformance).toBeNull(); + expect(response9.errors.length).toEqual(1); + expect((response9.errors[0] as any).errorType).toEqual('Unauthorized'); + + // Delete as Admin, should succeed + const response10 = await GRAPHQL_CLIENT_1.query(deleteMutation, {}); + + expect(response10.data.deletePerformance).toBeDefined(); + expect(response10.data.deletePerformance.id).toEqual('P1'); + }); + + test('Test authorized user can get Performance with no created stage', async () => { + const createPerf = `mutation { + create: createPerformance(input: { + id: "P3" + performer: "Perf #3" + description: "desc" + time: "2021-11-11T00:00:00Z" + }) { + id + performer + description + time + createdAt + updatedAt + } + }`; + + const getPerf = `query { + g1: getPerformance(id: "P3") { + id + performer + description + time + stage { + id + name + createdAt + updatedAt + } + createdAt + updatedAt + } + } + `; + + const createStage = `mutation { + createStage(input: {name: "stage3", id: "003"}) { + createdAt + id + name + updatedAt + } + }`; + + const updatePerf = `mutation { + u1: updatePerformance(input: {id: "P3", performanceStageId: "003"}) { + createdAt + description + id + performer + time + updatedAt + stage { + id + name + } + } + }`; + + // first make a query to the record 'P3' + const response1 = await GRAPHQL_CLIENT_1.query(getPerf, {}); + expect(response1).toBeDefined(); + expect(response1.data.g1).toBeNull(); + + // create performance and expect stage to be null + await GRAPHQL_CLIENT_1.query(createPerf, {}); + const response2 = await GRAPHQL_CLIENT_1.query(getPerf, {}); + expect(response2).toBeDefined(); + expect(response2.data.g1).toBeDefined(); + expect(response2.data.g1.id).toEqual('P3'); + expect(response2.data.g1.description).toEqual('desc'); + expect(response2.data.g1.stage).toBeNull(); + + //create stage and then add it to perf should show stage in perf + await GRAPHQL_CLIENT_1.query(createStage, {}); + const response3 = await GRAPHQL_CLIENT_1.query(updatePerf, {}); + expect(response3).toBeDefined(); + expect(response3.data.u1).toBeDefined(); + expect(response3.data.u1.id).toEqual('P3'); + expect(response3.data.u1.stage).toMatchObject({ + id: '003', + name: 'stage3', + }); + }); +}); diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/DefaultValueTransformer.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/DefaultValueTransformer.e2e.test.ts index 5c212a0a084..3606926f13c 100644 --- a/packages/graphql-transformers-e2e-tests/src/__tests__/DefaultValueTransformer.e2e.test.ts +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/DefaultValueTransformer.e2e.test.ts @@ -65,6 +65,7 @@ beforeAll(async () => { const transformer = new GraphQLTransform({ transformers: [new ModelTransformer(), new DefaultValueTransformer()], + sandboxModeEnabled: true, }); const out = transformer.transform(validSchema); const finishedStack = await deploy( @@ -95,7 +96,7 @@ afterAll(async () => { await cleanupStackAfterTest(BUCKET_NAME, STACK_NAME, cf); }); -test('Test next token with key', async () => { +test('Default value directive', async () => { await createPost(); const posts = await listPosts(); diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/HttpTransformerV2.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/HttpTransformerV2.e2e.test.ts new file mode 100644 index 00000000000..774beb25825 --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/HttpTransformerV2.e2e.test.ts @@ -0,0 +1,443 @@ +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { HttpTransformer } from '@aws-amplify/graphql-http-transformer'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { GraphQLClient } from '../GraphQLClient'; +import { default as moment } from 'moment'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { S3Client } from '../S3Client'; +import { default as S3 } from 'aws-sdk/clients/s3'; +import { deployJsonServer, destroyJsonServer } from '../cdkUtils'; + +jest.setTimeout(2000000); + +const cf = new CloudFormationClient('us-west-2'); +const customS3Client = new S3Client('us-west-2'); +const awsS3Client = new S3({ region: 'us-west-2' }); +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `HttpTransformerV2Test-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `appsync-http-transformer-v2-test-bucket-${BUILD_TIMESTAMP}`; +const LOCAL_FS_BUILD_DIR = '/tmp/http_transformer_v2_tests/'; +const S3_ROOT_DIR_KEY = 'deployments'; + +let GRAPHQL_CLIENT = undefined; + +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} + +beforeAll(async () => { + const { apiUrl } = deployJsonServer(); + + const validSchema = ` + type Comment @model { + id: ID! + title: String + simpleGet: CompObj @http(method: GET, url: "${apiUrl}posts/1") + simpleGet2: CompObj @http(url: "${apiUrl}posts/2") + complexPost( + id: Int, + title: String!, + body: String, + userId: Int + ): CompObj @http(method: POST, url: "${apiUrl}posts") + complexPut( + id: Int!, + title: String, + body: String, + userId: Int + ): CompObj @http(method: PUT, url: "${apiUrl}posts/:id") + deleter: String @http(method: DELETE, url: "${apiUrl}posts/4") + complexGet( + data: String!, + userId: Int!, + _limit: Int + ): [CompObj] @http(url: "${apiUrl}:data") + complexGet2( + dataType: String!, + postId: Int!, + secondType: String!, + id: Int + ): [PostComment] @http(url: "${apiUrl}:dataType/:postId/:secondType") + } + type CompObj { + userId: Int + id: Int + title: String + body: String + } + type PostComment { + postId: Int + id: Int + name: String + email: String + body: String + } + `; + + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + console.error(`Failed to create bucket: ${e}`); + } + + const transformer = new GraphQLTransform({ + transformers: [new ModelTransformer(), new HttpTransformer()], + sandboxModeEnabled: true, + }); + + const out = transformer.transform(validSchema); + + try { + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + {}, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + + // Arbitrary wait to make sure everything is ready. + await cf.wait(5, () => Promise.resolve()); + + expect(finishedStack).toBeDefined(); + + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const getApiKey = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIApiKeyOutput); + const endpoint = getApiEndpoint(finishedStack.Outputs); + const apiKey = getApiKey(finishedStack.Outputs); + + expect(apiKey).toBeDefined(); + expect(endpoint).toBeDefined(); + + GRAPHQL_CLIENT = new GraphQLClient(endpoint, { 'x-api-key': apiKey }); + } catch (e) { + console.error(e); + expect(true).toEqual(false); + } +}); + +afterAll(async () => { + destroyJsonServer(); + + await cleanupStackAfterTest(BUCKET_NAME, STACK_NAME, cf); +}); + +/** + * Test queries below + */ +test('Test HTTP GET request', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + simpleGet { + id + title + body + } + } + }`, + {}, + ); + + const post1Title = 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit'; + + expect(response.errors).toBeUndefined(); + expect(response.data.createComment.id).toBeDefined(); + expect(response.data.createComment.title).toEqual('Hello, World!'); + expect(response.data.createComment.simpleGet).toBeDefined(); + expect(response.data.createComment.simpleGet.title).toEqual(post1Title); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); + +test('Test HTTP GET request 2', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + simpleGet2 { + id + title + body + } + } + }`, + {}, + ); + + const post2Title = 'qui est esse'; + + expect(response.errors).toBeUndefined(); + expect(response.data.createComment.id).toBeDefined(); + expect(response.data.createComment.title).toEqual('Hello, World!'); + expect(response.data.createComment.simpleGet2).toBeDefined(); + expect(response.data.createComment.simpleGet2.title).toEqual(post2Title); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); + +test('Test HTTP POST request', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + complexPost( + body: { + title: "foo", + body: "bar", + userId: 2 + } + ) { + id + title + body + userId + } + } + }`, + {}, + ); + + expect(response.errors).toBeUndefined(); + expect(response.data.createComment.id).toBeDefined(); + expect(response.data.createComment.title).toEqual('Hello, World!'); + expect(response.data.createComment.complexPost).toBeDefined(); + expect(response.data.createComment.complexPost.title).toEqual('foo'); + expect(response.data.createComment.complexPost.userId).toEqual(2); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); + +test('Test HTTP PUT request', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + complexPut( + params: { + id: "3" + }, + body: { + title: "foo", + body: "bar", + userId: 2 + } + ) { + id + title + body + userId + } + } + }`, + {}, + ); + + expect(response.errors).toBeUndefined(); + expect(response.data.createComment.id).toBeDefined(); + expect(response.data.createComment.title).toEqual('Hello, World!'); + expect(response.data.createComment.complexPut).toBeDefined(); + expect(response.data.createComment.complexPut.title).toEqual('foo'); + expect(response.data.createComment.complexPut.userId).toEqual(2); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); + +test('Test HTTP DELETE request', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + deleter + } + }`, + {}, + ); + + expect(response.errors).toBeUndefined(); + expect(response.data.createComment.id).toBeDefined(); + expect(response.data.createComment.title).toEqual('Hello, World!'); + expect(response.data.createComment.deleter).not.toBeNull(); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); + +test('Test GET with URL param and query values', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + complexGet( + params: { + data: "posts" + }, + query: { + userId: 1, + _limit: 7 + } + ) { + id + title + body + } + } + }`, + {}, + ); + + expect(response.errors).toBeUndefined(); + expect(response.data.createComment.id).toBeDefined(); + expect(response.data.createComment.title).toEqual('Hello, World!'); + expect(response.data.createComment.complexGet).toBeDefined(); + expect(response.data.createComment.complexGet.length).toEqual(7); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); + +test('Test GET with multiple URL params and query values', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + complexGet2( + params: { + dataType: "posts", + postId: "1", + secondType: "comments" + }, + query: { + id: 2 + } + ) { + id + name + email + } + } + }`, + {}, + ); + + expect(response.errors).toBeUndefined(); + expect(response.data.createComment.id).toBeDefined(); + expect(response.data.createComment.title).toEqual('Hello, World!'); + expect(response.data.createComment.complexGet2).toBeDefined(); + expect(response.data.createComment.complexGet2[0].email).toEqual('Jayne_Kuhic@sydney.com'); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); + +test('Test that GET errors when missing a required Query input object', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + complexGet( + params: { + data: "posts", + } + ) { + id + title + body + } + } + }`, + {}, + ); + + expect(response.data).toBeNull(); + expect(response.errors).toBeDefined(); + expect(response.errors).toHaveLength(1); + expect(response.errors[0].message).toEqual( + "Validation error of type MissingFieldArgument: Missing field argument query @ 'createComment/complexGet'", + ); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); + +test('Test that POST errors when missing a non-null arg in query/body', async () => { + try { + const response = await GRAPHQL_CLIENT.query( + `mutation { + createComment(input: { title: "Hello, World!" }) { + id + title + complexPost( + body: { + id: 1, + body: "bar" + } + ) { + id + title + body + } + } + }`, + {}, + ); + + expect(response.data.createComment.complexPost).toBeNull(); + expect(response.errors).toBeDefined(); + expect(response.errors).toHaveLength(1); + expect(response.errors[0].message).toEqual( + 'An argument you marked as Non-Null is not present in the query nor the body of your request.', + ); + } catch (e) { + console.error(e); + // fail + expect(e).toBeUndefined(); + } +}); diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/IndexTransformer.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/IndexTransformer.e2e.test.ts index d67eb917e80..e7c68dc903a 100644 --- a/packages/graphql-transformers-e2e-tests/src/__tests__/IndexTransformer.e2e.test.ts +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/IndexTransformer.e2e.test.ts @@ -100,6 +100,7 @@ beforeAll(async () => { const transformer = new GraphQLTransform({ featureFlags, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new IndexTransformer()], + sandboxModeEnabled: true, }); const out = transformer.transform(validSchema); const finishedStack = await deploy( diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/IndexWithAuthV2.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/IndexWithAuthV2.e2e.test.ts new file mode 100644 index 00000000000..6480ac22034 --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/IndexWithAuthV2.e2e.test.ts @@ -0,0 +1,292 @@ +import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { GraphQLClient } from '../GraphQLClient'; +import { default as moment } from 'moment'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { S3Client } from '../S3Client'; +import { S3, CognitoIdentityServiceProvider as CognitoClient } from 'aws-sdk'; +import { + addUserToGroup, + authenticateUser, + configureAmplify, + createGroup, + createUserPool, + createUserPoolClient, + signupUser, +} from '../cognitoUtils'; + +jest.setTimeout(2000000); + +const AWS_REGION = 'us-west-2'; + +const cf = new CloudFormationClient(AWS_REGION); +const customS3Client = new S3Client(AWS_REGION); +const awsS3Client = new S3({ region: AWS_REGION }); +const cognitoClient = new CognitoClient({ apiVersion: '2016-04-19', region: AWS_REGION }); +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `IndexAuthTransformerTests-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `appsync-auth-index-transformer-test-bucket-${BUILD_TIMESTAMP}`; +const LOCAL_FS_BUILD_DIR = '/tmp/index_with_auth_transformer_tests/'; +const S3_ROOT_DIR_KEY = 'deployments'; + +let GRAPHQL_ENDPOINT = undefined; + +/** + * Client 1 is logged in and is a member of the Admin group. + */ +let GRAPHQL_CLIENT_1: GraphQLClient = undefined; + +/** + * Client 2 is logged in and is a member of the Devs group. + */ +let GRAPHQL_CLIENT_2: GraphQLClient = undefined; + +/** + * Client 3 is logged in and has no group memberships. + */ +let GRAPHQL_CLIENT_3: GraphQLClient = undefined; + +let USER_POOL_ID = undefined; + +const USERNAME1 = 'user1@test.com'; +const USERNAME2 = 'user2@test.com'; +const USERNAME3 = 'user3@test.com'; +const TMP_PASSWORD = 'Password123!'; +const REAL_PASSWORD = 'Password1234!'; + +const ADMIN_GROUP_NAME = 'Admin'; +const DEVS_GROUP_NAME = 'Devs'; +const PARTICIPANT_GROUP_NAME = 'Participant'; +const WATCHER_GROUP_NAME = 'Watcher'; + +beforeAll(async () => { + const validSchema = /* GraphQL */ ` + type Order @model @auth(rules: [{ allow: owner, ownerField: "customerEmail" }, { allow: groups, groups: ["Admin"] }]) { + customerEmail: String! @primaryKey(sortKeyFields: ["orderId"]) + createdAt: AWSDateTime + orderId: String! @index(name: "GSI", queryField: "ordersByOrderId") + } + `; + + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + console.warn(`Could not create bucket: ${e}`); + } + const userPoolResponse = await createUserPool(cognitoClient, `UserPool${STACK_NAME}`); + USER_POOL_ID = userPoolResponse.UserPool.Id; + const userPoolClientResponse = await createUserPoolClient(cognitoClient, USER_POOL_ID, `UserPool${STACK_NAME}`); + const userPoolClientId = userPoolClientResponse.UserPoolClient.ClientId; + + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }, + transformers: [ + new ModelTransformer(), + new PrimaryKeyTransformer(), + new IndexTransformer(), + new AuthTransformer({ addAwsIamAuthInOutputSchema: false }), + ], + }); + const out = transformer.transform(validSchema); + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + { AuthCognitoUserPoolId: USER_POOL_ID }, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + // Arbitrary wait to make sure everything is ready. + await cf.wait(5, () => Promise.resolve()); + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const getApiKey = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIApiKeyOutput); + const apiKey = getApiKey(finishedStack.Outputs); + GRAPHQL_ENDPOINT = getApiEndpoint(finishedStack.Outputs); + expect(apiKey).not.toBeTruthy(); + + // Verify we have all the details + expect(GRAPHQL_ENDPOINT).toBeTruthy(); + expect(USER_POOL_ID).toBeTruthy(); + expect(userPoolClientId).toBeTruthy(); + + // Configure Amplify, create users, and sign in. + configureAmplify(USER_POOL_ID, userPoolClientId); + + await signupUser(USER_POOL_ID, USERNAME1, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME2, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME3, TMP_PASSWORD); + await createGroup(USER_POOL_ID, ADMIN_GROUP_NAME); + await createGroup(USER_POOL_ID, PARTICIPANT_GROUP_NAME); + await createGroup(USER_POOL_ID, WATCHER_GROUP_NAME); + await createGroup(USER_POOL_ID, DEVS_GROUP_NAME); + await addUserToGroup(ADMIN_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(PARTICIPANT_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(WATCHER_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(DEVS_GROUP_NAME, USERNAME2, USER_POOL_ID); + const authResAfterGroup: any = await authenticateUser(USERNAME1, TMP_PASSWORD, REAL_PASSWORD); + + const idToken = authResAfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_1 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken }); + + const authRes2AfterGroup: any = await authenticateUser(USERNAME2, TMP_PASSWORD, REAL_PASSWORD); + const idToken2 = authRes2AfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_2 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken2 }); + + const authRes3: any = await authenticateUser(USERNAME3, TMP_PASSWORD, REAL_PASSWORD); + const idToken3 = authRes3.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_3 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken3 }); +}); + +afterAll(async () => { + await cleanupStackAfterTest(BUCKET_NAME, STACK_NAME, cf, { cognitoClient, userPoolId: USER_POOL_ID }); +}); + +/** + * Test queries below + */ + +test('Test createOrder mutation as admin', async () => { + const response = await createOrder(GRAPHQL_CLIENT_1, USERNAME2, 'order1'); + expect(response.data.createOrder.customerEmail).toBeDefined(); + expect(response.data.createOrder.orderId).toEqual('order1'); + expect(response.data.createOrder.createdAt).toBeDefined(); +}); + +test('Test createOrder mutation as owner', async () => { + const response = await createOrder(GRAPHQL_CLIENT_2, USERNAME2, 'order2'); + expect(response.data.createOrder.customerEmail).toBeDefined(); + expect(response.data.createOrder.orderId).toEqual('order2'); + expect(response.data.createOrder.createdAt).toBeDefined(); +}); + +test('Test createOrder mutation as owner', async () => { + const response = await createOrder(GRAPHQL_CLIENT_3, USERNAME2, 'order3'); + expect(response.data.createOrder).toBeNull(); + expect(response.errors).toHaveLength(1); +}); + +test('Test list orders as owner', async () => { + await createOrder(GRAPHQL_CLIENT_3, USERNAME3, 'owned1'); + await createOrder(GRAPHQL_CLIENT_3, USERNAME3, 'owned2'); + const listResponse = await listOrders(GRAPHQL_CLIENT_3, USERNAME3, { beginsWith: 'owned' }); + expect(listResponse.data.listOrders.items).toHaveLength(2); +}); + +test('Test list orders as non owner', async () => { + await createOrder(GRAPHQL_CLIENT_3, USERNAME3, 'unowned1'); + await createOrder(GRAPHQL_CLIENT_3, USERNAME3, 'unowned2'); + const listResponse = await listOrders(GRAPHQL_CLIENT_2, USERNAME3, { beginsWith: 'unowned' }); + expect(listResponse.data.listOrders).toBeNull(); + expect(listResponse.errors).toHaveLength(1); +}); + +test('Test get orders as owner', async () => { + await createOrder(GRAPHQL_CLIENT_2, USERNAME2, 'myobj'); + const getResponse = await getOrder(GRAPHQL_CLIENT_2, USERNAME2, 'myobj'); + expect(getResponse.data.getOrder.orderId).toEqual('myobj'); +}); + +test('Test get orders as non-owner', async () => { + await createOrder(GRAPHQL_CLIENT_2, USERNAME2, 'notmyobj'); + const getResponse = await getOrder(GRAPHQL_CLIENT_3, USERNAME2, 'notmyobj'); + expect(getResponse.data.getOrder).toBeNull(); + expect(getResponse.errors).toHaveLength(1); +}); + +test('Test query orders as owner', async () => { + await createOrder(GRAPHQL_CLIENT_3, USERNAME3, 'ownedby3a'); + const listResponse = await ordersByOrderId(GRAPHQL_CLIENT_3, 'ownedby3a'); + expect(listResponse.data.ordersByOrderId.items).toHaveLength(1); +}); + +test('Test query orders as non owner', async () => { + await createOrder(GRAPHQL_CLIENT_3, USERNAME3, 'notownedby2a'); + const listResponse = await ordersByOrderId(GRAPHQL_CLIENT_2, 'notownedby2a'); + expect(listResponse.data.ordersByOrderId.items).toHaveLength(0); +}); + +// helper functions +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} + +async function createOrder(client: GraphQLClient, customerEmail: string, orderId: string) { + const result = await client.query( + `mutation CreateOrder($input: CreateOrderInput!) { + createOrder(input: $input) { + customerEmail + orderId + createdAt + } + }`, + { + input: { customerEmail, orderId }, + }, + ); + return result; +} + +async function getOrder(client: GraphQLClient, customerEmail: string, orderId: string) { + const result = await client.query( + `query GetOrder($customerEmail: String!, $orderId: String!) { + getOrder(customerEmail: $customerEmail, orderId: $orderId) { + customerEmail + orderId + createdAt + } + }`, + { customerEmail, orderId }, + ); + return result; +} + +async function listOrders(client: GraphQLClient, customerEmail: string, orderId: { beginsWith: string }) { + const result = await client.query( + `query ListOrder($customerEmail: String, $orderId: ModelStringKeyConditionInput) { + listOrders(customerEmail: $customerEmail, orderId: $orderId) { + items { + customerEmail + orderId + createdAt + } + nextToken + } + }`, + { customerEmail, orderId }, + ); + return result; +} + +async function ordersByOrderId(client: GraphQLClient, orderId: string) { + const result = await client.query( + `query OrdersByOrderId($orderId: String!) { + ordersByOrderId(orderId: $orderId) { + items { + customerEmail + orderId + createdAt + } + nextToken + } + }`, + { orderId }, + ); + return result; +} diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/ModelTransformer.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/ModelTransformer.e2e.test.ts index 05b4620a6b0..8b7c892fa84 100644 --- a/packages/graphql-transformers-e2e-tests/src/__tests__/ModelTransformer.e2e.test.ts +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/ModelTransformer.e2e.test.ts @@ -77,9 +77,15 @@ beforeAll(async () => { content: String updatedOn: Int # No automatic generation of timestamp if its not AWSDateTime } + type Todo @model(queries: { list: "getTodoList" }, mutations: { create: "createTodoItem", delete: "removeTodo" }) { + id: ID! + title: String! + description: String + } `; const transformer = new GraphQLTransform({ transformers: [new ModelTransformer()], + sandboxModeEnabled: true, }); const out = transformer.transform(validSchema); @@ -188,7 +194,7 @@ test('Test updateComment mutation with null and empty', async () => { const notRequiredFieldValue = 'thisisnotrequired'; const response = await GRAPHQL_CLIENT.query( /* GraphQL */ ` - mutation($input: CreateRequireInput!) { + mutation ($input: CreateRequireInput!) { createRequire(input: $input) { id requiredField @@ -207,7 +213,7 @@ test('Test updateComment mutation with null and empty', async () => { const id = response.data.createRequire.id; const updateResponse = await GRAPHQL_CLIENT.query( /* GraphQL */ ` - mutation($input: UpdateRequireInput!) { + mutation ($input: UpdateRequireInput!) { updateRequire(input: $input) { id requiredField @@ -225,7 +231,7 @@ test('Test updateComment mutation with null and empty', async () => { expect(updateResponse.data.updateRequire.notRequiredField).toEqual(notRequiredFieldValue); const update2Response = await GRAPHQL_CLIENT.query( /* GraphQL */ ` - mutation($input: UpdateRequireInput!) { + mutation ($input: UpdateRequireInput!) { updateRequire(input: $input) { id requiredField @@ -789,6 +795,73 @@ test('Test updatePost mutation with non-model types', async () => { expect(updateResponse.data.updatePost.appearsIn).toEqual(['NEWHOPE', 'EMPIRE']); }); +test('Test renamed queries and mutations', async () => { + let response = await GRAPHQL_CLIENT.query( + `mutation { + createTodoItem(input: { title: "Hello, World!", description: "Description Text" }) { + id + title + createdAt + updatedAt + description + } + }`, + {}, + ); + expect(response.data.createTodoItem.id).toBeDefined(); + expect(response.data.createTodoItem.title).toEqual('Hello, World!'); + expect(response.data.createTodoItem.description).toEqual('Description Text'); + expect(response.data.createTodoItem.createdAt).toBeDefined(); + expect(response.data.createTodoItem.updatedAt).toBeDefined(); + + const id = response.data.createTodoItem.id; + + response = await GRAPHQL_CLIENT.query( + `query { + getTodoList { + items { + id + title + createdAt + updatedAt + description + } + } + }`, + {}, + ); + + expect(response.data.getTodoList.items).toBeDefined(); + expect(response.data.getTodoList.items.length).toEqual(1); + + response = await GRAPHQL_CLIENT.query( + `mutation { + removeTodo(input: {id: "${id}"}) { + id + } + }`, + {}, + ); + + response = await GRAPHQL_CLIENT.query( + `query { + getTodoList { + items { + id + title + createdAt + updatedAt + description + } + } + }`, + {}, + ); + + expect(response.data.getTodoList.items).toBeDefined(); + expect(response.data.getTodoList.items.length).toEqual(0); +}); + describe('Timestamp configuration', () => { test('Test createdAt is present in the schema', async () => { const response = await GRAPHQL_CLIENT.query( diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/MultiAuthV2Transformer.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/MultiAuthV2Transformer.e2e.test.ts new file mode 100644 index 00000000000..09a8d1d7ba1 --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/MultiAuthV2Transformer.e2e.test.ts @@ -0,0 +1,1064 @@ +import { Auth } from 'aws-amplify'; +import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { HasOneTransformer, HasManyTransformer } from '@aws-amplify/graphql-relational-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { S3Client } from '../S3Client'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { CognitoIdentityServiceProvider as CognitoClient, S3, CognitoIdentity, IAM } from 'aws-sdk'; +import moment from 'moment'; +import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; +import { createUserPool, createIdentityPool, createUserPoolClient, configureAmplify, authenticateUser, signupUser } from '../cognitoUtils'; +import { IAMHelper } from '../IAMHelper'; +import { ResourceConstants } from 'graphql-transformer-common'; +import gql from 'graphql-tag'; +import AWS = require('aws-sdk'); +import 'isomorphic-fetch'; + +// to deal with bug in cognito-identity-js +(global as any).fetch = require('node-fetch'); + +// To overcome of the way of how AmplifyJS picks up currentUserCredentials +const anyAWS = AWS as any; +if (anyAWS && anyAWS.config && anyAWS.config.credentials) { + delete anyAWS.config.credentials; +} + +jest.setTimeout(2000000); + +const AWS_REGION = 'us-west-2'; + +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} + +const cf = new CloudFormationClient(AWS_REGION); +const customS3Client = new S3Client(AWS_REGION); +const cognitoClient = new CognitoClient({ apiVersion: '2016-04-19', region: AWS_REGION }); +const identityClient = new CognitoIdentity({ apiVersion: '2014-06-30', region: AWS_REGION }); +const iamHelper = new IAMHelper(AWS_REGION); +const awsS3Client = new S3({ region: AWS_REGION }); + +// stack info +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `MultiAuthV2TransformerTests-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `appsync-multi-auth-v2-transformer-test-bucket-${BUILD_TIMESTAMP}`; +const LOCAL_FS_BUILD_DIR = '/tmp/multi_authv2_transformer_tests/'; +const S3_ROOT_DIR_KEY = 'deployments'; +const AUTH_ROLE_NAME = `${STACK_NAME}-authRole`; +const UNAUTH_ROLE_NAME = `${STACK_NAME}-unauthRole`; +let USER_POOL_ID: string; +let IDENTITY_POOL_ID: string; +let GRAPHQL_ENDPOINT: string; +let APIKEY_GRAPHQL_CLIENT: AWSAppSyncClient = undefined; +let USER_POOL_AUTH_CLIENT: AWSAppSyncClient = undefined; +let IAM_UNAUTHCLIENT: AWSAppSyncClient = undefined; +let IAM_AUTHCLIENT: AWSAppSyncClient = undefined; + +const USERNAME1 = 'user1@test.com'; +const TMP_PASSWORD = 'Password123!'; +const REAL_PASSWORD = 'Password1234!'; + +beforeAll(async () => { + const validSchema = ` + # Allow anyone to access. This is translated into API_KEY. + type PostPublic @model @auth(rules: [{ allow: public }]) { + id: ID! + title: String + } + + # Allow anyone to access. This is translated to IAM with unauth role. + type PostPublicIAM @model @auth(rules: [{ allow: public, provider: iam }]) { + id: ID! + title: String + } + + # Allow anyone with a valid Amazon Cognito UserPools JWT to access. + type PostPrivate @model @auth(rules: [{ allow: private }]) { + id: ID! + title: String + } + + # Allow anyone with a sigv4 signed request with relevant policy to access. + type PostPrivateIAM @model @auth(rules: [{ allow: private, provider: iam }]) { + id: ID! + title: String + } + + # I have a model that is protected by userPools by default. + # I want to call createPost from my lambda. + type PostOwnerIAM + @model + @auth( + rules: [ + # The cognito user pool owner can CRUD. + { allow: owner } + # A lambda function using IAM can call Mutation.createPost. + { allow: private, provider: iam, operations: [create] } + ] + ) { + id: ID! + title: String + owner: String + } + + type PostSecretFieldIAM + @model + @auth( + rules: [ + # The cognito user pool and can CRUD. + { allow: private } + # iam user can also have CRUD + { allow: private, provider: iam } + ] + ) { + id: ID + title: String + owner: String + secret: String + @auth( + rules: [ + # Only a lambda function using IAM can create/read/update this field + { allow: private, provider: iam, operations: [create,read,update] } + ] + ) + } + + type PostConnection @model @auth(rules: [{ allow: public }]) { + id: ID! + title: String! + comments: [CommentConnection] @hasMany + } + + # allow access via cognito user pools + type CommentConnection @model @auth(rules: [{ allow: private }]) { + id: ID! + content: String! + post: PostConnection @hasOne + } + + type PostIAMWithKeys + @model + @auth( + rules: [ + # API Key can CRUD + { allow: public } + # IAM can read + { allow: public, provider: iam, operations: [read] } + ] + ) { + id: ID! + title: String + type: String + @index( + name: "byDate" + sortKeyFields: ["date"] + queryField: "getPostIAMWithKeysByDate" + ) + date: AWSDateTime + } + + # This type is for the managed policy slicing, only deployment test in this e2e + type TodoWithExtraLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongName + @model(subscriptions: null) + @auth(rules: [{ allow: private, provider: iam }]) { + id: ID! + namenamenamenamenamenamenamenamenamenamenamenamenamenamename001: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename002: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename003: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename004: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename005: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename006: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename007: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename008: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename009: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename010: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename011: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename012: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename013: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename014: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename015: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename016: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename017: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename018: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename019: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename020: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename021: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename022: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename023: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename024: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename025: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename026: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename027: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename028: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename029: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename030: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename031: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename032: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename033: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename034: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename035: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename036: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename037: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename038: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename039: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename040: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename041: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename042: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename043: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename044: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename045: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename046: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename047: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename048: String! + @auth(rules: [{ allow: private, provider: iam }]) + namenamenamenamenamenamenamenamenamenamenamenamenamenamename049: String! + @auth(rules: [{ allow: private, provider: iam }]) + description: String + } + `; + + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'API_KEY', + apiKeyConfig: { + description: 'E2E Test API Key', + apiKeyExpirationDays: 300, + }, + }, + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new IndexTransformer(), + new PrimaryKeyTransformer(), + new HasOneTransformer(), + new HasManyTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + console.error(`Failed to create bucket: ${e}`); + } + + // create userpool + const userPoolResponse = await createUserPool(cognitoClient, `UserPool${STACK_NAME}`); + USER_POOL_ID = userPoolResponse.UserPool.Id; + const userPoolClientResponse = await createUserPoolClient(cognitoClient, USER_POOL_ID, `UserPool${STACK_NAME}`); + const userPoolClientId = userPoolClientResponse.UserPoolClient.ClientId; + // create auth and unauthroles + const roles = await iamHelper.createRoles(AUTH_ROLE_NAME, UNAUTH_ROLE_NAME); + // create identitypool + IDENTITY_POOL_ID = await createIdentityPool(identityClient, `IdentityPool${STACK_NAME}`, { + authRoleArn: roles.authRole.Arn, + unauthRoleArn: roles.unauthRole.Arn, + providerName: `cognito-idp.${AWS_REGION}.amazonaws.com/${USER_POOL_ID}`, + clientId: userPoolClientId, + }); + const out = transformer.transform(validSchema); + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + { AuthCognitoUserPoolId: USER_POOL_ID, authRoleName: roles.authRole.RoleName, unauthRoleName: roles.unauthRole.RoleName }, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + // Wait for any propagation to avoid random + // "The security token included in the request is invalid" errors + await new Promise(res => setTimeout(() => res(), 5000)); + + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const getApiKey = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIApiKeyOutput); + GRAPHQL_ENDPOINT = getApiEndpoint(finishedStack.Outputs); + + const apiKey = getApiKey(finishedStack.Outputs); + expect(apiKey).toBeTruthy(); + + // Verify we have all the details + expect(GRAPHQL_ENDPOINT).toBeTruthy(); + expect(USER_POOL_ID).toBeTruthy(); + expect(userPoolClientId).toBeTruthy(); + + // Configure Amplify, create users, and sign in + configureAmplify(USER_POOL_ID, userPoolClientId, IDENTITY_POOL_ID); + + const unauthCreds = await Auth.currentCredentials(); + IAM_UNAUTHCLIENT = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + auth: { + type: AUTH_TYPE.AWS_IAM, + credentials: { + accessKeyId: unauthCreds.accessKeyId, + secretAccessKey: unauthCreds.secretAccessKey, + sessionToken: unauthCreds.sessionToken, + }, + }, + disableOffline: true, + }); + + await signupUser(USER_POOL_ID, USERNAME1, TMP_PASSWORD); + const authRes = await authenticateUser(USERNAME1, TMP_PASSWORD, REAL_PASSWORD); + const idToken = authRes.getIdToken().getJwtToken(); + + USER_POOL_AUTH_CLIENT = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + auth: { + type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS, + jwtToken: () => idToken, + }, + disableOffline: true, + }); + + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const authCreds = await Auth.currentCredentials(); + IAM_AUTHCLIENT = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + auth: { + type: AUTH_TYPE.AWS_IAM, + credentials: { + accessKeyId: authCreds.accessKeyId, + secretAccessKey: authCreds.secretAccessKey, + sessionToken: authCreds.sessionToken, + }, + }, + disableOffline: true, + }); + + APIKEY_GRAPHQL_CLIENT = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + auth: { + type: AUTH_TYPE.API_KEY, + apiKey: apiKey, + }, + disableOffline: true, + }); +}); + +afterAll(async () => { + await cleanupStackAfterTest( + BUCKET_NAME, + STACK_NAME, + cf, + { cognitoClient, userPoolId: USER_POOL_ID }, + { identityClient, identityPoolId: IDENTITY_POOL_ID }, + ); + try { + await iamHelper.deleteRole(AUTH_ROLE_NAME); + } catch (e) { + console.warn(`Error during auth role cleanup ${e}`); + } + try { + await iamHelper.deleteRole(UNAUTH_ROLE_NAME); + } catch (e) { + console.warn(`Error during unauth role cleanup ${e}`); + } +}); + +test("test 'public' authStrategy", async () => { + try { + const createMutation = gql` + mutation { + createPostPublic(input: { title: "Hello, World!" }) { + id + title + } + } + `; + + const getQuery = gql` + query ($id: ID!) { + getPostPublic(id: $id) { + id + title + } + } + `; + const response = await APIKEY_GRAPHQL_CLIENT.mutate({ + mutation: createMutation, + fetchPolicy: 'no-cache', + }); + expect(response.data.createPostPublic.id).toBeDefined(); + expect(response.data.createPostPublic.title).toEqual('Hello, World!'); + const postId = response.data.createPostPublic.id; + + // user authenticated with user pools should fail + await expect( + USER_POOL_AUTH_CLIENT.query({ + query: getQuery, + variables: { id: postId }, + fetchPolicy: 'no-cache', + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access getPostPublic on type Query'); + + // user authenticated with iam should fail + // should be a 401 error since the unauth role does not have a policy to getPostPublic + await expect( + IAM_UNAUTHCLIENT.query({ + query: getQuery, + variables: { id: postId }, + fetchPolicy: 'no-cache', + }), + ).rejects.toThrow('Network error: Response not successful: Received status code 401'); + } catch (err) { + expect(err).not.toBeDefined(); + } +}); + +test(`Test 'public' provider: 'iam' authStrategy`, async () => { + try { + const createMutation = gql` + mutation { + createPostPublicIAM(input: { title: "Hello, World!" }) { + id + title + } + } + `; + + const getQuery = gql` + query ($id: ID!) { + getPostPublicIAM(id: $id) { + id + title + } + } + `; + + const response = await IAM_UNAUTHCLIENT.mutate({ + mutation: createMutation, + fetchPolicy: 'no-cache', + }); + expect(response.data.createPostPublicIAM.id).toBeDefined(); + expect(response.data.createPostPublicIAM.title).toEqual('Hello, World!'); + + const postId = response.data.createPostPublicIAM.id; + + // Authenticate User Pools user must fail + await expect( + USER_POOL_AUTH_CLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postId, + }, + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access getPostPublicIAM on type Query'); + + // API Key must fail + await expect( + APIKEY_GRAPHQL_CLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postId, + }, + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access getPostPublicIAM on type Query'); + } catch (e) { + expect(e).not.toBeDefined(); + } +}); + +test(`Test 'private' authStrategy`, async () => { + try { + const createMutation = gql` + mutation { + createPostPrivate(input: { title: "Hello, World!" }) { + id + title + } + } + `; + + const getQuery = gql` + query ($id: ID!) { + getPostPrivate(id: $id) { + id + title + } + } + `; + + const response = await USER_POOL_AUTH_CLIENT.mutate({ + mutation: createMutation, + fetchPolicy: 'no-cache', + }); + expect(response.data.createPostPrivate.id).toBeDefined(); + expect(response.data.createPostPrivate.title).toEqual('Hello, World!'); + + const postId = response.data.createPostPrivate.id; + + // Authenticate API Key fail + await expect( + APIKEY_GRAPHQL_CLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postId, + }, + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access getPostPrivate on type Query'); + + // IAM with unauth role must fail + await expect( + IAM_UNAUTHCLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postId, + }, + }), + ).rejects.toThrowError('Network error: Response not successful: Received status code 401'); + } catch (e) { + expect(e).not.toBeDefined(); + } +}); + +test(`Test 'private' provider: 'iam' authStrategy`, async () => { + // This test reuses the unauth role, but any IAM credentials would work + // in real world scenarios, we've to see if provider override works. + try { + const createMutation = gql` + mutation { + createPostPrivateIAM(input: { title: "Hello, World!" }) { + id + title + } + } + `; + + const getQuery = gql` + query ($id: ID!) { + getPostPrivateIAM(id: $id) { + id + title + } + } + `; + + const response = await IAM_AUTHCLIENT.mutate({ + mutation: createMutation, + fetchPolicy: 'no-cache', + }); + expect(response.data.createPostPrivateIAM.id).toBeDefined(); + expect(response.data.createPostPrivateIAM.title).toEqual('Hello, World!'); + + const postId = response.data.createPostPrivateIAM.id; + + // Authenticate User Pools user must fail + await expect( + USER_POOL_AUTH_CLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postId, + }, + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access getPostPrivateIAM on type Query'); + + // API Key must fail + await expect( + APIKEY_GRAPHQL_CLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postId, + }, + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access getPostPrivateIAM on type Query'); + + // public iam user must fail + await expect( + IAM_UNAUTHCLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postId, + }, + }), + ).rejects.toThrow('Network error: Response not successful: Received status code 401'); + } catch (e) { + console.error(e); + expect(e).not.toBeDefined(); + } +}); + +test(`Test 'private' provider: 'iam' authStrategy`, async () => { + // This test reuses the unauth role, but any IAM credentials would work + // in real world scenarios, we've to see if provider override works. + + // - Create UserPool - Verify owner + // - Create IAM - Verify owner (blank) + // - Get UserPool owner - Verify success + // - Get UserPool non-owner - Verify deny + // - Get IAM - Verify deny + // - Get API Key - Verify deny + + try { + const createMutation = gql` + mutation { + createPostOwnerIAM(input: { title: "Hello, World!" }) { + id + title + owner + } + } + `; + + const getQuery = gql` + query ($id: ID!) { + getPostOwnerIAM(id: $id) { + id + title + owner + } + } + `; + + const response = await USER_POOL_AUTH_CLIENT.mutate({ + mutation: createMutation, + fetchPolicy: 'no-cache', + }); + expect(response.data.createPostOwnerIAM.id).toBeDefined(); + expect(response.data.createPostOwnerIAM.title).toEqual('Hello, World!'); + expect(response.data.createPostOwnerIAM.owner).toEqual(USERNAME1); + + const postIdOwner = response.data.createPostOwnerIAM.id; + + const responseIAM = await IAM_AUTHCLIENT.mutate({ + mutation: createMutation, + fetchPolicy: 'no-cache', + }); + expect(responseIAM.data.createPostOwnerIAM.id).toBeDefined(); + expect(responseIAM.data.createPostOwnerIAM.title).toEqual('Hello, World!'); + expect(responseIAM.data.createPostOwnerIAM.owner).toBeNull(); + + const postIdIAM = responseIAM.data.createPostOwnerIAM.id; + + const responseGetUserPool = await USER_POOL_AUTH_CLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postIdOwner, + }, + }); + + expect(responseGetUserPool.data.getPostOwnerIAM.id).toBeDefined(); + expect(responseGetUserPool.data.getPostOwnerIAM.title).toEqual('Hello, World!'); + expect(responseGetUserPool.data.getPostOwnerIAM.owner).toEqual(USERNAME1); + + await expect( + USER_POOL_AUTH_CLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { id: postIdIAM }, + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access getPostOwnerIAM on type Query'); + + await expect( + IAM_UNAUTHCLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { id: postIdOwner }, + }), + ).rejects.toThrow('Network error: Response not successful: Received status code 401'); + + await expect( + APIKEY_GRAPHQL_CLIENT.query({ + query: getQuery, + variables: { id: postIdOwner }, + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access getPostOwnerIAM on type Query'); + } catch (e) { + console.error(e); + expect(e).not.toBeDefined(); + } +}); + +describe(`Test IAM protected field operations`, () => { + // This test reuses the unauth role, but any IAM credentials would work + // in real world scenarios, we've to see if provider override works. + + const createMutation = gql` + mutation { + createPostSecretFieldIAM(input: { title: "Hello, World!" }) { + id + title + } + } + `; + + const createMutationWithSecret = gql` + mutation { + createPostSecretFieldIAM(input: { title: "Hello, World!", secret: "42" }) { + id + title + secret + } + } + `; + + const getQuery = gql` + query ($id: ID!) { + getPostSecretFieldIAM(id: $id) { + id + title + } + } + `; + + const getQueryWithSecret = gql` + query ($id: ID!) { + getPostSecretFieldIAM(id: $id) { + id + title + secret + } + } + `; + + let postIdNoSecret = ''; + let postIdSecret = ''; + + beforeAll(async () => { + try { + // - Create UserPool - no secret - Success + const response = await USER_POOL_AUTH_CLIENT.mutate({ + mutation: createMutation, + fetchPolicy: 'no-cache', + }); + + postIdNoSecret = response.data.createPostSecretFieldIAM.id; + + // - Create IAM - with secret - Success + const responseIAMSecret = await IAM_AUTHCLIENT.mutate({ + mutation: createMutationWithSecret, + fetchPolicy: 'no-cache', + }); + + postIdSecret = responseIAMSecret.data.createPostSecretFieldIAM.id; + } catch (e) { + expect(e).not.toBeDefined(); + } + }); + + it('Get UserPool - Succeed', async () => { + const responseGetUserPool = await USER_POOL_AUTH_CLIENT.query({ + query: getQuery, + fetchPolicy: 'no-cache', + variables: { + id: postIdNoSecret, + }, + }); + expect(responseGetUserPool.data.getPostSecretFieldIAM.id).toBeDefined(); + expect(responseGetUserPool.data.getPostSecretFieldIAM.title).toEqual('Hello, World!'); + }); + + it('Get UserPool with secret - Fail', async () => { + expect.assertions(1); + await expect( + USER_POOL_AUTH_CLIENT.query({ + query: getQueryWithSecret, + fetchPolicy: 'no-cache', + variables: { id: postIdSecret }, + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access secret on type String'); + }); +}); + +describe(`IAM Tests`, () => { + const createMutation = gql` + mutation { + createPostIAMWithKeys(input: { title: "Hello, World!", type: "Post", date: "2019-01-01T00:00:00Z" }) { + id + title + type + date + } + } + `; + + const getPostIAMWithKeysByDate = gql` + query { + getPostIAMWithKeysByDate(type: "Post") { + items { + id + title + type + date + } + } + } + `; + + let postId = ''; + + beforeAll(async () => { + try { + // - Create API Key - Success + const response = await APIKEY_GRAPHQL_CLIENT.mutate({ + mutation: createMutation, + fetchPolicy: 'no-cache', + }); + postId = response.data.createPostIAMWithKeys.id; + } catch (e) { + expect(e).not.toBeDefined(); + } + }); + + it('Execute @key query - Succeed', async () => { + const response = await IAM_UNAUTHCLIENT.query({ + query: getPostIAMWithKeysByDate, + fetchPolicy: 'no-cache', + }); + expect(response.data.getPostIAMWithKeysByDate.items).toBeDefined(); + expect(response.data.getPostIAMWithKeysByDate.items.length).toEqual(1); + const post = response.data.getPostIAMWithKeysByDate.items[0]; + expect(post.id).toEqual(postId); + expect(post.title).toEqual('Hello, World!'); + expect(post.type).toEqual('Post'); + expect(post.date).toEqual('2019-01-01T00:00:00Z'); + }); +}); + +describe(`relational tests with @auth on type`, () => { + const createPostMutation = gql` + mutation { + createPostConnection(input: { title: "Hello, World!" }) { + id + title + } + } + `; + + const createCommentMutation = gql` + mutation ($postId: ID!) { + createCommentConnection(input: { content: "Comment", commentConnectionPostId: $postId }) { + id + content + } + } + `; + + const getPostQuery = gql` + query ($postId: ID!) { + getPostConnection(id: $postId) { + id + title + } + } + `; + + const getPostQueryWithComments = gql` + query ($postId: ID!) { + getPostConnection(id: $postId) { + id + title + comments { + items { + id + content + } + } + } + } + `; + + const getCommentQuery = gql` + query ($commentId: ID!) { + getCommentConnection(id: $commentId) { + id + content + } + } + `; + + const getCommentWithPostQuery = gql` + query ($commentId: ID!) { + getCommentConnection(id: $commentId) { + id + content + post { + id + title + } + } + } + `; + + let postId = ''; + let commentId = ''; + + beforeAll(async () => { + try { + // Add a comment with ApiKey - Succeed + const response = await APIKEY_GRAPHQL_CLIENT.mutate({ + mutation: createPostMutation, + fetchPolicy: 'no-cache', + }); + + postId = response.data.createPostConnection.id; + + // Add a comment with UserPool - Succeed + const commentResponse = await USER_POOL_AUTH_CLIENT.mutate({ + mutation: createCommentMutation, + fetchPolicy: 'no-cache', + variables: { + postId, + }, + }); + + commentId = commentResponse.data.createCommentConnection.id; + } catch (e) { + expect(e).not.toBeDefined(); + } + }); + + it('Create a Post with UserPool - Fail', async () => { + expect.assertions(1); + await expect( + USER_POOL_AUTH_CLIENT.mutate({ + mutation: createPostMutation, + fetchPolicy: 'no-cache', + }), + ).rejects.toThrow('GraphQL error: Not Authorized to access createPostConnection on type Mutation'); + }); + + it('Add a comment with ApiKey - Fail', async () => { + expect.assertions(1); + await expect( + APIKEY_GRAPHQL_CLIENT.mutate({ + mutation: createCommentMutation, + fetchPolicy: 'no-cache', + variables: { + postId, + }, + }), + ).rejects.toThrow('Not Authorized to access createCommentConnection on type Mutation'); + }); + + it('Get Post with ApiKey - Succeed', async () => { + const responseGetPost = await APIKEY_GRAPHQL_CLIENT.query({ + query: getPostQuery, + fetchPolicy: 'no-cache', + variables: { + postId, + }, + }); + expect(responseGetPost.data.getPostConnection.id).toEqual(postId); + expect(responseGetPost.data.getPostConnection.title).toEqual('Hello, World!'); + }); + + it('Get Post with UserPool - Fail', async () => { + expect.assertions(1); + await expect( + USER_POOL_AUTH_CLIENT.query({ + query: getPostQuery, + fetchPolicy: 'no-cache', + variables: { + postId, + }, + }), + ).rejects.toThrow('Not Authorized to access getPostConnection on type Query'); + }); + + it('Get Comment with UserPool - Succeed', async () => { + const responseGetComment = await USER_POOL_AUTH_CLIENT.query({ + query: getCommentQuery, + fetchPolicy: 'no-cache', + variables: { + commentId, + }, + }); + expect(responseGetComment.data.getCommentConnection.id).toEqual(commentId); + expect(responseGetComment.data.getCommentConnection.content).toEqual('Comment'); + }); + + it('Get Comment with ApiKey - Fail', async () => { + expect.assertions(1); + await expect( + APIKEY_GRAPHQL_CLIENT.query({ + query: getCommentQuery, + fetchPolicy: 'no-cache', + variables: { + commentId, + }, + }), + ).rejects.toThrow('Not Authorized to access getCommentConnection on type Query'); + }); +}); diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/NonModelAuthV2Function.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/NonModelAuthV2Function.e2e.test.ts new file mode 100644 index 00000000000..ffb0f95e34f --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/NonModelAuthV2Function.e2e.test.ts @@ -0,0 +1,243 @@ +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { FunctionTransformer } from '@aws-amplify/graphql-function-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { Auth } from 'aws-amplify'; +import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; +import { CognitoIdentity } from 'aws-sdk'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { default as CognitoClient } from 'aws-sdk/clients/cognitoidentityserviceprovider'; +import { default as S3 } from 'aws-sdk/clients/s3'; +import { ResourceConstants } from 'graphql-transformer-common'; +import gql from 'graphql-tag'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import 'isomorphic-fetch'; +import { default as moment } from 'moment'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { configureAmplify, createIdentityPool, createUserPool, createUserPoolClient, signupUser, authenticateUser } from '../cognitoUtils'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { IAMHelper } from '../IAMHelper'; +import { LambdaHelper } from '../LambdaHelper'; +import { S3Client } from '../S3Client'; + +// to deal with bug in cognito-identity-js +(global as any).fetch = require('node-fetch'); + +jest.setTimeout(2000000); + +const REGION = 'us-west-2'; +const cf = new CloudFormationClient(REGION); +const identityClient = new CognitoIdentity({ apiVersion: '2014-06-30', region: REGION }); +const cognitoClient = new CognitoClient({ apiVersion: '2016-04-19', region: REGION }); +const customS3Client = new S3Client(REGION); +const awsS3Client = new S3({ region: REGION }); +const iamHelper = new IAMHelper(REGION); + +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `FunctionTransformerTestsV2-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `appsync-function-v2-transformer-test-bucket-${BUILD_TIMESTAMP}`; +const LOCAL_FS_BUILD_DIR = '/tmp/nonmodel_auth_function_v2_transformer_tests/'; +const S3_ROOT_DIR_KEY = 'deployments'; +const ECHO_FUNCTION_NAME = `long-prefix-e2e-test-functions-echo-dev-${BUILD_TIMESTAMP}`; +const LAMBDA_EXECUTION_ROLE_NAME = `amplify_e2e_tests_lambda_basic_${BUILD_TIMESTAMP}`; +const LAMBDA_EXECUTION_POLICY_NAME = `amplify_e2e_tests_lambda_basic_access_${BUILD_TIMESTAMP}`; +let LAMBDA_EXECUTION_POLICY_ARN = ''; +const AUTH_ROLE_NAME = `${STACK_NAME}-authRole`; +const UNAUTH_ROLE_NAME = `${STACK_NAME}-unauthRole`; +let IAM_AUTH_CLIENT: AWSAppSyncClient = undefined; +let USER_POOL_ID: string; +let IDENTITY_POOL_ID: string; +let GRAPHQL_ENDPOINT: string; + +const USERNAME1 = 'user1@test.com'; + +const TMP_PASSWORD = 'Password123!'; +const REAL_PASSWORD = 'Password1234!'; + +const LAMBDA_HELPER = new LambdaHelper(); +const IAM_HELPER = new IAMHelper(); + +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} + +beforeAll(async () => { + try { + const role = await IAM_HELPER.createLambdaExecutionRole(LAMBDA_EXECUTION_ROLE_NAME); + await wait(5000); + const policy = await IAM_HELPER.createLambdaExecutionPolicy(LAMBDA_EXECUTION_POLICY_NAME); + await wait(5000); + LAMBDA_EXECUTION_POLICY_ARN = policy.Policy.Arn; + await IAM_HELPER.attachLambdaExecutionPolicy(policy.Policy.Arn, role.Role.RoleName); + await wait(10000); + await LAMBDA_HELPER.createFunction(ECHO_FUNCTION_NAME, role.Role.Arn, 'echoResolverFunction'); + } catch (e) { + console.warn(`Could not setup function: ${e}`); + } + + const validSchema = ` + type Query { + echo(msg: String!): String! @function(name: "${ECHO_FUNCTION_NAME}") @auth (rules: [{ allow: private, provider: iam }]) + } + `; + + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + console.warn(`Could not create bucket: ${e}`); + } + + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'API_KEY', + apiKeyConfig: { + description: 'E2E Test API Key', + apiKeyExpirationDays: 300, + }, + }, + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [ + new ModelTransformer(), + new FunctionTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const out = transformer.transform(validSchema); + + // create userpool + const userPoolResponse = await createUserPool(cognitoClient, `UserPool${STACK_NAME}`); + USER_POOL_ID = userPoolResponse.UserPool.Id; + const userPoolClientResponse = await createUserPoolClient(cognitoClient, USER_POOL_ID, `UserPool${STACK_NAME}`); + const userPoolClientId = userPoolClientResponse.UserPoolClient.ClientId; + // create auth and unauth roles + const roles = await iamHelper.createRoles(AUTH_ROLE_NAME, UNAUTH_ROLE_NAME); + // create identity pool + IDENTITY_POOL_ID = await createIdentityPool(identityClient, `IdentityPool${STACK_NAME}`, { + authRoleArn: roles.authRole.Arn, + unauthRoleArn: roles.unauthRole.Arn, + providerName: `cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}`, + clientId: userPoolClientId, + }); + + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + { AuthCognitoUserPoolId: USER_POOL_ID, authRoleName: roles.authRole.RoleName, unauthRoleName: roles.unauthRole.RoleName }, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + + // Arbitrary wait to make sure everything is ready. + await cf.wait(5, () => Promise.resolve()); + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const endpoint = getApiEndpoint(finishedStack.Outputs); + expect(endpoint).toBeDefined(); + GRAPHQL_ENDPOINT = getApiEndpoint(finishedStack.Outputs); + expect(GRAPHQL_ENDPOINT).toBeTruthy(); + + // Verify we have all the details + expect(USER_POOL_ID).toBeTruthy(); + expect(userPoolClientId).toBeTruthy(); + + // Configure Amplify, create users, and sign in. + configureAmplify(USER_POOL_ID, userPoolClientId, IDENTITY_POOL_ID); + + await signupUser(USER_POOL_ID, USERNAME1, TMP_PASSWORD); + await authenticateUser(USERNAME1, TMP_PASSWORD, REAL_PASSWORD); + + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const authCredentials = await Auth.currentCredentials(); + IAM_AUTH_CLIENT = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: REGION, + auth: { + type: AUTH_TYPE.AWS_IAM, + credentials: authCredentials, + }, + disableOffline: true, + }); +}); + +afterAll(async () => { + await cleanupStackAfterTest( + BUCKET_NAME, + STACK_NAME, + cf, + { cognitoClient, userPoolId: USER_POOL_ID }, + { identityClient, identityPoolId: IDENTITY_POOL_ID }, + ); + + try { + await IAM_HELPER.deleteRole(AUTH_ROLE_NAME); + } catch (e) { + console.warn(`Error during auth role cleanup ${e}`); + } + try { + await IAM_HELPER.deleteRole(UNAUTH_ROLE_NAME); + } catch (e) { + console.warn(`Error during unauth role cleanup ${e}`); + } + + try { + await LAMBDA_HELPER.deleteFunction(ECHO_FUNCTION_NAME); + } catch (e) { + console.warn(`Error during function cleanup: ${e}`); + } + try { + await IAM_HELPER.detachLambdaExecutionPolicy(LAMBDA_EXECUTION_POLICY_ARN, LAMBDA_EXECUTION_ROLE_NAME); + } catch (e) { + console.warn(`Error during policy dissociation: ${e}`); + } + try { + await IAM_HELPER.deleteRole(LAMBDA_EXECUTION_ROLE_NAME); + } catch (e) { + console.warn(`Error during role cleanup: ${e}`); + } + try { + await IAM_HELPER.deletePolicy(LAMBDA_EXECUTION_POLICY_ARN); + } catch (e) { + console.warn(`Error during policy cleanup: ${e}`); + } +}); + +/** + * Test queries below + */ +test('Test calling echo function as a user via IAM', async () => { + const query = gql` + query { + echo(msg: "Hello") + } + `; + + const response = await IAM_AUTH_CLIENT.query<{ echo: string }>({ + query, + fetchPolicy: 'no-cache', + }); + + expect(response.data.echo).toEqual('Hello'); +}); + +function wait(ms: number) { + return new Promise((resolve, reject) => { + setTimeout(() => resolve(), ms); + }); +} diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/PerFieldAuthV2Transformer.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/PerFieldAuthV2Transformer.e2e.test.ts new file mode 100644 index 00000000000..6126e08fc93 --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/PerFieldAuthV2Transformer.e2e.test.ts @@ -0,0 +1,624 @@ +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { CreateBucketRequest } from 'aws-sdk/clients/s3'; +import { CognitoIdentityServiceProvider as CognitoClient, S3 } from 'aws-sdk'; +import { GraphQLClient } from '../GraphQLClient'; +import { S3Client } from '../S3Client'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { default as moment } from 'moment'; +import * as fs from 'fs'; +import { + createUserPool, + createUserPoolClient, + createGroup, + addUserToGroup, + configureAmplify, + signupUser, + authenticateUser, +} from '../cognitoUtils'; +import 'isomorphic-fetch'; + +// to deal with bug in cognito-identity-js +(global as any).fetch = require('node-fetch'); + +jest.setTimeout(2000000); + +const AWS_REGION = 'us-west-2'; + +const cf = new CloudFormationClient(AWS_REGION); +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `PerFieldAuthV2Tests-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `per-field-authv2-tests-bucket-${BUILD_TIMESTAMP}`; +const LOCAL_BUILD_ROOT = '/tmp/per_field_authv2_tests/'; +const DEPLOYMENT_ROOT_KEY = 'deployments'; + +let GRAPHQL_ENDPOINT = undefined; + +/** + * Client 1 is logged in and is a member of the Admin group. + */ +let GRAPHQL_CLIENT_1 = undefined; + +/** + * Client 2 is logged in and is a member of the Devs group. + */ +let GRAPHQL_CLIENT_2 = undefined; + +/** + * Client 3 is logged in and has no group memberships. + */ +let GRAPHQL_CLIENT_3 = undefined; + +let USER_POOL_ID = undefined; + +const USERNAME1 = 'user1@test.com'; +const USERNAME2 = 'user2@test.com'; +const USERNAME3 = 'user3@test.com'; +const TMP_PASSWORD = 'Password123!'; +const REAL_PASSWORD = 'Password1234!'; + +const ADMIN_GROUP_NAME = 'Admin'; +const DEVS_GROUP_NAME = 'Devs'; +const PARTICIPANT_GROUP_NAME = 'Participant'; +const WATCHER_GROUP_NAME = 'Watcher'; +const INSTRUCTOR_GROUP_NAME = 'Instructor'; + +const cognitoClient = new CognitoClient({ apiVersion: '2016-04-19', region: 'us-west-2' }); +const customS3Client = new S3Client('us-west-2'); +const awsS3Client = new S3({ region: 'us-west-2' }); + +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} + +beforeAll(async () => { + // Create a stack for the post model with auth enabled. + if (!fs.existsSync(LOCAL_BUILD_ROOT)) { + fs.mkdirSync(LOCAL_BUILD_ROOT); + } + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + console.warn(`Could not create bucket: ${e}`); + } + const validSchema = ` + # Owners may update their owned records. + # Admins may create Employee records. + # Any authenticated user may view Employee ids & e_mails. + # Owners and members of "Admin" group may see employee salaries. + # Owners of "Admin" group may create and update employee salaries. + type Employee + @model(subscriptions: { level: public }) + @auth( + rules: [ + { allow: groups, groups: ["Admin"], operations: [create, read, update] } + { allow: private, operations: [read] } + { allow: owner, ownerField: "e_mail", operations: [read, update] } + ] + ) { + id: ID + # bio and notes are the only field an owner can update + bio: String + + # Fields with ownership conditions take precendence to the Object @auth. + # That means that both the @auth on Object AND the @auth on the field must + # be satisfied. + + # Owners & "Admin"s may view employee e_mail addresses. Only "Admin"s may create/update. + # TODO: { allow: authenticated } would be useful here so that any employee could view. + # Should also allow creation of underscore fields + e_mail: String + @auth( + rules: [ + { allow: groups, groups: ["Admin"], operations: [create, update, read] } + { allow: owner, ownerField: "e_mail", operations: [read] } + ] + ) + + # The owner & "Admin"s may view the salary. Only "Admins" may create/update. + salary: Int + @auth( + rules: [ + { allow: groups, groups: ["Admin"], operations: [create,update, read] } + { allow: owner, ownerField: "e_mail", operations: [read] } + ] + ) + + # The delete operation means you cannot update the value to "null" or "undefined". + # Since delete operations are at the object level, this actually adds auth rules to the update mutation. + notes: String + @auth( + rules: [ + { allow: groups, groups: ["Admin"], operations: [create, read] }, + { + allow: owner + ownerField: "e_mail" + operations: [read, update, delete] + } + ] + ) + } + + type Student @model + @auth(rules: [ + {allow: owner} + {allow: groups, groups: ["Instructor"]} + ]){ + id: String, + name: String, + bio: String, + notes: String @auth(rules: [{allow: owner}]) + } + + type Post @model + @auth(rules: [ + { allow: groups, groups: ["Admin"] }, + { allow: owner, ownerField: "owner1", operations: [read, create] }]) + { + id: ID + owner1: String @auth(rules: [ + { allow: groups, groups: ["Admin"], operations: [create, read, delete] }, + { allow: owner, ownerField: "owner1", operations: [read, create] } + ]) + text: String @auth(rules: [ + { allow: groups, groups: ["Admin"], operations: [create, read] }, + { allow: owner, ownerField: "owner1", operations : [read, update]}]) + }`; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }, + transformers: [ + new ModelTransformer(), + new AuthTransformer({ + addAwsIamAuthInOutputSchema: false, + }), + ], + }); + const userPoolResponse = await createUserPool(cognitoClient, `UserPool${STACK_NAME}`); + USER_POOL_ID = userPoolResponse.UserPool.Id; + const userPoolClientResponse = await createUserPoolClient(cognitoClient, USER_POOL_ID, `UserPool${STACK_NAME}`); + const userPoolClientId = userPoolClientResponse.UserPoolClient.ClientId; + try { + // Clean the bucket + const out = transformer.transform(validSchema); + + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + { AuthCognitoUserPoolId: USER_POOL_ID }, + LOCAL_BUILD_ROOT, + BUCKET_NAME, + DEPLOYMENT_ROOT_KEY, + BUILD_TIMESTAMP, + ); + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + GRAPHQL_ENDPOINT = getApiEndpoint(finishedStack.Outputs); + + // Verify we have all the details + expect(GRAPHQL_ENDPOINT).toBeTruthy(); + expect(USER_POOL_ID).toBeTruthy(); + expect(userPoolClientId).toBeTruthy(); + + // Configure Amplify, create users, and sign in. + configureAmplify(USER_POOL_ID, userPoolClientId); + + await signupUser(USER_POOL_ID, USERNAME1, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME2, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME3, TMP_PASSWORD); + await createGroup(USER_POOL_ID, ADMIN_GROUP_NAME); + await createGroup(USER_POOL_ID, PARTICIPANT_GROUP_NAME); + await createGroup(USER_POOL_ID, WATCHER_GROUP_NAME); + await createGroup(USER_POOL_ID, DEVS_GROUP_NAME); + await createGroup(USER_POOL_ID, INSTRUCTOR_GROUP_NAME); + await addUserToGroup(ADMIN_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(PARTICIPANT_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(WATCHER_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(DEVS_GROUP_NAME, USERNAME2, USER_POOL_ID); + await addUserToGroup(INSTRUCTOR_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(INSTRUCTOR_GROUP_NAME, USERNAME2, USER_POOL_ID); + + const authResAfterGroup: any = await authenticateUser(USERNAME1, TMP_PASSWORD, REAL_PASSWORD); + const idToken = authResAfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_1 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken }); + + const authRes2AfterGroup: any = await authenticateUser(USERNAME2, TMP_PASSWORD, REAL_PASSWORD); + const idToken2 = authRes2AfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_2 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken2 }); + + const authRes3: any = await authenticateUser(USERNAME3, TMP_PASSWORD, REAL_PASSWORD); + const idToken3 = authRes3.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_3 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken3 }); + + // Wait for any propagation to avoid random + // "The security token included in the request is invalid" errors + await new Promise(res => setTimeout(() => res(), 5000)); + } catch (e) { + console.error(e); + expect(true).toEqual(false); + } +}); + +afterAll(async () => { + await cleanupStackAfterTest(BUCKET_NAME, STACK_NAME, cf, { cognitoClient, userPoolId: USER_POOL_ID }); +}); + +/** + * Tests + */ +test('Test that only Admins can create Employee records.', async () => { + const createUser1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createEmployee(input: { e_mail: "user2@test.com", salary: 100 }) { + id + e_mail + salary + } + }`, + {}, + ); + expect(createUser1.data.createEmployee.e_mail).toEqual('user2@test.com'); + expect(createUser1.data.createEmployee.salary).toEqual(100); + + const tryToCreateAsNonAdmin = await GRAPHQL_CLIENT_2.query( + `mutation { + createEmployee(input: { e_mail: "user2@test.com", salary: 101 }) { + id + e_mail + salary + } + }`, + {}, + ); + expect(tryToCreateAsNonAdmin.data.createEmployee).toBeNull(); + expect(tryToCreateAsNonAdmin.errors).toHaveLength(1); + + const tryToCreateAsNonAdmin2 = await GRAPHQL_CLIENT_3.query( + `mutation { + createEmployee(input: { e_mail: "user2@test.com", salary: 101 }) { + id + e_mail + salary + } + }`, + {}, + ); + expect(tryToCreateAsNonAdmin2.data.createEmployee).toBeNull(); + expect(tryToCreateAsNonAdmin2.errors).toHaveLength(1); +}); + +test('Test that only Admins may update salary & e_mail.', async () => { + const createUser1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createEmployee(input: { e_mail: "user2@test.com", salary: 100 }) { + id + e_mail + salary + } + }`, + {}, + ); + const employeeId = createUser1.data.createEmployee.id; + expect(employeeId).not.toBeNull(); + expect(createUser1.data.createEmployee.e_mail).toEqual('user2@test.com'); + expect(createUser1.data.createEmployee.salary).toEqual(100); + + const tryToUpdateAsNonAdmin = await GRAPHQL_CLIENT_2.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", salary: 101 }) { + id + e_mail + salary + } + }`, + {}, + ); + expect(tryToUpdateAsNonAdmin.data.updateEmployee).toBeNull(); + expect(tryToUpdateAsNonAdmin.errors).toHaveLength(1); + + const tryToUpdateAsNonAdmin2 = await GRAPHQL_CLIENT_2.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", e_mail: "someonelese@gmail.com" }) { + id + e_mail + salary + } + }`, + {}, + ); + expect(tryToUpdateAsNonAdmin2.data.updateEmployee).toBeNull(); + expect(tryToUpdateAsNonAdmin2.errors).toHaveLength(1); + + const tryToUpdateAsNonAdmin3 = await GRAPHQL_CLIENT_3.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", e_mail: "someonelese@gmail.com" }) { + id + e_mail + salary + } + }`, + {}, + ); + expect(tryToUpdateAsNonAdmin3.data.updateEmployee).toBeNull(); + expect(tryToUpdateAsNonAdmin3.errors).toHaveLength(1); + + const updateAsAdmin = await GRAPHQL_CLIENT_1.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", e_mail: "someonelese@gmail.com" }) { + id + e_mail + salary + } + }`, + {}, + ); + expect(updateAsAdmin.data.updateEmployee.e_mail).toEqual('someonelese@gmail.com'); + expect(updateAsAdmin.data.updateEmployee.salary).toEqual(100); + + const updateAsAdmin2 = await GRAPHQL_CLIENT_1.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", salary: 99 }) { + id + e_mail + salary + } + }`, + {}, + ); + expect(updateAsAdmin2.data.updateEmployee.e_mail).toEqual('someonelese@gmail.com'); + expect(updateAsAdmin2.data.updateEmployee.salary).toEqual(99); +}); + +test('Test that owners may update their bio.', async () => { + const createUser1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createEmployee(input: { e_mail: "user2@test.com", salary: 100 }) { + id + e_mail + salary + } + }`, + {}, + ); // 2afcb900-7fa1-4cb2-aca6-587f3e217c15 + const employeeId = createUser1.data.createEmployee.id; + expect(employeeId).not.toBeNull(); + expect(createUser1.data.createEmployee.e_mail).toEqual('user2@test.com'); + expect(createUser1.data.createEmployee.salary).toEqual(100); + + const tryToUpdateAsNonAdmin = await GRAPHQL_CLIENT_2.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", bio: "Does cool stuff." }) { + id + e_mail + salary + bio + } + }`, + {}, + ); + expect(tryToUpdateAsNonAdmin.data.updateEmployee.bio).toEqual('Does cool stuff.'); + expect(tryToUpdateAsNonAdmin.data.updateEmployee.e_mail).toEqual('user2@test.com'); + expect(tryToUpdateAsNonAdmin.data.updateEmployee.salary).toEqual(100); +}); + +test('Test that everyone may view employee bios.', async () => { + const createUser1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createEmployee(input: { e_mail: "user3@test.com", salary: 100, bio: "Likes long walks on the beach" }) { + id + e_mail + salary + bio + } + }`, + {}, + ); + const employeeId = createUser1.data.createEmployee.id; + expect(employeeId).not.toBeNull(); + expect(createUser1.data.createEmployee.e_mail).toEqual('user3@test.com'); + expect(createUser1.data.createEmployee.salary).toEqual(100); + expect(createUser1.data.createEmployee.bio).toEqual('Likes long walks on the beach'); + + const getAsNonAdmin = await GRAPHQL_CLIENT_2.query( + `query { + getEmployee(id: "${employeeId}") { + id + e_mail + bio + } + }`, + {}, + ); + // Should not be able to view the e_mail as the non owner + expect(getAsNonAdmin.data.getEmployee.e_mail).toBeNull(); + // Should be able to view the bio. + expect(getAsNonAdmin.data.getEmployee.bio).toEqual('Likes long walks on the beach'); + expect(getAsNonAdmin.errors).toHaveLength(1); + + const listAsNonAdmin = await GRAPHQL_CLIENT_2.query( + `query { + listEmployees { + items { + id + bio + } + } + }`, + {}, + ); + expect(listAsNonAdmin.data.listEmployees.items.length).toBeGreaterThan(1); + let seenId = false; + for (const item of listAsNonAdmin.data.listEmployees.items) { + if (item.id === employeeId) { + seenId = true; + expect(item.bio).toEqual('Likes long walks on the beach'); + } + } + expect(seenId).toEqual(true); +}); + +test('Test that only owners may "delete" i.e. update the field to null.', async () => { + const createUser1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createEmployee(input: { e_mail: "user3@test.com", salary: 200, notes: "note1" }) { + id + e_mail + salary + notes + } + }`, + {}, + ); + const employeeId = createUser1.data.createEmployee.id; + expect(employeeId).not.toBeNull(); + expect(createUser1.data.createEmployee.e_mail).toEqual('user3@test.com'); + expect(createUser1.data.createEmployee.salary).toEqual(200); + expect(createUser1.data.createEmployee.notes).toEqual('note1'); + + const tryToDeleteUserNotes = await GRAPHQL_CLIENT_2.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", notes: null }) { + id + notes + } + }`, + {}, + ); + expect(tryToDeleteUserNotes.data.updateEmployee).toBeNull(); + expect(tryToDeleteUserNotes.errors).toHaveLength(1); + + const updateNewsWithNotes = await GRAPHQL_CLIENT_3.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", notes: "something else" }) { + id + notes + } + }`, + {}, + ); + expect(updateNewsWithNotes.data.updateEmployee.notes).toEqual('something else'); + + const updateAsAdmin = await GRAPHQL_CLIENT_1.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", notes: null }) { + id + notes + } + }`, + {}, + ); + expect(updateAsAdmin.data.updateEmployee).toBeNull(); + expect(updateAsAdmin.errors).toHaveLength(1); + + const deleteNotes = await GRAPHQL_CLIENT_3.query( + `mutation { + updateEmployee(input: { id: "${employeeId}", notes: null }) { + id + notes + } + }`, + {}, + ); + expect(deleteNotes.data.updateEmployee.notes).toBeNull(); +}); + +test('Test with auth with subscriptions on default behavior', async () => { + /** + * client 1 and 2 are in the same user pool though client 1 should + * not be able to see notes if they are created by client 2 + * */ + const secureNote1 = 'secureNote1'; + const createStudent2 = await GRAPHQL_CLIENT_2.query( + `mutation { + createStudent(input: {bio: "bio1", name: "student1", notes: "${secureNote1}"}) { + id + bio + name + notes + owner + } + }`, + {}, + ); + expect(createStudent2.data.createStudent.id).toBeDefined(); + const createStudent1queryID = createStudent2.data.createStudent.id; + expect(createStudent2.data.createStudent.bio).toEqual('bio1'); + expect(createStudent2.data.createStudent.notes).toBeNull(); + // running query as username2 should return value + const queryForStudent2 = await GRAPHQL_CLIENT_2.query( + `query { + getStudent(id: "${createStudent1queryID}") { + bio + id + name + notes + owner + } + }`, + {}, + ); + expect(queryForStudent2.data.getStudent.notes).toEqual(secureNote1); + + // running query as username3 should return the type though return notes as null + const queryAsStudent1 = await GRAPHQL_CLIENT_1.query( + `query { + getStudent(id: "${createStudent1queryID}") { + bio + id + name + notes + owner + } + }`, + {}, + ); + expect(queryAsStudent1.data.getStudent.notes).toBeNull(); +}); + +test('AND per-field dynamic auth rule test', async () => { + const createPostResponse = await GRAPHQL_CLIENT_1.query(`mutation CreatePost { + createPost(input: {owner1: "${USERNAME1}", text: "mytext"}) { + id + text + owner1 + } + }`); + const postID1 = createPostResponse.data.createPost.id; + expect(postID1).toBeDefined(); + expect(createPostResponse.data.createPost.text).toEqual('mytext'); + expect(createPostResponse.data.createPost.owner1).toEqual(USERNAME1); + + const badUpdatePostResponse = await GRAPHQL_CLIENT_1.query(`mutation UpdatePost { + updatePost(input: {id: "${postID1}", text: "newText", owner1: "${USERNAME1}"}) { + id + owner1 + text + } + } + `); + expect(badUpdatePostResponse.errors[0].data).toBeNull(); + expect(badUpdatePostResponse.errors[0].errorType).toEqual('Unauthorized'); + + const correctUpdatePostResponse = await GRAPHQL_CLIENT_1.query(`mutation UpdatePost { + updatePost(input: {id: "${postID1}", text: "newText"}) { + id + owner1 + text + } + }`); + expect(correctUpdatePostResponse.data.updatePost.owner1).toEqual(USERNAME1); + expect(correctUpdatePostResponse.data.updatePost.text).toEqual('newText'); +}); diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/PredictionsTransformerV2Tests.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/PredictionsTransformerV2Tests.e2e.test.ts new file mode 100644 index 00000000000..1f311234feb --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/PredictionsTransformerV2Tests.e2e.test.ts @@ -0,0 +1,184 @@ +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { PredictionsTransformer } from '@aws-amplify/graphql-predictions-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { default as S3 } from 'aws-sdk/clients/s3'; +import * as fs from 'fs-extra'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { default as moment } from 'moment'; +import path from 'path'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { GraphQLClient } from '../GraphQLClient'; +import { S3Client } from '../S3Client'; + +// tslint:disable: no-magic-numbers +jest.setTimeout(2000000); + +const AWS_REGION = 'us-east-2'; +const cf = new CloudFormationClient(AWS_REGION); +const customS3Client = new S3Client(AWS_REGION); +const awsS3Client = new S3({ region: AWS_REGION }); +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `PredictionsTransformerV2Tests-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `appsync-predictions-transformer-v2-test-bucket-${BUILD_TIMESTAMP}`; +const LOCAL_FS_BUILD_DIR = '/tmp/predictions_transformer_tests_v2/'; +const S3_ROOT_DIR_KEY = 'deployments'; + +let GRAPHQL_CLIENT: GraphQLClient = undefined; + +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} + +beforeAll(async () => { + const validSchema = ` + type Query { + translateImageText: String @predictions(actions: [ identifyText ]) + translateLabels: String @predictions(actions: [ identifyLabels ]) + translateThis: String @predictions(actions: [ translateText ]) + speakTranslatedText: String @predictions(actions: [ translateText convertTextToSpeech]) + } + `; + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + console.warn(`Could not create bucket: ${e}`); + } + const transformer = new GraphQLTransform({ + transformers: [new ModelTransformer(), new PredictionsTransformer({ bucketName: BUCKET_NAME })], + sandboxModeEnabled: true, + }); + const out = transformer.transform(validSchema); + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + {}, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + // Arbitrary wait to make sure everything is ready. + await cf.wait(5, () => Promise.resolve()); + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const getApiKey = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIApiKeyOutput); + const endpoint = getApiEndpoint(finishedStack.Outputs); + const apiKey = getApiKey(finishedStack.Outputs); + expect(apiKey).toBeDefined(); + expect(endpoint).toBeDefined(); + GRAPHQL_CLIENT = new GraphQLClient(endpoint, { 'x-api-key': apiKey }); +}); + +afterAll(async () => { + await cleanupStackAfterTest(BUCKET_NAME, STACK_NAME, cf); +}); + +test('test translate and convert text to speech', async () => { + // logic to test graphql + const response = await GRAPHQL_CLIENT.query( + `query SpeakTranslatedText($input: SpeakTranslatedTextInput!) { + speakTranslatedText(input: $input) + }`, + { + input: { + translateText: { + sourceLanguage: 'en', + targetLanguage: 'es', + text: 'this is a voice test', + }, + convertTextToSpeech: { + voiceID: 'Conchita', + }, + }, + }, + ); + expect(response).toBeDefined(); + const pollyURL = response.data.speakTranslatedText; + // check that return format is a url + expect(pollyURL).toMatch(/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/); +}); + +test('test translate text individually', async () => { + const germanTranslation = /((\bDies\b)|(\bdas\b)|(\bder\b)) ist ein Sprachtest/i; + const response = await GRAPHQL_CLIENT.query( + `query TranslateThis($input: TranslateThisInput!) { + translateThis(input: $input) + }`, + { + input: { + translateText: { + sourceLanguage: 'en', + targetLanguage: 'de', + text: 'this is a voice test', + }, + }, + }, + ); + expect(response).toBeDefined(); + const translatedText = response.data.translateThis; + expect(translatedText).toMatch(germanTranslation); +}); + +test('test identify image text', async () => { + const file = path.join(__dirname, 'test-data', 'amazon.png'); + const buffer = fs.readFileSync(file); + + const params = { + Key: 'public/amazon-logo.png', + Body: buffer, + Bucket: BUCKET_NAME, + }; + + await awsS3Client.upload(params).promise(); + const response = await GRAPHQL_CLIENT.query( + `query TranslateImageText($input: TranslateImageTextInput!) { + translateImageText(input: $input) + }`, + { + input: { + identifyText: { + key: 'amazon-logo.png', + }, + }, + }, + ); + + expect(response).toBeDefined(); + expect(response.data.translateImageText).toEqual('Available on amazon R'); +}); + +test('test identify labels', async () => { + const file = path.join(__dirname, 'test-data', 'dogs.png'); + const buffer = fs.readFileSync(file); + + const params = { + Key: 'public/dogs.png', + Body: buffer, + Bucket: BUCKET_NAME, + }; + + await awsS3Client.upload(params).promise(); + const response = await GRAPHQL_CLIENT.query( + `query TranslateLabels($input: TranslateLabelsInput!) { + translateLabels(input: $input) + }`, + { + input: { + identifyLabels: { + key: 'dogs.png', + }, + }, + }, + ); + + expect(response).toBeDefined(); + expect(response.data.translateLabels).toBeDefined(); + expect(response.data.translateLabels.length > 0).toBeTruthy(); +}); diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/RelationalTransformers.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/RelationalTransformers.e2e.test.ts index af2c9ef1b26..6223d8e1c38 100644 --- a/packages/graphql-transformers-e2e-tests/src/__tests__/RelationalTransformers.e2e.test.ts +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/RelationalTransformers.e2e.test.ts @@ -1,3 +1,4 @@ +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { @@ -7,6 +8,7 @@ import { ManyToManyTransformer, } from '@aws-amplify/graphql-relational-transformer'; import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; import { ResourceConstants } from 'graphql-transformer-common'; import { CloudFormationClient } from '../CloudFormationClient'; import { Output } from 'aws-sdk/clients/cloudformation'; @@ -119,10 +121,18 @@ type Course @model { `; let out; try { + const authConfig: AppSyncAuthConfiguration = { + defaultAuthentication: { + authenticationType: 'API_KEY', + }, + additionalAuthenticationProviders: [], + }; + const authTransformer = new AuthTransformer({ authConfig, addAwsIamAuthInOutputSchema: false }); const modelTransformer = new ModelTransformer(); const indexTransformer = new IndexTransformer(); const hasOneTransformer = new HasOneTransformer(); const transformer = new GraphQLTransform({ + authConfig, featureFlags, transformers: [ modelTransformer, @@ -131,8 +141,10 @@ type Course @model { hasOneTransformer, new HasManyTransformer(), new BelongsToTransformer(), - new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer), + new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer), + authTransformer, ], + sandboxModeEnabled: true, }); out = transformer.transform(validSchema); } catch (e) { diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/RelationalWithAuthV2.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/RelationalWithAuthV2.e2e.test.ts new file mode 100644 index 00000000000..cc325ee5d47 --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/RelationalWithAuthV2.e2e.test.ts @@ -0,0 +1,511 @@ +import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { + BelongsToTransformer, + HasManyTransformer, + HasOneTransformer, + ManyToManyTransformer, +} from '@aws-amplify/graphql-relational-transformer'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { GraphQLClient } from '../GraphQLClient'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { S3Client } from '../S3Client'; +import { S3, CognitoIdentityServiceProvider as CognitoClient } from 'aws-sdk'; +import { default as moment } from 'moment'; +import { + addUserToGroup, + authenticateUser, + configureAmplify, + createGroup, + createUserPool, + createUserPoolClient, + signupUser, +} from '../cognitoUtils'; +// to deal with bug in cognito-identity-js +(global as any).fetch = require('node-fetch'); + +jest.setTimeout(2000000); + +const cf = new CloudFormationClient('us-west-2'); +const customS3Client = new S3Client('us-west-2'); +const awsS3Client = new S3({ region: 'us-west-2' }); +const cognitoClient = new CognitoClient({ apiVersion: '2016-04-19', region: 'us-west-2' }); +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `RelationalAuthV2TransformersTest-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `appsync-relational-auth-transformer-test-${BUILD_TIMESTAMP}`; +const LOCAL_FS_BUILD_DIR = '/tmp/relational_auth_transformer_tests/'; +const S3_ROOT_DIR_KEY = 'deployments'; + +let GRAPHQL_ENDPOINT = undefined; + +/** + * Client 1 is logged in and is a member of the Admin group. + */ +let GRAPHQL_CLIENT_1 = undefined; + +/** + * Client 2 is logged in and is a member of the Devs group. + */ +let GRAPHQL_CLIENT_2 = undefined; + +/** + * Client 3 is logged in and has no group memberships. + */ +let GRAPHQL_CLIENT_3 = undefined; + +let USER_POOL_ID = undefined; + +const USERNAME1 = 'user1@test.com'; +const USERNAME2 = 'user2@test.com'; +const USERNAME3 = 'user3@test.com'; +const TMP_PASSWORD = 'Password123!'; +const REAL_PASSWORD = 'Password1234!'; + +const ADMIN_GROUP_NAME = 'Admin'; +const DEVS_GROUP_NAME = 'Devs'; +const PARTICIPANT_GROUP_NAME = 'Participant'; +const WATCHER_GROUP_NAME = 'Watcher'; + +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} + +beforeAll(async () => { + const validSchema = ` + type Post @model @auth(rules: [{allow: owner}]) { + id: ID! + title: String! + author: User @belongsTo(fields: ["owner"]) + owner: ID! @index(name: "byOwner", sortKeyFields: ["id"]) + } + + type User @model @auth(rules: [{ allow: owner }]) { + id: ID! + posts: [Post] @hasMany(indexName: "byOwner", fields: ["id"]) + } + + type FieldProtected @model @auth(rules: [{ allow: private }, { allow: owner, operations: [read] }]) { + id: ID! + owner: String + ownerOnly: String @auth(rules: [{allow: owner}]) + } + + type OpenTopLevel @model @auth(rules: [{allow: private}]) { + id: ID! + name: String + owner: String + protected: [ConnectionProtected] @hasMany(indexName: "byTopLevel", fields: ["id"]) + } + + type ConnectionProtected @model(queries: null) @auth(rules: [{allow: owner}]) { + id: ID! + name: String + owner: String + topLevelID: ID! @index(name: "byTopLevel", sortKeyFields: ["id"]) + topLevel: OpenTopLevel @belongsTo(fields: ["topLevelID"]) + }`; + let out; + try { + const modelTransformer = new ModelTransformer(); + const indexTransformer = new IndexTransformer(); + const hasOneTransformer = new HasOneTransformer(); + const authTransformer = new AuthTransformer({ addAwsIamAuthInOutputSchema: false }); + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [], + }, + transformers: [ + modelTransformer, + new PrimaryKeyTransformer(), + indexTransformer, + hasOneTransformer, + new HasManyTransformer(), + new BelongsToTransformer(), + new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer), + authTransformer, + ], + }); + out = transformer.transform(validSchema); + } catch (e) { + console.error(`Failed to transform schema: ${e}`); + expect(true).toEqual(false); + } + try { + await awsS3Client + .createBucket({ + Bucket: BUCKET_NAME, + }) + .promise(); + } catch (e) { + console.error(`Failed to create S3 bucket: ${e}`); + expect(true).toEqual(false); + } + const userPoolResponse = await createUserPool(cognitoClient, `UserPool${STACK_NAME}`); + USER_POOL_ID = userPoolResponse.UserPool.Id; + const userPoolClientResponse = await createUserPoolClient(cognitoClient, USER_POOL_ID, `UserPool${STACK_NAME}`); + const userPoolClientId = userPoolClientResponse.UserPoolClient.ClientId; + try { + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + { AuthCognitoUserPoolId: USER_POOL_ID }, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const getApiKey = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIApiKeyOutput); + const apiKey = getApiKey(finishedStack.Outputs); + GRAPHQL_ENDPOINT = getApiEndpoint(finishedStack.Outputs); + expect(apiKey).not.toBeTruthy(); + + // Verify we have all the details + expect(GRAPHQL_ENDPOINT).toBeTruthy(); + expect(USER_POOL_ID).toBeTruthy(); + expect(userPoolClientId).toBeTruthy(); + + // Configure Amplify, create users, and sign in. + configureAmplify(USER_POOL_ID, userPoolClientId); + + await signupUser(USER_POOL_ID, USERNAME1, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME2, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME3, TMP_PASSWORD); + + await createGroup(USER_POOL_ID, ADMIN_GROUP_NAME); + await createGroup(USER_POOL_ID, PARTICIPANT_GROUP_NAME); + await createGroup(USER_POOL_ID, WATCHER_GROUP_NAME); + await createGroup(USER_POOL_ID, DEVS_GROUP_NAME); + await addUserToGroup(ADMIN_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(PARTICIPANT_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(WATCHER_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(DEVS_GROUP_NAME, USERNAME2, USER_POOL_ID); + const authResAfterGroup: any = await authenticateUser(USERNAME1, TMP_PASSWORD, REAL_PASSWORD); + + const idToken = authResAfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_1 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken }); + + const authRes2AfterGroup: any = await authenticateUser(USERNAME2, TMP_PASSWORD, REAL_PASSWORD); + const idToken2 = authRes2AfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_2 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken2 }); + + const authRes3: any = await authenticateUser(USERNAME3, TMP_PASSWORD, REAL_PASSWORD); + const idToken3 = authRes3.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_3 = new GraphQLClient(GRAPHQL_ENDPOINT, { Authorization: idToken3 }); + + // Wait for any propagation to avoid random + // "The security token included in the request is invalid" errors + await new Promise(res => setTimeout(() => res(), 5000)); + } catch (e) { + console.error(e); + expect(true).toEqual(false); + } +}); + +afterAll(async () => { + await cleanupStackAfterTest(BUCKET_NAME, STACK_NAME, cf, { cognitoClient, userPoolId: USER_POOL_ID }); +}); + +/** + * Test queries below + */ +test('Test creating a post and immediately view it via the User.posts connection.', async () => { + const createUser1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createUser(input: { id: "user1@test.com" }) { + id + } + }`, + {}, + ); + expect(createUser1.data.createUser.id).toEqual('user1@test.com'); + + const response = await GRAPHQL_CLIENT_1.query( + `mutation { + createPost(input: { title: "Hello, World!", owner: "user1@test.com" }) { + id + title + owner + } + }`, + {}, + ); + expect(response.data.createPost.id).toBeDefined(); + expect(response.data.createPost.title).toEqual('Hello, World!'); + expect(response.data.createPost.owner).toBeDefined(); + + const getResponse = await GRAPHQL_CLIENT_1.query( + `query { + getUser(id: "user1@test.com") { + posts { + items { + id + title + owner + author { + id + } + } + } + } + }`, + {}, + ); + expect(getResponse.data.getUser.posts.items[0].id).toBeDefined(); + expect(getResponse.data.getUser.posts.items[0].title).toEqual('Hello, World!'); + expect(getResponse.data.getUser.posts.items[0].owner).toEqual('user1@test.com'); + expect(getResponse.data.getUser.posts.items[0].author.id).toEqual('user1@test.com'); +}); + +test('Testing reading an owner protected field as a non owner', async () => { + const response1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createFieldProtected(input: { id: "1", owner: "${USERNAME1}", ownerOnly: "owner-protected" }) { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response1.data.createFieldProtected.id).toEqual('1'); + expect(response1.data.createFieldProtected.owner).toEqual(USERNAME1); + expect(response1.data.createFieldProtected.ownerOnly).toEqual(null); + + const response2 = await GRAPHQL_CLIENT_2.query( + `query { + getFieldProtected(id: "1") { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response2.data.getFieldProtected.ownerOnly).toBeNull(); + expect(response2.errors).toHaveLength(1); + + const response3 = await GRAPHQL_CLIENT_1.query( + `query { + getFieldProtected(id: "1") { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response3.data.getFieldProtected.id).toEqual('1'); + expect(response3.data.getFieldProtected.owner).toEqual(USERNAME1); + expect(response3.data.getFieldProtected.ownerOnly).toEqual('owner-protected'); +}); + +test('Test that @connection resolvers respect @model read operations.', async () => { + const response1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createOpenTopLevel(input: { id: "1", owner: "${USERNAME1}", name: "open" }) { + id + owner + name + } + }`, + {}, + ); + expect(response1.data.createOpenTopLevel.id).toEqual('1'); + expect(response1.data.createOpenTopLevel.owner).toEqual(USERNAME1); + expect(response1.data.createOpenTopLevel.name).toEqual('open'); + + const response2 = await GRAPHQL_CLIENT_2.query( + `mutation { + createConnectionProtected(input: { id: "1", owner: "${USERNAME2}", name: "closed", topLevelID: "1" }) { + id + owner + name + topLevelID + } + }`, + {}, + ); + expect(response2.data.createConnectionProtected.id).toEqual('1'); + expect(response2.data.createConnectionProtected.owner).toEqual(USERNAME2); + expect(response2.data.createConnectionProtected.name).toEqual('closed'); + + const response3 = await GRAPHQL_CLIENT_1.query( + `query { + getOpenTopLevel(id: "1") { + id + protected { + items { + id + name + owner + } + } + } + }`, + {}, + ); + expect(response3.data.getOpenTopLevel.id).toEqual('1'); + expect(response3.data.getOpenTopLevel.protected.items).toHaveLength(0); + + const response4 = await GRAPHQL_CLIENT_2.query( + `query { + getOpenTopLevel(id: "1") { + id + protected { + items { + id + name + owner + } + } + } + }`, + {}, + ); + expect(response4.data.getOpenTopLevel.id).toEqual('1'); + expect(response4.data.getOpenTopLevel.protected.items).toHaveLength(1); +}); + +// Per field auth in mutations +test('Test that owners cannot set the field of a FieldProtected object unless authorized.', async () => { + const response1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createFieldProtected(input: { id: "2", owner: "${USERNAME1}", ownerOnly: "owner-protected" }) { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response1.data.createFieldProtected.id).toEqual('2'); + expect(response1.data.createFieldProtected.owner).toEqual(USERNAME1); + expect(response1.data.createFieldProtected.ownerOnly).toEqual(null); + + const response2 = await GRAPHQL_CLIENT_1.query( + `mutation { + createFieldProtected(input: { id: "3", owner: "${USERNAME2}", ownerOnly: "owner-protected" }) { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response2.data.createFieldProtected).toBeNull(); + expect(response2.errors).toHaveLength(1); + + // The owner auth rule is on ownerOnly. Omitting the "ownerOnly" field will + // not trigger the @auth check + const response3 = await GRAPHQL_CLIENT_1.query( + `mutation { + createFieldProtected(input: { id: "4", owner: "${USERNAME2}" }) { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response3.data.createFieldProtected.id).toEqual('4'); + expect(response3.data.createFieldProtected.owner).toEqual(USERNAME2); + // The length is one because the 'ownerOnly' field is protected on reads. + // Since the caller is not the owner this will throw after the mutation succeeds + // and return partial results. + expect(response3.errors).toHaveLength(1); +}); + +test('Test that owners cannot update the field of a FieldProtected object unless authorized.', async () => { + const response1 = await GRAPHQL_CLIENT_1.query( + `mutation { + createFieldProtected(input: { owner: "${USERNAME1}", ownerOnly: "owner-protected" }) { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response1.data.createFieldProtected.id).not.toBeNull(); + expect(response1.data.createFieldProtected.owner).toEqual(USERNAME1); + expect(response1.data.createFieldProtected.ownerOnly).toEqual(null); + + const response2 = await GRAPHQL_CLIENT_2.query( + `mutation { + updateFieldProtected(input: { id: "${response1.data.createFieldProtected.id}", ownerOnly: "owner2-protected" }) { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response2.data.updateFieldProtected).toBeNull(); + expect(response2.errors).toHaveLength(1); + + // The auth rule is on ownerOnly. Omitting the "ownerOnly" field will + // not trigger the @auth check + const response3 = await GRAPHQL_CLIENT_1.query( + `mutation { + updateFieldProtected(input: { id: "${response1.data.createFieldProtected.id}", ownerOnly: "updated" }) { + id + owner + ownerOnly + } + }`, + {}, + ); + const resposne3ID = response3.data.updateFieldProtected.id; + expect(resposne3ID).toEqual(response1.data.createFieldProtected.id); + expect(response3.data.updateFieldProtected.owner).toEqual(USERNAME1); + + const response3query = await GRAPHQL_CLIENT_1.query(`query getMake1 { + getFieldProtected(id: "${resposne3ID}"){ + id + owner + ownerOnly + } + }`); + expect(response3query.data.getFieldProtected.ownerOnly).toEqual('updated'); + + // This request should succeed since we are not updating the protected field. + const response4 = await GRAPHQL_CLIENT_3.query( + `mutation { + updateFieldProtected(input: { id: "${response1.data.createFieldProtected.id}", owner: "${USERNAME3}" }) { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response4.data.updateFieldProtected.id).toEqual(response1.data.createFieldProtected.id); + expect(response4.data.updateFieldProtected.owner).toEqual(USERNAME3); + expect(response4.data.updateFieldProtected.ownerOnly).toBeNull(); + + const response5 = await GRAPHQL_CLIENT_3.query( + `query { + getFieldProtected( id: "${response1.data.createFieldProtected.id}" ) { + id + owner + ownerOnly + } + }`, + {}, + ); + expect(response5.data.getFieldProtected.ownerOnly).toEqual('updated'); +}); diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/SearchableModelTransformerV2.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/SearchableModelTransformerV2.e2e.test.ts index 3fb3a26fc72..4d1e4bdbd0d 100644 --- a/packages/graphql-transformers-e2e-tests/src/__tests__/SearchableModelTransformerV2.e2e.test.ts +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/SearchableModelTransformerV2.e2e.test.ts @@ -63,12 +63,12 @@ const createEntries = async () => { await waitForESPropagate(); }; -const waitForESPropagate = async (initialWaitSeconds = 5, maxRetryCount = 5 ) => { +const waitForESPropagate = async (initialWaitSeconds = 5, maxRetryCount = 5) => { const expectedCount = 8; let waitInMilliseconds = initialWaitSeconds * 1000; let currentRetryCount = 0; let searchResponse; - + do { await new Promise(r => setTimeout(r, waitInMilliseconds)); searchResponse = await GRAPHQL_CLIENT.query( @@ -84,7 +84,7 @@ const waitForESPropagate = async (initialWaitSeconds = 5, maxRetryCount = 5 ) => currentRetryCount += 1; waitInMilliseconds = waitInMilliseconds * 2; } while (searchResponse.data.searchPosts?.items?.length < expectedCount && currentRetryCount <= maxRetryCount); -} +}; beforeAll(async () => { const validSchema = ` @@ -109,6 +109,7 @@ beforeAll(async () => { const transformer = new GraphQLTransform({ featureFlags, transformers: [new ModelTransformer(), new SearchableModelTransformer()], + sandboxModeEnabled: true, }); try { await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); @@ -158,7 +159,7 @@ test('query for aggregate scalar results', async () => { searchPosts(aggregates: [{ name: "Minimum", type: min, - field: "ups" + field: ups }]) { aggregateItems { name @@ -184,7 +185,7 @@ test('query for aggregate bucket results', async () => { searchPosts(aggregates: [{ name: "Terms", type: terms, - field: "title" + field: title }]) { aggregateItems { name @@ -213,12 +214,12 @@ test('query for multiple aggregates', async () => { searchPosts(aggregates: [{ name: "Minimum", type: min, - field: "ups" + field: ups }, { name: "Terms", type: terms, - field: "title" + field: title }]) { aggregateItems { name diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/SearchableWithAuthV2.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/SearchableWithAuthV2.e2e.test.ts new file mode 100644 index 00000000000..a4918323d9c --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/SearchableWithAuthV2.e2e.test.ts @@ -0,0 +1,925 @@ +import { SearchableModelTransformer } from '@aws-amplify/graphql-searchable-transformer'; +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { S3Client } from '../S3Client'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import moment from 'moment'; +import { S3, CognitoIdentityServiceProvider as CognitoClient, CognitoIdentity } from 'aws-sdk'; +import { AWS } from '@aws-amplify/core'; +import { Auth } from 'aws-amplify'; +import { IAMHelper } from '../IAMHelper'; +import gql from 'graphql-tag'; +import { + addUserToGroup, + authenticateUser, + configureAmplify, + createGroup, + createIdentityPool, + createUserPool, + createUserPoolClient, + signupUser, +} from '../cognitoUtils'; +// to deal with bug in cognito-identity-js +(global as any).fetch = require('node-fetch'); +// To overcome of the way of how AmplifyJS picks up currentUserCredentials +const anyAWS = AWS as any; +if (anyAWS && anyAWS.config && anyAWS.config.credentials) { + delete anyAWS.config.credentials; +} + +// tslint:disable: no-magic-numbers +jest.setTimeout(60000 * 60); +const AWS_REGION = 'us-west-2'; + +const cf = new CloudFormationClient(AWS_REGION); +const customS3Client = new S3Client(AWS_REGION); +const awsS3Client = new S3({ region: AWS_REGION }); +const cognitoClient = new CognitoClient({ apiVersion: '2016-04-19', region: AWS_REGION }); +const identityClient = new CognitoIdentity({ apiVersion: '2014-06-30', region: AWS_REGION }); +const iamHelper = new IAMHelper(AWS_REGION); + +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `SearchableAuthV2Tests-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `searchable-authv2-tests-bucket-${BUILD_TIMESTAMP}`; +const LOCAL_FS_BUILD_DIR = '/tmp/searchable_authv2_tests/'; +const S3_ROOT_DIR_KEY = 'deployments'; +const AUTH_ROLE_NAME = `${STACK_NAME}-authRole`; +const UNAUTH_ROLE_NAME = `${STACK_NAME}-unauthRole`; +let USER_POOL_ID: string; +let IDENTITY_POOL_ID: string; +let GRAPHQL_ENDPOINT: string; +let API_KEY: string; + +/** + * Client 1 is logged in and has no group memberships. + */ +let GRAPHQL_CLIENT_1: AWSAppSyncClient = undefined; + +/** + * Client 2 is logged in and is a member of the admin and writer group. + */ +let GRAPHQL_CLIENT_2: AWSAppSyncClient = undefined; + +/** + * Client 3 is logged in and has no group memberships. + */ +let GRAPHQL_CLIENT_3: AWSAppSyncClient = undefined; + +/** + * Client 4 is logged in and is a member of the writer group + */ +let GRAPHQL_CLIENT_4: AWSAppSyncClient = undefined; + +/** + * Auth IAM Client + */ +let GRAPHQL_IAM_AUTH_CLIENT: AWSAppSyncClient = undefined; + +/** + * API Key Client + */ +let GRAPHQL_APIKEY_CLIENT: AWSAppSyncClient = undefined; + +const USERNAME1 = 'user1@test.com'; +const USERNAME2 = 'user2@test.com'; +const USERNAME3 = 'user3@test.com'; +const USERNAME4 = 'user4@test.com'; +const TMP_PASSWORD = 'Password123!'; +const REAL_PASSWORD = 'Password1234!'; +const WRITER_GROUP_NAME = 'writer'; +const ADMIN_GROUP_NAME = 'admin'; + +beforeAll(async () => { + const validSchema = ` + # Owners and Users in writer group + # can execute crud operations their owned records. + type Comment @model + @searchable + @auth(rules: [ + { allow: owner } + { allow: groups, groups: ["writer"]} + ]) { + id: ID! + content: String + } + # only users in the admin group are authorized to view entries in DynamicContent + type Todo @model + @searchable + @auth(rules: [ + { allow: groups, groupsField: "groups"} + ]) { + id: ID! + groups: String + content: String + } + # users with apikey perform crud operations on Post except for secret + # only users with auth role (iam) can view the secret + # only private iam roles are allowed to run aggregations + type Post @model + @searchable + @auth(rules: [ + { allow: public, provider: apiKey } + { allow: private, provider: iam } + ]) { + id: ID! + content: String + secret: String @auth(rules: [{ allow: private, provider: iam }]) + } + # only allow static group and dynamic group to have access on field + type Blog + @model + @searchable + @auth(rules: [{ allow: owner }, { allow: groups, groups: ["admin"] }, { allow: groups, groupsField: "groupsField" }]) { + id: ID! + title: String + ups: Int + downs: Int + percentageUp: Float + isPublished: Boolean + createdAt: AWSDateTime + updatedAt: AWSDateTime + owner: String + groupsField: String + # as a member of admin and member within groupsField I can run aggregations on secret + secret: String @auth(rules: [{ allow: groups, groups: ["admin"] }, { allow: groups, groupsField: "groupsField" }]) + } + `; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'API_KEY', + apiKeyConfig: { + description: 'E2E Test API Key', + apiKeyExpirationDays: 300, + }, + }, + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [new ModelTransformer(), new SearchableModelTransformer(), new AuthTransformer({ addAwsIamAuthInOutputSchema: false })], + }); + const userPoolResponse = await createUserPool(cognitoClient, `UserPool${STACK_NAME}`); + USER_POOL_ID = userPoolResponse.UserPool.Id; + const userPoolClientResponse = await createUserPoolClient(cognitoClient, USER_POOL_ID, `UserPool${STACK_NAME}`); + const userPoolClientId = userPoolClientResponse.UserPoolClient.ClientId; + // create auth and unauthroles + const { authRole, unauthRole } = await iamHelper.createRoles(AUTH_ROLE_NAME, UNAUTH_ROLE_NAME); + // create identitypool + IDENTITY_POOL_ID = await createIdentityPool(identityClient, `IdentityPool${STACK_NAME}`, { + authRoleArn: authRole.Arn, + unauthRoleArn: unauthRole.Arn, + providerName: `cognito-idp.${AWS_REGION}.amazonaws.com/${USER_POOL_ID}`, + clientId: userPoolClientId, + }); + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + console.error(`Failed to create bucket: ${e}`); + } + try { + const out = transformer.transform(validSchema); + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + { AuthCognitoUserPoolId: USER_POOL_ID, authRoleName: authRole.RoleName, unauthRoleName: unauthRole.RoleName }, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + // Arbitrary wait to make sure everything is ready. + await cf.wait(120, () => Promise.resolve()); + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const getApiKey = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIApiKeyOutput); + GRAPHQL_ENDPOINT = getApiEndpoint(finishedStack.Outputs); + API_KEY = getApiKey(finishedStack.Outputs); + + expect(API_KEY).toBeDefined(); + expect(GRAPHQL_ENDPOINT).toBeDefined(); + expect(userPoolClientId).toBeTruthy(); + + // Configure Amplify, create users, and sign in + configureAmplify(USER_POOL_ID, userPoolClientId, IDENTITY_POOL_ID); + + await signupUser(USER_POOL_ID, USERNAME1, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME2, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME3, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME4, TMP_PASSWORD); + await createGroup(USER_POOL_ID, WRITER_GROUP_NAME); + await createGroup(USER_POOL_ID, ADMIN_GROUP_NAME); + await addUserToGroup(WRITER_GROUP_NAME, USERNAME4, USER_POOL_ID); + await addUserToGroup(WRITER_GROUP_NAME, USERNAME2, USER_POOL_ID); + await addUserToGroup(ADMIN_GROUP_NAME, USERNAME2, USER_POOL_ID); + + const authResAfterGroup: any = await authenticateUser(USERNAME1, TMP_PASSWORD, REAL_PASSWORD); + const idToken = authResAfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_1 = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + disableOffline: true, + auth: { + type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS, + jwtToken: () => idToken, + }, + }); + + const authRes2AfterGroup: any = await authenticateUser(USERNAME2, TMP_PASSWORD, REAL_PASSWORD); + const idToken2 = authRes2AfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_2 = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + disableOffline: true, + auth: { + type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS, + jwtToken: () => idToken2, + }, + }); + + const authRes3: any = await authenticateUser(USERNAME3, TMP_PASSWORD, REAL_PASSWORD); + const idToken3 = authRes3.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_3 = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + disableOffline: true, + auth: { + type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS, + jwtToken: () => idToken3, + }, + }); + + const authRes4: any = await authenticateUser(USERNAME4, TMP_PASSWORD, REAL_PASSWORD); + const idToken4 = authRes4.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_4 = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + disableOffline: true, + auth: { + type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS, + jwtToken: () => idToken4, + }, + }); + + // sign out previous cognito user + await Auth.signOut(); + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const authCreds = await Auth.currentCredentials(); + GRAPHQL_IAM_AUTH_CLIENT = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + disableOffline: true, + auth: { + type: AUTH_TYPE.AWS_IAM, + credentials: authCreds, + }, + }); + + GRAPHQL_APIKEY_CLIENT = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + auth: { + type: AUTH_TYPE.API_KEY, + apiKey: API_KEY, + }, + disableOffline: true, + }); + + // Create sample mutations to test search queries + await createEntries(); + } catch (e) { + console.error(e); + throw e; + } +}); + +afterAll(async () => { + await cleanupStackAfterTest( + BUCKET_NAME, + STACK_NAME, + cf, + { cognitoClient, userPoolId: USER_POOL_ID }, + { identityClient, identityPoolId: IDENTITY_POOL_ID }, + ); + try { + await iamHelper.deleteRole(AUTH_ROLE_NAME); + } catch (e) { + console.warn(`Error during auth role cleanup ${e}`); + } + try { + await iamHelper.deleteRole(UNAUTH_ROLE_NAME); + } catch (e) { + console.warn(`Error during unauth role cleanup ${e}`); + } +}); + +/** + * Tests + */ + +// cognito owner check +test('test Comments as owner', async () => { + const ownerResponse: any = await GRAPHQL_CLIENT_1.query({ + query: gql` + query SearchComments { + searchComments { + items { + id + content + owner + } + nextToken + } + } + `, + }); + expect(ownerResponse.data.searchComments).toBeDefined(); + expect(ownerResponse.data.searchComments.items.length).toEqual(1); + expect(ownerResponse.data.searchComments.items[0].content).toEqual('ownerContent'); +}); + +// cognito static group check +test('test Comments as user in writer group', async () => { + const writerResponse: any = await GRAPHQL_CLIENT_2.query({ + query: gql` + query SearchComments { + searchComments { + items { + id + content + owner + } + nextToken + } + } + `, + }); + expect(writerResponse.data.searchComments).toBeDefined(); + expect(writerResponse.data.searchComments.items.length).toEqual(4); + // only ownerContent should have the owner name + // because the group permission was met we did not populate an owner field + // therefore there is no owner + expect(writerResponse.data.searchComments.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + content: 'ownerContent', + owner: USERNAME1, + }), + expect.objectContaining({ + id: expect.any(String), + content: 'content1', + owner: null, + }), + expect.objectContaining({ + id: expect.any(String), + content: 'content1', + owner: null, + }), + expect.objectContaining({ + id: expect.any(String), + content: 'content3', + owner: null, + }), + ]), + ); +}); + +// cognito test as unauthorized user +test('test Comments as user that is not an owner nor is in writer group', async () => { + const user3Response: any = await GRAPHQL_CLIENT_3.query({ + query: gql` + query SearchComments { + searchComments { + items { + id + content + owner + } + nextToken + } + } + `, + }); + expect(user3Response.data.searchComments).toBeDefined(); + expect(user3Response.data.searchComments.items.length).toEqual(0); + expect(user3Response.data.searchComments.nextToken).toBeNull(); +}); + +// cognito dynamic group check +test('test Todo as user in the dynamic group admin', async () => { + const adminResponse: any = await GRAPHQL_CLIENT_2.query({ + query: gql` + query SearchTodos { + searchTodos { + items { + id + groups + content + } + nextToken + } + } + `, + }); + expect(adminResponse.data.searchTodos).toBeDefined(); + expect(adminResponse.data.searchTodos.items.length).toEqual(3); + expect(adminResponse.data.searchTodos.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + content: 'adminContent1', + groups: ADMIN_GROUP_NAME, + }), + expect.objectContaining({ + id: expect.any(String), + content: 'adminContent2', + groups: ADMIN_GROUP_NAME, + }), + expect.objectContaining({ + id: expect.any(String), + content: 'adminContent3', + groups: ADMIN_GROUP_NAME, + }), + ]), + ); +}); + +// iam test +test('test Post as authorized user', async () => { + const authUser: any = await GRAPHQL_IAM_AUTH_CLIENT.query({ + query: gql` + query SearchPosts { + searchPosts { + items { + id + content + secret + } + nextToken + } + } + `, + }); + expect(authUser.data.searchPosts).toBeDefined(); + expect(authUser.data.searchPosts.items.length).toEqual(4); + expect(authUser.data.searchPosts.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + content: 'post2', + secret: 'post2secret', + }), + expect.objectContaining({ + id: expect.any(String), + content: 'post1', + secret: 'post1secret', + }), + expect.objectContaining({ + id: expect.any(String), + content: 'post3', + secret: 'post3secret', + }), + expect.objectContaining({ + id: expect.any(String), + content: 'publicPost', + secret: null, + }), + ]), + ); +}); + +// test apikey 2nd scenario +test('test searchPosts with apikey and secret removed', async () => { + const apiKeyResponse: any = await GRAPHQL_APIKEY_CLIENT.query({ + query: gql` + query SearchPosts { + searchPosts { + items { + id + content + } + nextToken + } + } + `, + }); + expect(apiKeyResponse.data.searchPosts).toBeDefined(); + expect(apiKeyResponse.data.searchPosts.items).toHaveLength(4); + expect(apiKeyResponse.data.searchPosts.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + content: 'post2', + }), + expect.objectContaining({ + id: expect.any(String), + content: 'post1', + }), + expect.objectContaining({ + id: expect.any(String), + content: 'post3', + }), + expect.objectContaining({ + id: expect.any(String), + content: 'publicPost', + }), + ]), + ); +}); + +// test iam/apiKey schema with unauth user +test('test post as an cognito user that is not allowed in this schema', async () => { + try { + await GRAPHQL_CLIENT_3.query({ + query: gql` + query SearchPosts { + searchPosts { + items { + id + content + secret + } + nextToken + } + } + `, + }); + } catch (err) { + expect(err.graphQLErrors[0].errorType).toEqual('Unauthorized'); + expect(err.graphQLErrors[0].message).toEqual('Not Authorized to access searchPosts on type Query'); + } +}); + +test('test that apikey is not allowed to query aggregations on secret for post', async () => { + try { + await GRAPHQL_APIKEY_CLIENT.query({ + query: gql` + query aggSearch { + searchPosts(aggregates: [{ name: "Terms", type: terms, field: secret }]) { + aggregateItems { + name + result { + ... on SearchableAggregateBucketResult { + buckets { + doc_count + key + } + } + } + } + } + } + `, + }); + } catch (err) { + expect(err.graphQLErrors[0].errorType).toEqual('Unauthorized'); + expect(err.graphQLErrors[0].message).toEqual('Unauthorized to run aggregation on field: secret'); + } +}); + +test('test that iam can run aggregations on secret field', async () => { + try { + const response: any = await GRAPHQL_IAM_AUTH_CLIENT.query({ + query: gql` + query aggSearch { + searchPosts(aggregates: [{ name: "Terms", type: terms, field: secret }]) { + aggregateItems { + name + result { + ... on SearchableAggregateBucketResult { + buckets { + doc_count + key + } + } + } + } + } + } + `, + }); + expect(response.data.searchPosts).toBeDefined(); + expect(response.data.searchPosts.aggregateItems).toHaveLength(1); + const aggregateItem = response.data.searchPosts.aggregateItems[0]; + expect(aggregateItem.name).toEqual('Terms'); + expect(aggregateItem.result.buckets).toHaveLength(3); + expect(aggregateItem.result.buckets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + doc_count: 1, + key: 'post1secret', + }), + expect.objectContaining({ + doc_count: 1, + key: 'post2secret', + }), + expect.objectContaining({ + doc_count: 1, + key: 'post3secret', + }), + ]), + ); + } catch (err) { + expect(err).not.toBeDefined(); + } +}); + +test('test that admin can run aggregate query on protected field', async () => { + try { + const response: any = await GRAPHQL_CLIENT_2.query({ + query: gql` + query { + searchBlogs(aggregates: [{ name: "Terms", type: terms, field: secret }]) { + aggregateItems { + name + result { + ... on SearchableAggregateBucketResult { + buckets { + doc_count + key + } + } + } + } + } + } + `, + }); + expect(response.data.searchBlogs).toBeDefined(); + expect(response.data.searchBlogs.aggregateItems); + const aggregateItem = response.data.searchBlogs.aggregateItems[0]; + expect(aggregateItem.name).toEqual('Terms'); + expect(aggregateItem.result.buckets).toHaveLength(2); + expect(aggregateItem.result.buckets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + doc_count: 2, + key: `${USERNAME1}secret`, + }), + expect.objectContaining({ + doc_count: 2, + key: `${USERNAME4}secret`, + }), + ]), + ); + } catch (err) { + expect(err).not.toBeDefined(); + } +}); + +test('test that member in writer group has writer group auth when running aggregate query', async () => { + try { + const response: any = await GRAPHQL_CLIENT_4.query({ + query: gql` + query { + searchBlogs(aggregates: [{ name: "Terms", type: terms, field: secret }]) { + aggregateItems { + name + result { + ... on SearchableAggregateBucketResult { + buckets { + doc_count + key + } + } + } + } + } + } + `, + }); + expect(response.data.searchBlogs).toBeDefined(); + expect(response.data.searchBlogs.aggregateItems); + const aggregateItem = response.data.searchBlogs.aggregateItems[0]; + expect(aggregateItem.name).toEqual('Terms'); + expect(aggregateItem.result.buckets).toHaveLength(2); + expect(aggregateItem.result.buckets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + doc_count: 2, + key: `${USERNAME1}secret`, + }), + expect.objectContaining({ + doc_count: 1, + key: `${USERNAME4}secret`, + }), + ]), + ); + } catch (err) { + expect(err).not.toBeDefined(); + } +}); + +test('test that an owner does not get any results for the agg query on the secret field', async () => { + try { + const response: any = await GRAPHQL_CLIENT_1.query({ + query: gql` + query { + searchBlogs(aggregates: [{ name: "Terms", type: terms, field: secret }]) { + aggregateItems { + name + result { + ... on SearchableAggregateBucketResult { + buckets { + doc_count + key + } + } + } + } + } + } + `, + }); + expect(response.data.searchBlogs).toBeDefined(); + expect(response.data.searchBlogs.aggregateItems); + const aggregateItem = response.data.searchBlogs.aggregateItems[0]; + expect(aggregateItem.name).toEqual('Terms'); + expect(aggregateItem.result.buckets).toHaveLength(0); + } catch (err) { + expect(err).not.toBeDefined(); + } +}); +test('test that an owner can run aggregations on records which belong to them', async () => { + try { + const response: any = await GRAPHQL_CLIENT_1.query({ + query: gql` + query { + searchBlogs(aggregates: [{ name: "Terms", type: terms, field: title }]) { + aggregateItems { + name + result { + ... on SearchableAggregateBucketResult { + buckets { + doc_count + key + } + } + } + } + } + } + `, + }); + expect(response.data.searchBlogs).toBeDefined(); + expect(response.data.searchBlogs.aggregateItems); + const aggregateItem = response.data.searchBlogs.aggregateItems[0]; + expect(aggregateItem.name).toEqual('Terms'); + expect(aggregateItem.result.buckets).toHaveLength(1); + expect(aggregateItem.result.buckets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + doc_count: 2, + key: 'cooking', + }), + ]), + ); + } catch (err) { + expect(err).not.toBeDefined(); + } +}); +/** + * Input types + * */ +type CreateCommentInput = { + id?: string | null; + content?: string | null; +}; + +type CreateTodoInput = { + id?: string | null; + groups?: string | null; + content?: string | null; +}; + +type CreatePostInput = { + id?: string | null; + content?: string | null; + secret?: string | null; +}; +export type CreateBlogInput = { + id?: string; + title?: string; + ups?: number; + owner?: string; + groupsField?: string; + secret?: string; +}; + +// mutations +async function createComment(client: AWSAppSyncClient, input: CreateCommentInput) { + const create = gql` + mutation CreateComment($input: CreateCommentInput!) { + createComment(input: $input) { + id + content + owner + } + } + `; + return await client.mutate({ mutation: create, variables: { input } }); +} +async function createTodo(client: AWSAppSyncClient, input: CreateTodoInput) { + const create = gql` + mutation CreateTodo($input: CreateTodoInput!) { + createTodo(input: $input) { + id + groups + content + } + } + `; + return await client.mutate({ mutation: create, variables: { input } }); +} +async function createPost(client: AWSAppSyncClient, input: CreatePostInput) { + const create = gql` + mutation CreatePost($input: CreatePostInput!) { + createPost(input: $input) { + id + content + } + } + `; + return await client.mutate({ mutation: create, variables: { input } }); +} + +async function createBlog(client: AWSAppSyncClient, input: CreateBlogInput) { + const create = gql` + mutation CreateBlog($input: CreateBlogInput!) { + createBlog(input: $input) { + id + title + ups + downs + percentageUp + isPublished + createdAt + updatedAt + owner + groupsField + secret + } + } + `; + return await client.mutate({ mutation: create, variables: { input } }); +} + +const createEntries = async () => { + await createComment(GRAPHQL_CLIENT_1, { + content: 'ownerContent', + }); + try { + await createPost(GRAPHQL_APIKEY_CLIENT, { content: 'publicPost' }); + } catch (err) { + // will err since the secret is in the fields response though the post should still go through + } + for (let i = 1; i < 4; i++) { + await createComment(GRAPHQL_CLIENT_2, { content: `content${i}` }); + await createTodo(GRAPHQL_CLIENT_2, { groups: 'admin', content: `adminContent${i}` }); + await createPost(GRAPHQL_IAM_AUTH_CLIENT, { content: `post${i}`, secret: `post${i}secret` }); + } + await createBlog(GRAPHQL_CLIENT_2, { + groupsField: WRITER_GROUP_NAME, + owner: USERNAME1, + secret: `${USERNAME1}secret`, + ups: 10, + title: 'cooking', + }); + await createBlog(GRAPHQL_CLIENT_2, { + groupsField: WRITER_GROUP_NAME, + owner: USERNAME1, + secret: `${USERNAME1}secret`, + ups: 10, + title: 'cooking', + }); + await createBlog(GRAPHQL_CLIENT_2, { + groupsField: WRITER_GROUP_NAME, + owner: USERNAME4, + secret: `${USERNAME4}secret`, + ups: 25, + title: 'golfing', + }); + await createBlog(GRAPHQL_CLIENT_2, { groupsField: 'editor', owner: USERNAME4, secret: `${USERNAME4}secret`, ups: 10, title: 'cooking' }); + // Waiting for the ES Cluster + Streaming Lambda infra to be setup + await cf.wait(120, () => Promise.resolve()); +}; + +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/SubscriptionsWithAuthV2.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/SubscriptionsWithAuthV2.e2e.test.ts new file mode 100644 index 00000000000..ed08239d920 --- /dev/null +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/SubscriptionsWithAuthV2.e2e.test.ts @@ -0,0 +1,1110 @@ +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; +import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; +import { CloudFormationClient } from '../CloudFormationClient'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { Output } from 'aws-sdk/clients/cloudformation'; +import { CognitoIdentityServiceProvider as CognitoClient, S3, CognitoIdentity } from 'aws-sdk'; +import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; +import { AWS } from '@aws-amplify/core'; +import { Auth } from 'aws-amplify'; +import gql from 'graphql-tag'; +import { S3Client } from '../S3Client'; +import { cleanupStackAfterTest, deploy } from '../deployNestedStacks'; +import { default as moment } from 'moment'; +import { + createUserPool, + createUserPoolClient, + createGroup, + addUserToGroup, + configureAmplify, + signupUser, + authenticateUser, + createIdentityPool, +} from '../cognitoUtils'; +import 'isomorphic-fetch'; +import { API } from 'aws-amplify'; +import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api'; +import { withTimeOut } from '../promiseWithTimeout'; +import { IAMHelper } from '../IAMHelper'; +import * as Observable from 'zen-observable'; + +// tslint:disable: no-use-before-declare +// to deal with bug in cognito-identity-js +(global as any).fetch = require('node-fetch'); +// to deal with subscriptions in node env +(global as any).WebSocket = require('ws'); + +// To overcome of the way of how AmplifyJS picks up currentUserCredentials +const anyAWS = AWS as any; +if (anyAWS && anyAWS.config && anyAWS.config.credentials) { + delete anyAWS.config.credentials; +} + +// delay times +const SUBSCRIPTION_DELAY = 10000; +const PROPAGATION_DELAY = 5000; +const JEST_TIMEOUT = 2000000; +const SUBSCRIPTION_TIMEOUT = 10000; + +jest.setTimeout(JEST_TIMEOUT); + +function outputValueSelector(key: string) { + return (outputs: Output[]) => { + const output = outputs.find((o: Output) => o.OutputKey === key); + return output ? output.OutputValue : null; + }; +} + +const AWS_REGION = 'us-west-2'; +const cf = new CloudFormationClient(AWS_REGION); +const customS3Client = new S3Client(AWS_REGION); +const cognitoClient = new CognitoClient({ apiVersion: '2016-04-19', region: AWS_REGION }); +const identityClient = new CognitoIdentity({ apiVersion: '2014-06-30', region: AWS_REGION }); +const iamHelper = new IAMHelper(AWS_REGION); +const awsS3Client = new S3({ region: AWS_REGION }); + +// stack info +const BUILD_TIMESTAMP = moment().format('YYYYMMDDHHmmss'); +const STACK_NAME = `SubscriptionAuthV2Tests-${BUILD_TIMESTAMP}`; +const BUCKET_NAME = `subscription-authv2-tests-bucket-${BUILD_TIMESTAMP}`; +const LOCAL_FS_BUILD_DIR = '/tmp/subscription_authv2_tests/'; +const S3_ROOT_DIR_KEY = 'deployments'; +const AUTH_ROLE_NAME = `${STACK_NAME}-authRole`; +const UNAUTH_ROLE_NAME = `${STACK_NAME}-unauthRole`; +let USER_POOL_ID: string; +let IDENTITY_POOL_ID: string; +let GRAPHQL_ENDPOINT: string; +let API_KEY: string; + +/** + * Client 1 is logged in and is a member of the Admin group. + */ +let GRAPHQL_CLIENT_1: AWSAppSyncClient = undefined; + +/** + * Client 2 is logged in and is a member of the Devs group. + */ +let GRAPHQL_CLIENT_2: AWSAppSyncClient = undefined; + +/** + * Auth IAM Client + */ +let GRAPHQL_IAM_AUTH_CLIENT: AWSAppSyncClient = undefined; + +const USERNAME1 = 'user1@test.com'; +const USERNAME2 = 'user2@test.com'; +const USERNAME3 = 'user3@test.com'; +const TMP_PASSWORD = 'Password123!'; +const REAL_PASSWORD = 'Password1234!'; + +const INSTRUCTOR_GROUP_NAME = 'Instructor'; +const MEMBER_GROUP_NAME = 'Member'; +const ADMIN_GROUP_NAME = 'Admin'; + +// interface inputs +interface MemberInput { + id: string; + name?: string; + createdAt?: string; + updatedAt?: string; +} + +interface CreateStudentInput { + id?: string; + name?: string; + email?: string; + ssn?: string; +} + +interface UpdateStudentInput { + id: string; + name?: string; + email?: string; + ssn?: string; +} + +interface CreatePostInput { + id?: string; + title: string; + postOwner: string; +} + +interface CreateTodoInput { + id?: string; + name?: string; + description?: string; +} + +interface UpdateTodoInput { + id: string; + name?: string; + description?: string; +} + +interface DeleteTypeInput { + id: string; +} + +beforeEach(async () => { + try { + await Auth.signOut(); + } catch (ex) { + // don't need to fail tests on this error + } +}); + +beforeAll(async () => { + const validSchema = ` + # Owners may update their owned records. + # Instructors may create Student records. + # Any authenticated user may view Student names & emails. + # Only Owners can see the ssn + + type Student @model + @auth(rules: [ + {allow: owner} + {allow: groups, groups: ["Instructor"]} + ]) { + id: String, + name: String, + email: AWSEmail, + ssn: String @auth(rules: [{allow: owner}]) + } + + type Member @model + @auth(rules: [ + { allow: groups, groups: ["Admin"] } + { allow: groups, groups: ["Member"], operations: [read] } + ]) { + id: ID + name: String + createdAt: AWSDateTime + updatedAt: AWSDateTime + } + + type Post @model + @auth(rules: [ + { allow: owner, ownerField: "postOwner" } + { allow: private, operations: [read], provider: iam } + ]) + { + id: ID! + title: String + postOwner: String + } + + type Todo @model @auth(rules: [ + { allow: private, provider: iam } + { allow: public } + ]){ + id: ID! + name: String @auth(rules: [ + { allow: private, provider: iam } + ]) + description: String + }`; + const transformer = new GraphQLTransform({ + authConfig: { + defaultAuthentication: { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'API_KEY', + apiKeyConfig: { + description: 'E2E Test API Key', + apiKeyExpirationDays: 300, + }, + }, + { + authenticationType: 'AWS_IAM', + }, + ], + }, + transformers: [new ModelTransformer(), new AuthTransformer({ addAwsIamAuthInOutputSchema: false })], + }); + + try { + await awsS3Client.createBucket({ Bucket: BUCKET_NAME }).promise(); + } catch (e) { + console.error(`Failed to create bucket: ${e}`); + } + + // create userpool + const userPoolResponse = await createUserPool(cognitoClient, `UserPool${STACK_NAME}`); + USER_POOL_ID = userPoolResponse.UserPool.Id; + const userPoolClientResponse = await createUserPoolClient(cognitoClient, USER_POOL_ID, `UserPool${STACK_NAME}`); + const userPoolClientId = userPoolClientResponse.UserPoolClient.ClientId; + // create auth and unauthroles + const { authRole, unauthRole } = await iamHelper.createRoles(AUTH_ROLE_NAME, UNAUTH_ROLE_NAME); + // create identitypool + IDENTITY_POOL_ID = await createIdentityPool(identityClient, `IdentityPool${STACK_NAME}`, { + authRoleArn: authRole.Arn, + unauthRoleArn: unauthRole.Arn, + providerName: `cognito-idp.${AWS_REGION}.amazonaws.com/${USER_POOL_ID}`, + clientId: userPoolClientId, + }); + const out = transformer.transform(validSchema); + const finishedStack = await deploy( + customS3Client, + cf, + STACK_NAME, + out, + { AuthCognitoUserPoolId: USER_POOL_ID, authRoleName: authRole.RoleName, unauthRoleName: unauthRole.RoleName }, + LOCAL_FS_BUILD_DIR, + BUCKET_NAME, + S3_ROOT_DIR_KEY, + BUILD_TIMESTAMP, + ); + + expect(finishedStack).toBeDefined(); + const getApiEndpoint = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIEndpointOutput); + const getApiKey = outputValueSelector(ResourceConstants.OUTPUTS.GraphQLAPIApiKeyOutput); + GRAPHQL_ENDPOINT = getApiEndpoint(finishedStack.Outputs); + + API_KEY = getApiKey(finishedStack.Outputs); + expect(API_KEY).toBeTruthy(); + + // Verify we have all the details + expect(GRAPHQL_ENDPOINT).toBeTruthy(); + expect(USER_POOL_ID).toBeTruthy(); + expect(userPoolClientId).toBeTruthy(); + + // Configure Amplify, create users, and sign in + configureAmplify(USER_POOL_ID, userPoolClientId, IDENTITY_POOL_ID); + + await signupUser(USER_POOL_ID, USERNAME1, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME2, TMP_PASSWORD); + await signupUser(USER_POOL_ID, USERNAME3, TMP_PASSWORD); + + await createGroup(USER_POOL_ID, INSTRUCTOR_GROUP_NAME); + await createGroup(USER_POOL_ID, MEMBER_GROUP_NAME); + await createGroup(USER_POOL_ID, ADMIN_GROUP_NAME); + await addUserToGroup(ADMIN_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(MEMBER_GROUP_NAME, USERNAME2, USER_POOL_ID); + await addUserToGroup(INSTRUCTOR_GROUP_NAME, USERNAME1, USER_POOL_ID); + await addUserToGroup(INSTRUCTOR_GROUP_NAME, USERNAME2, USER_POOL_ID); + + // authenticate user3 we'll use amplify api for subscription calls + await authenticateUser(USERNAME3, TMP_PASSWORD, REAL_PASSWORD); + + const authResAfterGroup: any = await authenticateUser(USERNAME1, TMP_PASSWORD, REAL_PASSWORD); + const idToken = authResAfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_1 = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + disableOffline: true, + auth: { + type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS, + jwtToken: idToken, + }, + }); + const authRes2AfterGroup: any = await authenticateUser(USERNAME2, TMP_PASSWORD, REAL_PASSWORD); + const idToken2 = authRes2AfterGroup.getIdToken().getJwtToken(); + GRAPHQL_CLIENT_2 = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + disableOffline: true, + auth: { + type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS, + jwtToken: idToken2, + }, + }); + + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const authCreds = await Auth.currentCredentials(); + GRAPHQL_IAM_AUTH_CLIENT = new AWSAppSyncClient({ + url: GRAPHQL_ENDPOINT, + region: AWS_REGION, + disableOffline: true, + auth: { + type: AUTH_TYPE.AWS_IAM, + credentials: authCreds, + }, + }); + // Wait for any propagation to avoid random + // "The security token included in the request is invalid" errors + await new Promise(res => setTimeout(res, PROPAGATION_DELAY)); +}); + +afterAll(async () => { + await cleanupStackAfterTest( + BUCKET_NAME, + STACK_NAME, + cf, + { cognitoClient, userPoolId: USER_POOL_ID }, + { identityClient, identityPoolId: IDENTITY_POOL_ID }, + ); + await iamHelper.deleteRole(AUTH_ROLE_NAME); + await iamHelper.deleteRole(UNAUTH_ROLE_NAME); +}); + +/** + * Tests + */ + +// tests using cognito +test('Test that only authorized members are allowed to view subscriptions', async () => { + // subscribe to create students as user 2 + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnCreateStudent { + onCreateStudent { + id + name + email + ssn + owner + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, _) => { + subscription = observer.subscribe((event: any) => { + const student = event.value.data.onCreateStudent; + subscription.unsubscribe(); + expect(student.name).toEqual('student1'); + expect(student.email).toEqual('student1@domain.com'); + expect(student.ssn).toBeNull(); + resolve(undefined); + }); + }); + + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + await createStudent(GRAPHQL_CLIENT_1, { + name: 'student1', + email: 'student1@domain.com', + ssn: 'AAA-01-SSSS', + }); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnCreateStudent Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('Test that an user not in the group is not allowed to view the subscription', async () => { + // subscribe to create students as user 3 + // const observer = onCreateStudent(GRAPHQL_CLIENT_3) + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME3, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnCreateStudent { + onCreateStudent { + id + name + email + ssn + owner + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, _) => { + subscription = observer.subscribe({ + error: (err: any) => { + expect(err.error.errors[0].message).toEqual( + 'Connection failed: {"errors":[{"errorType":"Unauthorized","message":"Not Authorized to access onCreateStudent on type Student"}]}', + ); + resolve(undefined); + }, + }); + }); + + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + await createStudent(GRAPHQL_CLIENT_1, { + name: 'student2', + email: 'student2@domain.com', + ssn: 'BBB-00-SNSN', + }); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('Test a subscription on update', async () => { + // subscribe to update students as user 2 + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME2, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnUpdateStudent { + onUpdateStudent { + id + name + email + ssn + owner + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, _) => { + subscription = observer.subscribe((event: any) => { + const student = event.value.data.onUpdateStudent; + subscription.unsubscribe(); + expect(student.id).toEqual(student3ID); + expect(student.name).toEqual('student3'); + expect(student.email).toEqual('emailChanged@domain.com'); + expect(student.ssn).toBeNull(); + resolve(undefined); + }); + }); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + const student3 = await createStudent(GRAPHQL_CLIENT_1, { + name: 'student3', + email: 'changeThisEmail@domain.com', + ssn: 'CCC-01-SNSN', + }); + expect(student3.data.createStudent).toBeDefined(); + const student3ID = student3.data.createStudent.id; + expect(student3.data.createStudent.name).toEqual('student3'); + expect(student3.data.createStudent.email).toEqual('changeThisEmail@domain.com'); + expect(student3.data.createStudent.ssn).toBeNull(); + + await updateStudent(GRAPHQL_CLIENT_1, { + id: student3ID, + email: 'emailChanged@domain.com', + }); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnUpdateStudent Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('Test a subscription on delete', async () => { + // subscribe to onDelete as user 2 + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME2, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnDeleteStudent { + onDeleteStudent { + id + name + email + ssn + owner + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, reject) => { + subscription = observer.subscribe({ + next: event => { + const student = event.value.data.onDeleteStudent; + subscription.unsubscribe(); + expect(student.id).toEqual(student4ID); + expect(student.name).toEqual('student4'); + expect(student.email).toEqual('plsDelete@domain.com'); + expect(student.ssn).toBeNull(); + resolve(undefined); + }, + error: err => { + reject(err); + }, + }); + }); + const student4 = await createStudent(GRAPHQL_CLIENT_1, { + name: 'student4', + email: 'plsDelete@domain.com', + ssn: 'DDD-02-SNSN', + }); + expect(student4).toBeDefined(); + const student4ID = student4.data.createStudent.id; + expect(student4.data.createStudent.email).toEqual('plsDelete@domain.com'); + expect(student4.data.createStudent.ssn).toBeNull(); + + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + await deleteStudent(GRAPHQL_CLIENT_1, { id: student4ID }); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnDeleteStudent Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('test that group is only allowed to listen to subscriptions and listen to onCreate', async () => { + const memberID = '001'; + const memberName = 'username00'; + // test that a user that only read can't mutate + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME2, REAL_PASSWORD); + try { + await createMember(GRAPHQL_CLIENT_2, { id: '001', name: 'notUser' }); + } catch (err) { + expect(err).toBeDefined(); + expect(err.graphQLErrors[0].errorType).toEqual('Unauthorized'); + } + + // though they should see when a new member is created + const observer = API.graphql({ + query: gql` + subscription OnCreateMember { + onCreateMember { + id + name + createdAt + updatedAt + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, _) => { + subscription = observer.subscribe((event: any) => { + const member = event.value.data.onCreateMember; + subscription.unsubscribe(); + expect(member).toBeDefined(); + expect(member.id).toEqual(memberID); + expect(member.name).toEqual(memberName); + resolve(undefined); + }); + }); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + // user that is authorized creates the update the mutation + const createMemberResponse = await createMember(GRAPHQL_CLIENT_1, { id: memberID, name: memberName }); + expect(createMemberResponse.data.createMember.id).toEqual(memberID); + expect(createMemberResponse.data.createMember.name).toEqual(memberName); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnCreateMember Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('authorized group is allowed to listen to onUpdate', async () => { + const memberID = '001update'; + const oldMemberName = 'oldUsername'; + const newMemberName = 'newUsername'; + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME2, REAL_PASSWORD); + + const observer = API.graphql({ + query: gql` + subscription OnUpdateMember { + onUpdateMember { + id + name + createdAt + updatedAt + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + + const subscriptionPromise = new Promise((resolve, reject) => { + subscription = observer.subscribe({ + next: event => { + const subResponse = event.value.data.onUpdateMember; + subscription.unsubscribe(); + expect(subResponse).toBeDefined(); + expect(subResponse.id).toEqual(memberID); + expect(subResponse.name).toEqual(newMemberName); + resolve(undefined); + }, + complete: () => {}, + error: err => { + reject(err); + }, + }); + }); + const createMemberResponse = await createMember(GRAPHQL_CLIENT_1, { id: memberID, name: oldMemberName }); + expect(createMemberResponse.data.createMember.id).toEqual(memberID); + expect(createMemberResponse.data.createMember.name).toEqual(oldMemberName); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + // user that is authorized creates the update the mutation + const updateMemberResponse = await updateMember(GRAPHQL_CLIENT_1, { id: memberID, name: newMemberName }); + expect(updateMemberResponse.data.updateMember.id).toEqual(memberID); + expect(updateMemberResponse.data.updateMember.name).toEqual(newMemberName); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnUpdateMember Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('authorized group is allowed to listen to onDelete', async () => { + const memberID = '001delete'; + const memberName = 'newUsername'; + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME2, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnDeleteMember { + onDeleteMember { + id + name + createdAt + updatedAt + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + + const subscriptionPromise = new Promise((resolve, reject) => { + subscription = observer.subscribe({ + next: event => { + subscription.unsubscribe(); + const subResponse = event.value.data.onDeleteMember; + subscription.unsubscribe(); + expect(subResponse).toBeDefined(); + expect(subResponse.id).toEqual(memberID); + expect(subResponse.name).toEqual(memberName); + resolve(undefined); + }, + error: err => { + reject(err); + }, + }); + }); + + const createMemberResponse = await createMember(GRAPHQL_CLIENT_1, { id: memberID, name: memberName }); + expect(createMemberResponse.data.createMember.id).toEqual(memberID); + expect(createMemberResponse.data.createMember.name).toEqual(memberName); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + // user that is authorized creates the update the mutation + const deleteMemberResponse = await deleteMember(GRAPHQL_CLIENT_1, { id: memberID }); + expect(deleteMemberResponse.data.deleteMember.id).toEqual(memberID); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnDeleteMember Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +// ownerField Tests +test('Test subscription onCreatePost with ownerField', async () => { + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnCreatePost { + onCreatePost(postOwner: "${USERNAME1}") { + id + title + postOwner + } + }`, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, _) => { + subscription = observer.subscribe((event: any) => { + const post = event.value.data.onCreatePost; + subscription.unsubscribe(); + expect(post.title).toEqual('someTitle'); + expect(post.postOwner).toEqual(USERNAME1); + resolve(undefined); + }); + }); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + const createPostResponse = await createPost(GRAPHQL_CLIENT_1, { + title: 'someTitle', + postOwner: USERNAME1, + }); + expect(createPostResponse.data.createPost.title).toEqual('someTitle'); + expect(createPostResponse.data.createPost.postOwner).toEqual(USERNAME1); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnCreatePost Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('Test onCreatePost with optional argument', async () => { + reconfigureAmplifyAPI('AMAZON_COGNITO_USER_POOLS'); + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const failedObserver = API.graphql({ + query: gql` + subscription OnCreatePost { + onCreatePost { + id + title + postOwner + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, _) => { + subscription = failedObserver.subscribe( + event => {}, + err => { + expect(err.error.errors[0].message).toEqual( + 'Connection failed: {"errors":[{"errorType":"Unauthorized","message":"Not Authorized to access onCreatePost on type Post"}]}', + ); + resolve(undefined); + }, + ); + }); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnCreatePost Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +// iam tests +test('Test that IAM can listen and read to onCreatePost', async () => { + const postID = 'subscriptionID'; + const postTitle = 'titleMadeByPostOwner'; + + reconfigureAmplifyAPI('AWS_IAM'); + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnCreatePost { + onCreatePost { + id + title + postOwner + } + } + `, + authMode: GRAPHQL_AUTH_MODE.AWS_IAM, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + + const subscriptionPromise = new Promise((resolve, reject) => { + subscription = observer.subscribe( + (event: any) => { + const post = event.value.data.onCreatePost; + subscription.unsubscribe(); + expect(post).toBeDefined(); + expect(post.id).toEqual(postID); + expect(post.title).toEqual(postTitle); + expect(post.postOwner).toEqual(USERNAME1); + resolve(undefined); + }, + err => { + reject(err); + }, + ); + }); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + const createPostResponse = await createPost(GRAPHQL_CLIENT_1, { id: postID, title: postTitle, postOwner: USERNAME1 }); + expect(createPostResponse.data.createPost.id).toEqual(postID); + expect(createPostResponse.data.createPost.title).toEqual(postTitle); + expect(createPostResponse.data.createPost.postOwner).toEqual(USERNAME1); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + await subscriptionPromise; +}); + +test('test that subcsription with apiKey', async () => { + reconfigureAmplifyAPI('API_KEY', API_KEY); + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnCreateTodo { + onCreateTodo { + id + description + name + } + } + `, + authMode: GRAPHQL_AUTH_MODE.API_KEY, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + + const subscriptionPromise = new Promise((resolve, _) => { + subscription = observer.subscribe((event: any) => { + const post = event.value.data.onCreateTodo; + subscription.unsubscribe(); + expect(post.description).toEqual('someDescription'); + expect(post.name).toBeNull(); + resolve(undefined); + }); + }); + + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + const createTodoResponse = await createTodo(GRAPHQL_IAM_AUTH_CLIENT, { + description: 'someDescription', + name: 'todo1', + }); + expect(createTodoResponse.data.createTodo.description).toEqual('someDescription'); + expect(createTodoResponse.data.createTodo.name).toEqual(null); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'OnCreateTodo Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('test that subscription with apiKey onUpdate', async () => { + reconfigureAmplifyAPI('API_KEY', API_KEY); + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnUpdateTodo { + onUpdateTodo { + id + description + name + } + } + `, + authMode: GRAPHQL_AUTH_MODE.API_KEY, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, reject) => { + subscription = observer.subscribe( + (event: any) => { + const todo = event.value.data.onUpdateTodo; + subscription.unsubscribe(); + expect(todo.id).toEqual(todo2ID); + expect(todo.description).toEqual('todo2newDesc'); + expect(todo.name).toBeNull(); + resolve(undefined); + }, + err => { + reject(undefined); + }, + ); + }); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + const todo2 = await createTodo(GRAPHQL_IAM_AUTH_CLIENT, { + description: 'updateTodoDesc', + name: 'todo2', + }); + expect(todo2.data.createTodo.id).toBeDefined(); + const todo2ID = todo2.data.createTodo.id; + expect(todo2.data.createTodo.description).toEqual('updateTodoDesc'); + expect(todo2.data.createTodo.name).toBeNull(); + + // update the description on todo + const updateResponse = await updateTodo(GRAPHQL_IAM_AUTH_CLIENT, { + id: todo2ID, + description: 'todo2newDesc', + }); + expect(updateResponse.data.updateTodo.id).toEqual(todo2ID); + expect(updateResponse.data.updateTodo.description).toEqual('todo2newDesc'); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, 'createTodo Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +test('test that subscription with apiKey onDelete', async () => { + reconfigureAmplifyAPI('API_KEY', API_KEY); + await Auth.signIn(USERNAME1, REAL_PASSWORD); + const observer = API.graphql({ + query: gql` + subscription OnDeleteTodo { + onDeleteTodo { + id + description + name + } + } + `, + authMode: GRAPHQL_AUTH_MODE.API_KEY, + }) as unknown as Observable; + let subscription: ZenObservable.Subscription; + const subscriptionPromise = new Promise((resolve, _) => { + subscription = observer.subscribe((event: any) => { + const todo = event.value.data.onDeleteTodo; + subscription.unsubscribe(); + expect(todo.id).toEqual(todo3ID); + expect(todo.description).toEqual('deleteTodoDesc'); + expect(todo.name).toBeNull(); + resolve(undefined); + }); + }); + await new Promise(res => setTimeout(res, SUBSCRIPTION_DELAY)); + + const todo3 = await createTodo(GRAPHQL_IAM_AUTH_CLIENT, { + description: 'deleteTodoDesc', + name: 'todo3', + }); + expect(todo3.data.createTodo.id).toBeDefined(); + const todo3ID = todo3.data.createTodo.id; + expect(todo3.data.createTodo.description).toEqual('deleteTodoDesc'); + expect(todo3.data.createTodo.name).toBeNull(); + + // delete todo3 + await deleteTodo(GRAPHQL_IAM_AUTH_CLIENT, { + id: todo3ID, + }); + + return withTimeOut(subscriptionPromise, SUBSCRIPTION_TIMEOUT, ' OnDelete Todo Subscription timed out', () => { + subscription?.unsubscribe(); + }); +}); + +function reconfigureAmplifyAPI(appSyncAuthType: string, apiKey?: string) { + if (appSyncAuthType === 'API_KEY') { + API.configure({ + aws_appsync_graphqlEndpoint: GRAPHQL_ENDPOINT, + aws_appsync_region: AWS_REGION, + aws_appsync_authenticationType: appSyncAuthType, + aws_appsync_apiKey: apiKey, + }); + } else { + API.configure({ + aws_appsync_graphqlEndpoint: GRAPHQL_ENDPOINT, + aws_appsync_region: AWS_REGION, + aws_appsync_authenticationType: appSyncAuthType, + }); + } +} + +// mutations +async function createMember(client: AWSAppSyncClient, input: MemberInput) { + const request = gql` + mutation CreateMember($input: CreateMemberInput!) { + createMember(input: $input) { + id + name + createdAt + updatedAt + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function updateMember(client: AWSAppSyncClient, input: MemberInput) { + const request = gql` + mutation UpdateMember($input: UpdateMemberInput!) { + updateMember(input: $input) { + id + name + createdAt + updatedAt + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function deleteMember(client: AWSAppSyncClient, input: MemberInput) { + const request = gql` + mutation DeleteMember($input: DeleteMemberInput!) { + deleteMember(input: $input) { + id + name + createdAt + updatedAt + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function createStudent(client: AWSAppSyncClient, input: CreateStudentInput) { + const request = gql` + mutation CreateStudent($input: CreateStudentInput!) { + createStudent(input: $input) { + id + name + email + ssn + owner + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function updateStudent(client: AWSAppSyncClient, input: UpdateStudentInput) { + const request = gql` + mutation UpdateStudent($input: UpdateStudentInput!) { + updateStudent(input: $input) { + id + name + email + ssn + owner + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function deleteStudent(client: AWSAppSyncClient, input: DeleteTypeInput) { + const request = gql` + mutation DeleteStudent($input: DeleteStudentInput!) { + deleteStudent(input: $input) { + id + name + email + ssn + owner + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function createPost(client: AWSAppSyncClient, input: CreatePostInput) { + const request = gql` + mutation CreatePost($input: CreatePostInput!) { + createPost(input: $input) { + id + title + postOwner + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function createTodo(client: AWSAppSyncClient, input: CreateTodoInput) { + const request = gql` + mutation CreateTodo($input: CreateTodoInput!) { + createTodo(input: $input) { + id + description + name + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function updateTodo(client: AWSAppSyncClient, input: UpdateTodoInput) { + const request = gql` + mutation UpdateTodo($input: UpdateTodoInput!) { + updateTodo(input: $input) { + id + description + name + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} + +async function deleteTodo(client: AWSAppSyncClient, input: DeleteTypeInput) { + const request = gql` + mutation DeleteTodo($input: DeleteTodoInput!) { + deleteTodo(input: $input) { + id + description + name + } + } + `; + return await client.mutate({ mutation: request, variables: { input } }); +} diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/test-data/amazon.png b/packages/graphql-transformers-e2e-tests/src/__tests__/test-data/amazon.png new file mode 100644 index 0000000000000000000000000000000000000000..88ee36632cb691e190a8f05fae1c3e7d863e952e GIT binary patch literal 42713 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 literal 0 HcmV?d00001 diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/test-data/dogs.png b/packages/graphql-transformers-e2e-tests/src/__tests__/test-data/dogs.png new file mode 100644 index 0000000000000000000000000000000000000000..320a3ed9bf8266c7883bb1264bce5fda290192a4 GIT binary patch literal 195285 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⋘v;WZl=CSgsW6d)Fzm!` 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 literal 0 HcmV?d00001 diff --git a/packages/graphql-transformers-e2e-tests/src/cognitoUtils.ts b/packages/graphql-transformers-e2e-tests/src/cognitoUtils.ts index 86409aeed61..a667ec4b276 100644 --- a/packages/graphql-transformers-e2e-tests/src/cognitoUtils.ts +++ b/packages/graphql-transformers-e2e-tests/src/cognitoUtils.ts @@ -12,7 +12,7 @@ import { } from 'aws-sdk/clients/cognitoidentityserviceprovider'; import { ResourceConstants } from 'graphql-transformer-common'; import { IAM as cfnIAM, Cognito as cfnCognito } from 'cloudform-types'; -import { default as CognitoClient } from 'aws-sdk/clients/cognitoidentityserviceprovider'; +import { CognitoIdentityServiceProvider as CognitoClient, CognitoIdentity } from 'aws-sdk'; import TestStorage from './TestStorage'; import DeploymentResources from 'graphql-transformer-core/lib/DeploymentResources'; @@ -100,6 +100,37 @@ export async function addUserToGroup(groupName: string, username: string, userPo }); } +export async function createIdentityPool( + client: CognitoIdentity, + identityPoolName: string, + params: { authRoleArn: string; unauthRoleArn: string; providerName: string; clientId: string }, +): Promise { + const idPool = await client + .createIdentityPool({ + IdentityPoolName: identityPoolName, + AllowUnauthenticatedIdentities: true, + CognitoIdentityProviders: [ + { + ProviderName: params.providerName, + ClientId: params.clientId, + }, + ], + }) + .promise(); + + await client + .setIdentityPoolRoles({ + IdentityPoolId: idPool.IdentityPoolId, + Roles: { + authenticated: params.authRoleArn, + unauthenticated: params.unauthRoleArn, + }, + }) + .promise(); + + return idPool.IdentityPoolId; +} + export async function createUserPool(client: CognitoClient, userPoolName: string): Promise { return new Promise((res, rej) => { const params: CreateUserPoolRequest = { @@ -135,6 +166,14 @@ export async function deleteUserPool(client: CognitoClient, userPoolId: string): }); } +export async function deleteIdentityPool(client: CognitoIdentity, identityPoolId: string) { + await client + .deleteIdentityPool({ + IdentityPoolId: identityPoolId, + }) + .promise(); +} + export async function createUserPoolClient( client: CognitoClient, userPoolId: string, diff --git a/packages/graphql-transformers-e2e-tests/src/deployNestedStacks.ts b/packages/graphql-transformers-e2e-tests/src/deployNestedStacks.ts index 2f13e615b2d..fbdae01c607 100644 --- a/packages/graphql-transformers-e2e-tests/src/deployNestedStacks.ts +++ b/packages/graphql-transformers-e2e-tests/src/deployNestedStacks.ts @@ -3,8 +3,8 @@ import { CloudFormationClient } from './CloudFormationClient'; import * as fs from 'fs'; import * as path from 'path'; import { DeploymentResources } from 'graphql-transformer-core/lib/DeploymentResources'; -import { deleteUserPool } from './cognitoUtils'; -import CognitoIdentityServiceProvider from 'aws-sdk/clients/cognitoidentityserviceprovider'; +import { deleteUserPool, deleteIdentityPool } from './cognitoUtils'; +import { CognitoIdentityServiceProvider, CognitoIdentity } from 'aws-sdk'; import emptyBucket from './emptyBucket'; function deleteDirectory(directory: string) { @@ -178,7 +178,7 @@ export async function deploy( } function addAPIKeys(stack: DeploymentResources) { - if (!stack.rootStack.Resources.GraphQLAPIKey) { + if (stack.rootStack.Parameters.CreateAPIKey && !stack.rootStack.Resources.GraphQLAPIKey) { stack.rootStack.Resources.GraphQLAPIKey = { Type: 'AWS::AppSync::ApiKey', Properties: { @@ -189,7 +189,7 @@ function addAPIKeys(stack: DeploymentResources) { }; } - if (!stack.rootStack.Outputs.GraphQLAPIKeyOutput) { + if (stack.rootStack.Parameters.CreateAPIKey && !stack.rootStack.Outputs.GraphQLAPIKeyOutput) { stack.rootStack.Outputs.GraphQLAPIKeyOutput = { Value: { 'Fn::GetAtt': ['GraphQLAPIKey', 'ApiKey'], @@ -203,10 +203,15 @@ export const cleanupStackAfterTest = async ( stackName: string, cf: CloudFormationClient, cognitoParams?: { cognitoClient: CognitoIdentityServiceProvider; userPoolId: string }, + identityParams?: { identityClient: CognitoIdentity; identityPoolId: string }, ) => { try { await cf.deleteStack(stackName); + if (identityParams) { + await deleteIdentityPool(identityParams.identityClient, identityParams.identityPoolId); + } + if (cognitoParams) { await deleteUserPool(cognitoParams.cognitoClient, cognitoParams.userPoolId); } diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 00000000000..2eb694e041d --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,23 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "", + "main": "echo-current-cli-version.js", + "scripts": { + "split-e2e-tests": "yarn ts-node --transpile-only ./split-e2e-tests.ts" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@types/fs-extra": "^9.0.13", + "@types/glob": "^7.2.0", + "@types/js-yaml": "^4.0.4", + "@types/node": "^16.11.5", + "execa": "^5.1.1", + "fs-extra": "^10.0.0", + "glob": "^7.2.0", + "js-yaml": "^4.1.0", + "ts-node": "^10.4.0", + "typescript": "^4.4.4" + } +} diff --git a/scripts/split-e2e-tests.ts b/scripts/split-e2e-tests.ts index fdcb96efc02..48de3d7ec64 100644 --- a/scripts/split-e2e-tests.ts +++ b/scripts/split-e2e-tests.ts @@ -11,55 +11,22 @@ const CONCURRENCY = 25; // Each of these failures should be independently investigated, resolved, and removed from this list. // For now, this list is being used to skip creation of circleci jobs for these tasks const WINDOWS_TEST_FAILURES = [ - 'amplify-app-amplify_e2e_tests', - 'analytics-amplify_e2e_tests', - 'api_1-amplify_e2e_tests', - 'api_2-amplify_e2e_tests', - 'api_3-amplify_e2e_tests', - 'api_4-amplify_e2e_tests', - 'api_5-amplify_e2e_tests', - 'auth_1-amplify_e2e_tests', - 'auth_2-amplify_e2e_tests', - 'auth_3-amplify_e2e_tests', - 'auth_4-amplify_e2e_tests', - // Auth tests are failing because - // us-east-1 region is not allowed in parent e2e test account - // and `singleSelect` for region is not working properly in windows - 'auth_5-amplify_e2e_tests', - 'auth_6-amplify_e2e_tests', - 'auth_7-amplify_e2e_tests', - 'auth_8-amplify_e2e_tests', - 'containers-api-amplify_e2e_tests', + 'api_6-amplify_e2e_tests', 'datastore-modelgen-amplify_e2e_tests', 'delete-amplify_e2e_tests', 'env-amplify_e2e_tests', 'feature-flags-amplify_e2e_tests', - 'frontend_config_drift-amplify_e2e_tests', 'function_1-amplify_e2e_tests', 'function_2-amplify_e2e_tests', 'function_3-amplify_e2e_tests', 'function_4-amplify_e2e_tests', - 'function_6-amplify_e2e_tests', 'function_5-amplify_e2e_tests', + 'function_6-amplify_e2e_tests', 'function_7-amplify_e2e_tests', 'function_8-amplify_e2e_tests', 'function_9-amplify_e2e_tests', - 'geo-add-amplify_e2e_tests', - 'geo-update-amplify_e2e_tests', 'geo-remove-amplify_e2e_tests', - 'hooks-amplify_e2e_tests', - 'hosting-amplify_e2e_tests', - 'hostingPROD-amplify_e2e_tests', - 'iam-permissions-boundary-amplify_e2e_tests', - 'import_auth_1-amplify_e2e_tests', - 'import_auth_2-amplify_e2e_tests', - 'import_auth_3-amplify_e2e_tests', - 'import_dynamodb_1-amplify_e2e_tests', - 'import_dynamodb_2-amplify_e2e_tests', - 'import_s3_1-amplify_e2e_tests', - 'import_s3_2-amplify_e2e_tests', - 'init-amplify_e2e_tests', - 'interactions-amplify_e2e_tests', + 'geo-update-amplify_e2e_tests', 'layer-1-amplify_e2e_tests', 'layer-2-amplify_e2e_tests', 'layer-3-amplify_e2e_tests', @@ -68,47 +35,40 @@ const WINDOWS_TEST_FAILURES = [ 'migration-api-connection-migration2-amplify_e2e_tests', 'migration-api-key-migration1-amplify_e2e_tests', 'migration-api-key-migration2-amplify_e2e_tests', - 'migration-api-key-migration3-amplify_e2e_tests', - 'migration-node-function-amplify_e2e_tests', - 'notifications-amplify_e2e_tests', - 'predictions-amplify_e2e_tests', 'pull-amplify_e2e_tests', - 's3-sse-amplify_e2e_tests', - 'schema-auth-1-amplify_e2e_tests', - 'schema-auth-2-amplify_e2e_tests', - 'schema-auth-3-amplify_e2e_tests', - 'schema-auth-4-amplify_e2e_tests', - 'schema-auth-5-amplify_e2e_tests', - 'schema-auth-6-amplify_e2e_tests', - 'schema-auth-7-amplify_e2e_tests', - 'schema-auth-8-amplify_e2e_tests', - 'schema-auth-9-amplify_e2e_tests', - 'schema-auth-10-amplify_e2e_tests', - 'schema-auth-11-amplify_e2e_tests', - 'schema-auth-12-amplify_e2e_tests', - 'schema-auth-13-amplify_e2e_tests', - 'schema-connection-amplify_e2e_tests', - 'schema-data-access-patterns-amplify_e2e_tests', - 'schema-function-1-amplify_e2e_tests', - 'schema-function-2-amplify_e2e_tests', + 'schema-iterative-rollback-1-amplify_e2e_tests', + 'schema-iterative-rollback-2-amplify_e2e_tests', 'schema-iterative-update-1-amplify_e2e_tests', 'schema-iterative-update-2-amplify_e2e_tests', 'schema-iterative-update-3-amplify_e2e_tests', 'schema-iterative-update-4-amplify_e2e_tests', 'schema-iterative-update-locking-amplify_e2e_tests', - 'schema-iterative-rollback-1-amplify_e2e_tests', - 'schema-iterative-rollback-2-amplify_e2e_tests', - 'schema-key-amplify_e2e_tests_pkg', - 'schema-model-amplify_e2e_tests', - 'schema-predictions-amplify_e2e_tests', - 'schema-searchable-amplify_e2e_tests', - 'schema-versioned-amplify_e2e_tests', - 'storage-1-amplify_e2e_tests', - 'storage-2-amplify_e2e_tests', - 'storage-3-amplify_e2e_tests', - 'tags-amplify_e2e_tests', + 'schema-key-amplify_e2e_tests', + 'api_4-amplify_e2e_tests', + 'api_3-amplify_e2e_tests', + 'api_2-amplify_e2e_tests', + 'api_1-amplify_e2e_tests', + 'amplify-app-amplify_e2e_tests', + 'import_s3_1-amplify_e2e_tests', + 'import_s3_3-amplify_e2e_tests', + 'auth_4-amplify_e2e_tests', + 'auth_3-amplify_e2e_tests', 'custom_policies_container-amplify_e2e_tests', 'custom_policies_function-amplify_e2e_tests', + 'storage-4-amplify_e2e_tests', + 'resolvers-amplify_e2e_tests', + 'migration-api-key-migration3-amplify_e2e_tests', + 'migration-api-key-migration4-amplify_e2e_tests', + 'migration-api-key-migration5-amplify_e2e_tests', + + // 👇 These fail due to ExpiredToken. 👇 + // 👇 Tests should be split to speed up execution time. 👇 + 'geo-add-amplify_e2e_tests', + 'import_auth_1-amplify_e2e_tests', + 'import_auth_2-amplify_e2e_tests', + 'import_auth_3-amplify_e2e_tests', + 'import_dynamodb_2-amplify_e2e_tests', + 'import_s3_2-amplify_e2e_tests', ]; // Ensure to update packages/amplify-e2e-tests/src/cleanup-e2e-resources.ts is also updated this gets updated @@ -133,6 +93,9 @@ const USE_PARENT_ACCOUNT = [ 'import_dynamodb_1-amplify_e2e_tests', 'import_s3_1-amplify_e2e_tests', 'migration-api-key-migration2-amplify_e2e_tests', + 'migration-api-key-migration3-amplify_e2e_tests', + 'migration-api-key-migration4-amplify_e2e_tests', + 'migration-api-key-migration5-amplify_e2e_tests', 'storage-amplify_e2e_tests', ]; @@ -256,6 +219,8 @@ export type CircleCIConfig = { }; }; +const repoRoot = join(__dirname, '..'); + function getTestFiles(dir: string, pattern = 'src/**/*.test.ts'): string[] { // Todo: add reverse to run longest tests first return sortTestsBasedOnTime(glob.sync(pattern, { cwd: dir })); // .reverse(); @@ -444,17 +409,21 @@ function getRequiredJob(jobNames: string[], index: number, concurrency: number = } function loadConfig(): CircleCIConfig { - const configFile = join(process.cwd(), '.circleci', 'config.base.yml'); + const configFile = join(repoRoot, '.circleci', 'config.base.yml'); return yaml.load(fs.readFileSync(configFile, 'utf8')); } function saveConfig(config: CircleCIConfig): void { - const configFile = join(process.cwd(), '.circleci', 'config.yml'); + const configFile = join(repoRoot, '.circleci', 'generated_config.yml'); const output = ['# auto generated file. Edit config.base.yaml if you want to change', yaml.dump(config, { noRefs: true })]; fs.writeFileSync(configFile, output.join('\n')); } function verifyConfig() { + if (process.env.CIRCLECI) { + console.log('Skipping config verification since this is already running in a CCI environment.'); + return; + } try { execa.commandSync('which circleci'); } catch { @@ -463,12 +432,21 @@ function verifyConfig() { ); process.exit(1); } + const cci_config_path = join(repoRoot, '.circleci', 'config.yml'); + const cci_generated_config_path = join(repoRoot, '.circleci', 'generated_config.yml'); try { - execa.commandSync('circleci config validate'); + execa.commandSync(`circleci config validate ${cci_config_path}`); } catch { console.error(`"circleci config validate" command failed. Please check your .circleci/config.yml validity`); process.exit(1); } + try { + execa.commandSync(`circleci config validate ${cci_generated_config_path}`); + } catch (e) { + console.log(e); + console.error(`"circleci config validate" command failed. Please check your .circleci/generated_config.yml validity`); + process.exit(1); + } } function main(): void { @@ -477,35 +455,35 @@ function main(): void { config, 'amplify_e2e_tests', 'build_test_deploy', - join(process.cwd(), 'packages', 'amplify-e2e-tests'), + join(repoRoot, 'packages', 'amplify-e2e-tests'), CONCURRENCY, ); const splitPkgTests = splitTests( splitNodeTests, 'amplify_e2e_tests_pkg', 'build_test_deploy', - join(process.cwd(), 'packages', 'amplify-e2e-tests'), + join(repoRoot, 'packages', 'amplify-e2e-tests'), CONCURRENCY, ); const splitGqlTests = splitTests( splitPkgTests, 'graphql_e2e_tests', 'build_test_deploy', - join(process.cwd(), 'packages', 'graphql-transformers-e2e-tests'), + join(repoRoot, 'packages', 'graphql-transformers-e2e-tests'), CONCURRENCY, ); const splitV4MigrationTests = splitTests( splitGqlTests, 'amplify_migration_tests_v4', 'build_test_deploy', - join(process.cwd(), 'packages', 'amplify-migration-tests'), + join(repoRoot, 'packages', 'amplify-migration-tests'), CONCURRENCY, ); const splitLatestMigrationTests = splitTests( splitV4MigrationTests, 'amplify_migration_tests_latest', 'build_test_deploy', - join(process.cwd(), 'packages', 'amplify-migration-tests'), + join(repoRoot, 'packages', 'amplify-migration-tests'), CONCURRENCY, ); saveConfig(splitLatestMigrationTests); diff --git a/scripts/yarn.lock b/scripts/yarn.lock new file mode 100644 index 00000000000..4d25715a330 --- /dev/null +++ b/scripts/yarn.lock @@ -0,0 +1,337 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + +"@types/fs-extra@^9.0.13": + version "9.0.13" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" + integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== + dependencies: + "@types/node" "*" + +"@types/glob@^7.2.0": + version "7.2.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/js-yaml@^4.0.4": + version "4.0.4" + resolved "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.4.tgz#cc38781257612581a1a0eb25f1709d2b06812fce" + integrity sha512-AuHubXUmg0AzkXH0Mx6sIxeY/1C110mm/EkE/gB1sTRz3h2dao2W/63q42SlVST+lICxz5Oki2hzYA6+KnnieQ== + +"@types/minimatch@*": + version "3.0.5" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/node@*", "@types/node@^16.11.5": + version "16.11.5" + resolved "https://registry.npmjs.org/@types/node/-/node-16.11.5.tgz#e91be5ba4ab88c06095e7b61f9ad1767a1093faf" + integrity sha512-NyUV2DGcqYIx9op++MG2+Z4Nhw1tPhi0Wfs81TgncuX1aJC4zf2fgCJlJhl4BW9bCSS04e34VkqmOS96w0XQdg== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1: + version "8.5.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" + integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +execa@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob@^7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.8" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3: + version "3.0.5" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" + integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +ts-node@^10.4.0: + version "10.4.0" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" + integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== + dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + yn "3.1.1" + +typescript@^4.4.4: + version "4.4.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" + integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/yarn.lock b/yarn.lock index 9708ceec68a..1e9c739f700 100644 --- a/yarn.lock +++ b/yarn.lock @@ -156,6 +156,200 @@ prettier "^1.19.1" yargs "^15.1.0" +"@aws-amplify/graphql-function-transformer@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-function-transformer/-/graphql-function-transformer-0.4.4.tgz#56464e9cdbc86d8951a165eeae71725a7d28aadb" + integrity sha512-ZTbivT8wiM8whlggSiVOvCllhEN4JMISPq1kUKVOA1Fzg+TBxciuBmgIoEdreYDNi2nQo6+i2udMGiKJ/CjvMg== + dependencies: + "@aws-amplify/graphql-transformer-core" "0.9.1" + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/aws-lambda" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + +"@aws-amplify/graphql-http-transformer@0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-http-transformer/-/graphql-http-transformer-0.5.4.tgz#374cadfa01b6b1c069b083fb986bd6fc6f0fa47e" + integrity sha512-LisG3s8Cf4DvzdGMUKCXRA0tsZuFgLyp1W6z+LHP1IZjlWwTVOG6CSc33ZWX6nWFf5kfosorWIkluP+jchxzvQ== + dependencies: + "@aws-amplify/graphql-transformer-core" "0.9.1" + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/core" "~1.124.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + +"@aws-amplify/graphql-index-transformer@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-index-transformer/-/graphql-index-transformer-0.3.3.tgz#6d07bed54b20f72cf7bed617e45a1ce94512da50" + integrity sha512-ZAnQAeMHXQMIO3/DkvUzDN7F/agPd0JIP+UV91iaaA8Kv5DfPySrQbUBv7GprRkfWKr01gxbYL12iCm9VoAilQ== + dependencies: + "@aws-amplify/graphql-model-transformer" "0.6.3" + "@aws-amplify/graphql-transformer-core" "0.9.1" + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/aws-appsync" "~1.124.0" + "@aws-cdk/aws-dynamodb" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + +"@aws-amplify/graphql-model-transformer@0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-model-transformer/-/graphql-model-transformer-0.6.3.tgz#283f440ac4c97d6aceb55f31d6d657143eb35fe2" + integrity sha512-wZuxQnM7WdSq75DtbY0czDszC9pU0JM96xESX3Unf/B++EklWW5z9wkzs05bPSHQxrYW6dFCpJEIsdcee5LIIw== + dependencies: + "@aws-amplify/graphql-transformer-core" "0.9.1" + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/assets" "~1.124.0" + "@aws-cdk/aws-applicationautoscaling" "~1.124.0" + "@aws-cdk/aws-appsync" "~1.124.0" + "@aws-cdk/aws-autoscaling-common" "~1.124.0" + "@aws-cdk/aws-cloudformation" "~1.124.0" + "@aws-cdk/aws-cloudwatch" "~1.124.0" + "@aws-cdk/aws-codeguruprofiler" "~1.124.0" + "@aws-cdk/aws-dynamodb" "~1.124.0" + "@aws-cdk/aws-ec2" "~1.124.0" + "@aws-cdk/aws-efs" "~1.124.0" + "@aws-cdk/aws-events" "~1.124.0" + "@aws-cdk/aws-iam" "~1.124.0" + "@aws-cdk/aws-kms" "~1.124.0" + "@aws-cdk/aws-lambda" "~1.124.0" + "@aws-cdk/aws-logs" "~1.124.0" + "@aws-cdk/aws-s3" "~1.124.0" + "@aws-cdk/aws-s3-assets" "~1.124.0" + "@aws-cdk/aws-sns" "~1.124.0" + "@aws-cdk/aws-sqs" "~1.124.0" + "@aws-cdk/aws-ssm" "~1.124.0" + "@aws-cdk/cloud-assembly-schema" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + "@aws-cdk/custom-resources" "~1.124.0" + "@aws-cdk/cx-api" "~1.124.0" + "@aws-cdk/region-info" "~1.124.0" + constructs "^3.3.125" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + lodash "^4.17.21" + md5 "^2.3.0" + +"@aws-amplify/graphql-predictions-transformer@0.3.4": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-predictions-transformer/-/graphql-predictions-transformer-0.3.4.tgz#88feca0db4c60a553bd83cbe97dec8ea8680bc17" + integrity sha512-+Yi2gJ7sCEhqb+ZFGUHHW66xhpohb82yYGPwk+XNUZyo1nvKkYaAdb4S0N4fr75/I9xQc3rPF3NScKEuCDdhhg== + dependencies: + "@aws-amplify/graphql-transformer-core" "0.9.1" + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/aws-appsync" "~1.124.0" + "@aws-cdk/aws-iam" "~1.124.0" + "@aws-cdk/aws-lambda" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + +"@aws-amplify/graphql-relational-transformer@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-relational-transformer/-/graphql-relational-transformer-0.3.0.tgz#c6560842796afa8bbed30077e166ef6950d52d54" + integrity sha512-0XlH+A/8HWhTqtdIf+izkfg8iFCKPwTGRmPT+plZ1uz899PqyLKXzDZyPxnf+BRb2D7wP8NgAUK1qLlt7zh8gA== + dependencies: + "@aws-amplify/graphql-index-transformer" "0.3.3" + "@aws-amplify/graphql-model-transformer" "0.6.3" + "@aws-amplify/graphql-transformer-core" "0.9.1" + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/aws-appsync" "~1.124.0" + "@aws-cdk/aws-dynamodb" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + +"@aws-amplify/graphql-searchable-transformer@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-searchable-transformer/-/graphql-searchable-transformer-0.6.2.tgz#6ae35ecd702ffa676fe547e7ccbf040e010be256" + integrity sha512-CGwfXedp+vbEXTufX4aSqctE+idjUUL+AI/Sziyv84bGt2P602YaJtlZfx9JH6H+yfuxGRDE+rva8bAUcFrTYg== + dependencies: + "@aws-amplify/graphql-transformer-core" "0.9.1" + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/aws-appsync" "~1.124.0" + "@aws-cdk/aws-dynamodb" "~1.124.0" + "@aws-cdk/aws-ec2" "~1.124.0" + "@aws-cdk/aws-elasticsearch" "~1.124.0" + "@aws-cdk/aws-iam" "~1.124.0" + "@aws-cdk/aws-lambda" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + +"@aws-amplify/graphql-transformer-core@0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-transformer-core/-/graphql-transformer-core-0.9.1.tgz#7172c2053300026fb0c9b8e33731f1cdd5870151" + integrity sha512-hzMuA4FHe+EbUPATQrTUabaJ2ETGmHaf+JMsAllw3BkZLcicbwzvBA4iUvgyllkOXXCur3p7gWkdsOndiY0qGw== + dependencies: + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/assets" "~1.124.0" + "@aws-cdk/aws-applicationautoscaling" "~1.124.0" + "@aws-cdk/aws-appsync" "~1.124.0" + "@aws-cdk/aws-certificatemanager" "~1.124.0" + "@aws-cdk/aws-cloudwatch" "~1.124.0" + "@aws-cdk/aws-codeguruprofiler" "~1.124.0" + "@aws-cdk/aws-cognito" "~1.124.0" + "@aws-cdk/aws-dynamodb" "~1.124.0" + "@aws-cdk/aws-ec2" "~1.124.0" + "@aws-cdk/aws-efs" "~1.124.0" + "@aws-cdk/aws-events" "~1.124.0" + "@aws-cdk/aws-iam" "~1.124.0" + "@aws-cdk/aws-kms" "~1.124.0" + "@aws-cdk/aws-lambda" "~1.124.0" + "@aws-cdk/aws-logs" "~1.124.0" + "@aws-cdk/aws-route53" "~1.124.0" + "@aws-cdk/aws-s3" "~1.124.0" + "@aws-cdk/aws-s3-assets" "~1.124.0" + "@aws-cdk/aws-sqs" "~1.124.0" + "@aws-cdk/aws-ssm" "~1.124.0" + "@aws-cdk/cloud-assembly-schema" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + "@aws-cdk/custom-resources" "~1.124.0" + "@aws-cdk/cx-api" "~1.124.0" + "@aws-cdk/region-info" "~1.124.0" + change-case "^4.1.1" + constructs "^3.3.125" + deep-diff "^1.0.2" + fs-extra "^8.1.0" + glob "^7.1.6" + graphql "^14.5.8" + graphql-transformer-common "4.19.10" + lodash "^4.17.21" + md5 "^2.3.0" + ts-dedent "^2.0.0" + +"@aws-amplify/graphql-transformer-interfaces@1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-transformer-interfaces/-/graphql-transformer-interfaces-1.9.1.tgz#b0c394d7ed8be1344cbd974d29fa428d8202d999" + integrity sha512-8gMCLK6nzBfehPo2shEIeYqytB6Vn2+vtA3TAAlI0tkJFPY5NfWd5agcmXtXh15n6UZay1QQPUBmDVKAWUXk3Q== + dependencies: + "@aws-cdk/aws-appsync" "~1.124.0" + "@aws-cdk/aws-cloudwatch" "~1.124.0" + "@aws-cdk/aws-dynamodb" "~1.124.0" + "@aws-cdk/aws-ec2" "~1.124.0" + "@aws-cdk/aws-elasticsearch" "~1.124.0" + "@aws-cdk/aws-events" "~1.124.0" + "@aws-cdk/aws-iam" "~1.124.0" + "@aws-cdk/aws-kms" "~1.124.0" + "@aws-cdk/aws-lambda" "~1.124.0" + "@aws-cdk/aws-logs" "~1.124.0" + "@aws-cdk/aws-rds" "~1.124.0" + "@aws-cdk/aws-s3" "~1.124.0" + "@aws-cdk/aws-sam" "~1.124.0" + "@aws-cdk/aws-secretsmanager" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + "@aws-cdk/custom-resources" "~1.124.0" + constructs "^3.3.125" + graphql "^14.5.8" + "@aws-amplify/graphql-types-generator@2.8.0": version "2.8.0" resolved "https://registry.yarnpkg.com/@aws-amplify/graphql-types-generator/-/graphql-types-generator-2.8.0.tgz#4d1de0d2bebfe86863d892b175ef9ee5fea489ca" @@ -6144,10 +6338,10 @@ dependencies: source-map "^0.6.1" -"@types/update-notifier@^4.1.0": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-4.1.1.tgz#370a2159db6ca85a0c7bb1ed08e4346c7b12ea46" - integrity sha512-iPySyshoFAgiPMj6MC41X1rbNjkTGBhCQju9xAldSGe1Y6utyWG2Fbdr7OXuuqcH96KMZbuJjviu9bXgfVbJUg== +"@types/update-notifier@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-5.1.0.tgz#52ed6a2e9851fd6f1c88e93c85e8a0e1d5500fda" + integrity sha512-aGY5pH1Q/DcToKXl4MCj1c0uDUB+zSVFDRCI7Q7js5sguzBTqJV/5kJA2awofbtWYF3xnon1TYdZYnFditRPtQ== dependencies: "@types/configstore" "*" boxen "^4.2.0" @@ -6718,6 +6912,30 @@ amdefine@>=0.0.4: resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= +amplify-cli-core@1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/amplify-cli-core/-/amplify-cli-core-1.30.0.tgz#dcfc7555c3194c91cfd89c7edfdf6495e9f6fd70" + integrity sha512-G9JK8eFmuKHqflBSn+8tBGFDgf2r9UItL9hupCCe9h5wC7KVWxNFR1K0At24hsifVjt9f8j1HnFjZxmJGFmdwQ== + dependencies: + ajv "^6.12.3" + amplify-cli-logger "1.1.0" + amplify-prompts "1.2.0" + chalk "^4.1.1" + ci-info "^2.0.0" + cloudform-types "^4.2.0" + dotenv "^8.2.0" + execa "^5.1.1" + fs-extra "^8.1.0" + globby "^11.0.3" + hjson "^3.2.1" + js-yaml "^4.0.0" + lodash "^4.17.21" + node-fetch "^2.6.1" + open "^7.3.1" + proxy-agent "^5.0.0" + semver "^7.3.5" + which "^2.0.2" + amplify-codegen@^2.23.1: version "2.26.2" resolved "https://registry.yarnpkg.com/amplify-codegen/-/amplify-codegen-2.26.2.tgz#f5837dae2a15eaee214fe7eb318aa577a1f7e198" @@ -6750,6 +6968,115 @@ amplify-prompts@1.1.2: chalk "^4.1.1" enquirer "^2.3.6" +amplify-provider-awscloudformation@4.61.1: + version "4.61.1" + resolved "https://registry.yarnpkg.com/amplify-provider-awscloudformation/-/amplify-provider-awscloudformation-4.61.1.tgz#dcafdbf61bc720e0ec6953063185fd0b31402d3f" + integrity sha512-OZa5O45m8mMJD4IARD4GMff5RNt2V84OEO22IBuPYyz8HHazUZ/IEcd5bsCYztdaU9sDCSpd2BgWqdGoYr2HIw== + dependencies: + "@aws-amplify/graphql-function-transformer" "0.4.4" + "@aws-amplify/graphql-http-transformer" "0.5.4" + "@aws-amplify/graphql-index-transformer" "0.3.3" + "@aws-amplify/graphql-model-transformer" "0.6.3" + "@aws-amplify/graphql-predictions-transformer" "0.3.4" + "@aws-amplify/graphql-relational-transformer" "0.3.0" + "@aws-amplify/graphql-searchable-transformer" "0.6.2" + "@aws-amplify/graphql-transformer-core" "0.9.1" + "@aws-amplify/graphql-transformer-interfaces" "1.9.1" + "@aws-cdk/assets" "~1.124.0" + "@aws-cdk/aws-apigatewayv2" "~1.124.0" + "@aws-cdk/aws-autoscaling" "~1.124.0" + "@aws-cdk/aws-batch" "~1.124.0" + "@aws-cdk/aws-cloudformation" "~1.124.0" + "@aws-cdk/aws-cloudwatch" "~1.124.0" + "@aws-cdk/aws-codebuild" "~1.124.0" + "@aws-cdk/aws-codecommit" "~1.124.0" + "@aws-cdk/aws-codedeploy" "~1.124.0" + "@aws-cdk/aws-codepipeline" "~1.124.0" + "@aws-cdk/aws-codepipeline-actions" "~1.124.0" + "@aws-cdk/aws-ec2" "~1.124.0" + "@aws-cdk/aws-ecr" "~1.124.0" + "@aws-cdk/aws-ecr-assets" "~1.124.0" + "@aws-cdk/aws-ecs" "~1.124.0" + "@aws-cdk/aws-elasticloadbalancing" "~1.124.0" + "@aws-cdk/aws-elasticloadbalancingv2" "~1.124.0" + "@aws-cdk/aws-events" "~1.124.0" + "@aws-cdk/aws-events-targets" "~1.124.0" + "@aws-cdk/aws-iam" "~1.124.0" + "@aws-cdk/aws-kinesis" "~1.124.0" + "@aws-cdk/aws-kinesisfirehose" "~1.124.0" + "@aws-cdk/aws-kms" "~1.124.0" + "@aws-cdk/aws-lambda" "~1.124.0" + "@aws-cdk/aws-logs" "~1.124.0" + "@aws-cdk/aws-route53" "~1.124.0" + "@aws-cdk/aws-s3" "~1.124.0" + "@aws-cdk/aws-s3-assets" "~1.124.0" + "@aws-cdk/aws-secretsmanager" "~1.124.0" + "@aws-cdk/aws-servicecatalog" "~1.124.0" + "@aws-cdk/aws-servicediscovery" "~1.124.0" + "@aws-cdk/aws-sns" "~1.124.0" + "@aws-cdk/aws-sns-subscriptions" "~1.124.0" + "@aws-cdk/aws-sqs" "~1.124.0" + "@aws-cdk/aws-stepfunctions" "~1.124.0" + "@aws-cdk/core" "~1.124.0" + "@aws-cdk/custom-resources" "~1.124.0" + "@aws-cdk/region-info" "~1.124.0" + "@octokit/rest" "^18.0.9" + amplify-cli-core "1.30.0" + amplify-cli-logger "1.1.0" + amplify-codegen "^2.23.1" + amplify-util-import "1.5.13" + archiver "^5.3.0" + aws-sdk "^2.963.0" + bottleneck "2.19.5" + cfn-lint "^1.9.7" + chalk "^4.1.1" + cloudform "^4.2.0" + cloudform-types "^4.2.0" + columnify "^1.5.4" + constructs "^3.3.125" + cors "^2.8.5" + deep-diff "^1.0.2" + extract-zip "^2.0.1" + folder-hash "^4.0.1" + fs-extra "^8.1.0" + glob "^7.1.6" + graphql "^14.5.8" + graphql-auth-transformer "6.24.24" + graphql-connection-transformer "4.21.23" + graphql-dynamodb-transformer "6.22.23" + graphql-elasticsearch-transformer "4.12.3" + graphql-function-transformer "2.5.22" + graphql-http-transformer "4.18.10" + graphql-key-transformer "2.23.23" + graphql-predictions-transformer "2.5.22" + graphql-transformer-core "6.30.0" + graphql-versioned-transformer "4.17.23" + ignore "^5.1.8" + import-from "^3.0.0" + import-global "^0.1.0" + ini "^1.3.5" + inquirer "^7.3.3" + is-wsl "^2.2.0" + jose "^2.0.2" + lodash "^4.17.21" + lodash.throttle "^4.1.1" + moment "^2.24.0" + netmask "^2.0.2" + node-fetch "^2.6.1" + ora "^4.0.3" + promise-sequential "^1.1.1" + proxy-agent "^5.0.0" + rimraf "^3.0.0" + xstate "^4.14.0" + +amplify-util-import@1.5.13: + version "1.5.13" + resolved "https://registry.yarnpkg.com/amplify-util-import/-/amplify-util-import-1.5.13.tgz#ab191c3c1cc3e6ac5cf1089fd2c9ca35042107b7" + integrity sha512-OriJeF7l3pEv4PDjVjPh7ElhMnJE2HiMoMgTs5sdZbkqyigisEoEn1EWrdfzJNT7eBsBH8xDKuZjs3yRqooc4Q== + dependencies: + amplify-cli-core "1.30.0" + aws-sdk "^2.963.0" + ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" @@ -8063,6 +8390,20 @@ boxen@^4.2.0: type-fest "^0.8.1" widest-line "^3.1.0" +boxen@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + bplist-creator@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.0.8.tgz#56b2a6e79e9aec3fc33bf831d09347d73794e79c" @@ -8822,7 +9163,7 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^2.2.0: +cli-boxes@^2.2.0, cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== @@ -12559,12 +12900,12 @@ global-dirs@^0.1.0, global-dirs@^0.1.1: dependencies: ini "^1.3.4" -global-dirs@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" - integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== dependencies: - ini "1.3.7" + ini "2.0.0" global-modules@2.0.0: version "2.0.0" @@ -12710,6 +13051,17 @@ graphiql@^1.3.2: graphql-language-service "^3.1.2" markdown-it "^10.0.0" +graphql-auth-transformer@6.24.24: + version "6.24.24" + resolved "https://registry.yarnpkg.com/graphql-auth-transformer/-/graphql-auth-transformer-6.24.24.tgz#b56728f2a830f16c68ff15d7371d2fdac459cbd3" + integrity sha512-zktdk6+RnILB3QlaW1q+n8lOgbRiPOb5RMTxvXYRWKOfkQyE23uf9uzuTRpDUHtg4ldIqSPTcEE1H/+IzKURaQ== + dependencies: + graphql "^14.5.8" + graphql-connection-transformer "4.21.23" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + graphql-config@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-2.2.2.tgz#a4b577826bba9b83e7b0f6cd617be43ca67da045" @@ -12721,6 +13073,66 @@ graphql-config@^2.2.1: lodash "^4.17.4" minimatch "^3.0.4" +graphql-connection-transformer@4.21.23: + version "4.21.23" + resolved "https://registry.yarnpkg.com/graphql-connection-transformer/-/graphql-connection-transformer-4.21.23.tgz#77a1c6ad4f6d4b2708c89ee96a08ba38165d0088" + integrity sha512-R24n5Ea4og3s9vI7emMKydVCCpHaTSxpdXfSTUQCKqFwXuaIQM8pUTZqhanAkaRJzdXg7n43f2VryFwTUL4GwA== + dependencies: + cloudform-types "^4.2.0" + graphql "^14.5.8" + graphql-dynamodb-transformer "6.22.23" + graphql-key-transformer "2.23.23" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + +graphql-dynamodb-transformer@6.22.23: + version "6.22.23" + resolved "https://registry.yarnpkg.com/graphql-dynamodb-transformer/-/graphql-dynamodb-transformer-6.22.23.tgz#6c931fc2b4ecabbf56d7ebc1a2ed5849c542bd5f" + integrity sha512-Q7qW/NdiQy8Lu4kjWo2P4I9augDL9RePkyQQHP+x3Z2pYsXFf8Z9FuufiH1RQYZ8a89wboVpfunUByYYn1LVLw== + dependencies: + "@types/pluralize" "^0.0.29" + cloudform-types "^4.2.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + md5 "^2.2.1" + pluralize "^8.0.0" + +graphql-elasticsearch-transformer@4.12.3: + version "4.12.3" + resolved "https://registry.yarnpkg.com/graphql-elasticsearch-transformer/-/graphql-elasticsearch-transformer-4.12.3.tgz#6e8192d291c0a941c376ecc645d7ded21e0b9579" + integrity sha512-XsNtkaIfJMrV4bJltdMAlZ1v3HodC3Y+Qkyzgh8uQozHV0YhZzTpwY9tpnpKO0Z+PK34Jsuy5B3fFJlrkgUAuw== + dependencies: + cloudform-types "^4.2.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + +graphql-function-transformer@2.5.22: + version "2.5.22" + resolved "https://registry.yarnpkg.com/graphql-function-transformer/-/graphql-function-transformer-2.5.22.tgz#b3f65d74e4955f292468ee08d24d3b02b7d5e52c" + integrity sha512-Yyn20ZtrwBS444pp0Qzp+hEwQGBE+Wemg4OK7Np6gGWu1s9XTgYhzvdSkm8l36FR3SYDxKu+sWFZ96mdm6Q+Eg== + dependencies: + cloudform-types "^4.2.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + +graphql-http-transformer@4.18.10: + version "4.18.10" + resolved "https://registry.yarnpkg.com/graphql-http-transformer/-/graphql-http-transformer-4.18.10.tgz#70b69d095f492a9c77899ed3da13a50f417d918d" + integrity sha512-sPowKCAd2+Tf6KdpTj46l6i8aNI9m9nTur+sHpMKPAqS0sxhCFpBUsz0Xdy2mks6mZWKG9V4NFDh5FaoD816Og== + dependencies: + cloudform-types "^4.2.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + graphql-import@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.7.1.tgz#4add8d91a5f752d764b0a4a7a461fcd93136f223" @@ -12734,6 +13146,18 @@ graphql-iso-date@^3.6.1: resolved "https://registry.yarnpkg.com/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz#bd2d0dc886e0f954cbbbc496bbf1d480b57ffa96" integrity sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q== +graphql-key-transformer@2.23.23: + version "2.23.23" + resolved "https://registry.yarnpkg.com/graphql-key-transformer/-/graphql-key-transformer-2.23.23.tgz#bfd40f63a828a45306fff0230df498603278c696" + integrity sha512-GWRGRXV5zwsPDKuFwnU4bTuzeeQfXBMO0sL/V0SeslkHv6HCI/rPEkffh98bz5W0H4T7tqzfX7/y8zv7125Mfw== + dependencies: + cloudform-types "^4.2.0" + graphql "^14.5.8" + graphql-dynamodb-transformer "6.22.23" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + graphql-language-service-interface@^2.8.2: version "2.8.4" resolved "https://registry.yarnpkg.com/graphql-language-service-interface/-/graphql-language-service-interface-2.8.4.tgz#3ff31754e9b295b1abc26b97d286c00835aacff0" @@ -12772,6 +13196,17 @@ graphql-language-service@^3.1.2: graphql-language-service-interface "^2.8.2" graphql-language-service-types "^1.8.0" +graphql-predictions-transformer@2.5.22: + version "2.5.22" + resolved "https://registry.yarnpkg.com/graphql-predictions-transformer/-/graphql-predictions-transformer-2.5.22.tgz#4ea2b2d5afa168916901732b017b2a7d62b9b9b6" + integrity sha512-5OFl7laJ1s0B9WEv7zf98VwXh4KEJvry836k0ggOr8/Oo1i6ZUcf1UFMk8VK7p811b/hLu4pO6rPfLkNbssKBA== + dependencies: + cloudform-types "^4.2.0" + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + graphql-request@^1.5.0: version "1.8.2" resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-1.8.2.tgz#398d10ae15c585676741bde3fc01d5ca948f8fbe" @@ -12825,11 +13260,34 @@ graphql-tools@^4.0.6: iterall "^1.1.3" uuid "^3.1.0" +graphql-transformer-core@6.30.0: + version "6.30.0" + resolved "https://registry.yarnpkg.com/graphql-transformer-core/-/graphql-transformer-core-6.30.0.tgz#c532ad8b01400bdf5044e5f43f944f013750a66b" + integrity sha512-YBxdJpUE8K9zenHaLubnLhKlJBjMYfJgzwT5Zv/IowAwyTvNUoDsnox6M1ov8kEHru2VV+eKVqQjlswKWHnXog== + dependencies: + amplify-cli-core "1.30.0" + cloudform-types "^4.2.0" + deep-diff "^1.0.2" + fs-extra "^8.1.0" + glob "^7.1.6" + graphql "^14.5.8" + graphql-transformer-common "4.19.10" + graphql-type-json@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115" integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg== +graphql-versioned-transformer@4.17.23: + version "4.17.23" + resolved "https://registry.yarnpkg.com/graphql-versioned-transformer/-/graphql-versioned-transformer-4.17.23.tgz#c4157542db61b98af4460370749a4fc88c6e714f" + integrity sha512-pYBa/1o9fwX4WX4HKmxDXYtdSrOgUiuj1d0gmuYLoFPTzkl3sOoJ1WUskX5Jk8az0bvuw91hQP/NAFeU8p/tSw== + dependencies: + graphql "^14.5.8" + graphql-mapping-template "4.18.3" + graphql-transformer-common "4.19.10" + graphql-transformer-core "6.30.0" + graphql-ws@^4.3.2: version "4.9.0" resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-4.9.0.tgz#5cfd8bb490b35e86583d8322f5d5d099c26e365c" @@ -13589,10 +14047,10 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.8" @@ -13979,13 +14437,13 @@ is-gzip@^1.0.0: resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM= -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" + global-dirs "^3.0.0" + is-path-inside "^3.0.2" is-interactive@^1.0.0: version "1.0.0" @@ -14019,10 +14477,10 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== is-number-object@^1.0.4: version "1.0.6" @@ -14089,7 +14547,7 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" -is-path-inside@^3.0.1: +is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -15267,7 +15725,7 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" -latest-version@^5.0.0: +latest-version@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== @@ -19074,7 +19532,7 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.0.1: +pupa@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== @@ -22270,22 +22728,23 @@ upath@^2.0.1: resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -update-notifier@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" - integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== dependencies: - boxen "^4.2.0" - chalk "^3.0.0" + boxen "^5.0.0" + chalk "^4.1.0" configstore "^5.0.1" has-yarn "^2.1.0" import-lazy "^2.1.0" is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" semver-diff "^3.1.1" xdg-basedir "^4.0.0" @@ -22541,9 +23000,9 @@ vm-browserify@^1.0.1: integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== vm2@^3.9.3: - version "3.9.3" - resolved "https://registry.npmjs.org/vm2/-/vm2-3.9.3.tgz#29917f6cc081cc43a3f580c26c5b553fd3c91f40" - integrity sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q== + version "3.9.5" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.5.tgz#5288044860b4bbace443101fcd3bddb2a0aa2496" + integrity sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng== vorpal@^1.12.0: version "1.12.0"