├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── amplify.yml ├── amplify ├── .config │ └── project-config.json └── backend │ ├── api │ └── StoreApi │ │ ├── parameters.json │ │ ├── resolvers │ │ ├── Mutation.addOrder.req.vtl │ │ ├── Mutation.addOrder.res.vtl │ │ ├── Mutation.addPaymentAccount.req.vtl │ │ ├── Mutation.addPaymentAccount.res.vtl │ │ ├── Query.getPaymentAccounts.req.vtl │ │ ├── Query.getPaymentAccounts.res.vtl │ │ ├── Query.getUserInfo.req.vtl │ │ ├── Query.getUserInfo.res.vtl │ │ ├── Query.listRecentOrders.req.vtl │ │ ├── Query.listRecentOrders.res.vtl │ │ ├── Query.listRecentOrdersByStatus.req.vtl │ │ └── Query.listRecentOrdersByStatus.res.vtl │ │ ├── schema.graphql │ │ ├── stacks │ │ └── CustomResources.json │ │ └── transform.conf.json │ ├── auth │ └── storeapp039c491c │ │ ├── parameters.json │ │ └── storeapp039c491c-cloudformation-template.yml │ ├── backend-config.json │ ├── payment │ └── service │ │ └── template.json │ └── user │ └── service │ ├── parameters.json │ └── template.json ├── images ├── add_repository.png ├── build_settings.png ├── connect_local_to_cloud_backend.png └── solution_overview.png ├── orderapi ├── .gitignore ├── .graphqlconfig.yml ├── amplify │ ├── .config │ │ └── project-config.json │ └── backend │ │ ├── api │ │ └── OrderApi │ │ │ ├── parameters.json │ │ │ ├── schema.graphql │ │ │ ├── stacks │ │ │ └── CustomResources.json │ │ │ └── transform.conf.json │ │ └── backend-config.json ├── deploy_OrderApi.sh └── src │ └── graphql │ ├── mutations.js │ ├── queries.js │ ├── schema.json │ └── subscriptions.js ├── package.json ├── paymentapp ├── Dockerfile ├── requirements.txt └── src │ └── app.py ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── Nav.js ├── graphql ├── mutations.js ├── queries.js ├── schema.json └── subscriptions.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | #amplify 26 | amplify/\#current-cloud-backend 27 | amplify/.config/local-* 28 | amplify/backend/amplify-meta.json 29 | amplify/backend/awscloudformation 30 | build/ 31 | dist/ 32 | node_modules/ 33 | aws-exports.js 34 | awsconfiguration.json 35 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS AppSync Microservices-Access-Layer Reference Architecture 2 | 3 | ![solution_overview](images/solution_overview.png) 4 | 5 | The AWS AppSync Serverless GraphQL microservice-access-layer reference architecture showcases AWS AppSync as a single interface to access and combine data from multiple microservices running in different environments : 6 | 7 | - **UserService:** RESTful API built using Amazon API Gateway and AWS Lambda 8 | - **OrderService:** GraphQL API on different AWS AppSync endpoint 9 | - **PaymentService:** Containerized service running inside an isolated VPC with no internet access 10 | 11 | The sample app is based on a very simple webstore where users can login, and interact with different services. 12 | 13 | 14 | 15 | ### Quicklinks 16 | 17 | - [One-Click Deploy with the Amplify Console](#one-click-deploy-with-the-amplify-console) 18 | - [Setup local environment](#setup-local-environment) 19 | - [Clean Up](#clean-up) 20 | 21 | ## Getting Started 22 | 23 | ## One-Click Deploy with the Amplify Console 24 | 25 | 1. **Prerequisites** 26 | 27 | 28 | - [AWS Account](https://aws.amazon.com/mobile/details) with appropriate permissions to create the related resources 29 | - [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) with output configured as JSON `(pip install awscli --upgrade --user)` 30 | - [Install Docker](https://docs.docker.com/install/) 31 | 32 | - If using Windows, you'll need the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install-win10) 33 | 34 | - Create new Amazon Elastic Container Registry (Amazon ECR) (in the same region where amplify app will be deployed) and push payment application to container registry. 35 | 36 | ```bash 37 | git clone git@github.com:/appsync-refarch-microserviceaccesslayer.git 38 | 39 | 40 | cd appsync-refarch-microserviceaccesslayer/paymentapp 41 | REPO=$(aws ecr create-repository --repository-name paymentapp --image-tag-mutability IMMUTABLE --output text --query repository.repositoryUri) 42 | 43 | echo '# get-login' 44 | $(aws ecr get-login --no-include-email) 45 | 46 | echo '# repo' 47 | echo ${REPO} 48 | 49 | echo '# build docker image' 50 | docker build . -t python/paymentapp:v1 51 | 52 | echo '# tag this app as version 1' 53 | docker tag python/paymentapp:v1 ${REPO}:v1 54 | 55 | echo '# docker push' 56 | docker push ${REPO}:v1 57 | 58 | ``` 59 | 60 | 2. Click the button to load the AWS Amplify Console. Amplify Console will build and deploy your backend and frontend in a single workflow. the end to end deployment should take around 20 minutes: 61 | 62 |

63 | 64 | Deploy to Amplify Console 65 | 66 |

67 | 68 | 3. Connect your source code from a Git repository (select GitHub if you are cloning this sample), authorize AWS Amplify to connect to this reppository and select a branch. 69 | 70 | ![Add Repository](images/add_repository.png) 71 | 72 | 4. Create new environment, branch and create an IAM role (with necessary permissions) 73 | 74 | ![Configure Build](images/build_settings.png) 75 | 76 | **Note:** If you use any other branch than master, make sure to update resource path in resolver mapping template `./amplify/backend/api/StoreApi/resolvers/Query.getUserInfo.req.vtl`. You can read more about configuring HTTP Resolver for AWS AppSync [here](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-http-resolvers.html) 77 | 78 | 79 | 5. Wait for the build, deployment and verification steps 80 | 6. Access your app from the hosted site generated by the Amplify Console(https://master.xxxxxxxx.amplifyapp.com) 81 | 82 | ## Setup local environment 83 | 84 | 1. **Prerequisites** 85 | 86 | 87 | - [NodeJS](https://nodejs.org/en/download/) with [NPM](https://docs.npmjs.com/getting-started/installing-node) 88 | - [AWS Amplify CLI](https://github.com/aws-amplify/amplify-cli) configured for a region where [AWS AppSync](https://docs.aws.amazon.com/general/latest/gr/rande.html) and all other services in use are available `(npm install -g @aws-amplify/cli)` 89 | - [Install JQ](https://stedolan.github.io/jq/) 90 | 91 | 92 | 2. You have already cloned the repo in previous step. Change directory to application root and install dependencies 93 | 94 | ```bash 95 | cd appsync-refarch-microserviceaccesslayer && npm install 96 | ``` 97 | 98 | 3. Select your app in amplify console. All Apps -> aws-appsync-refarch-microservices -> Backend Environment -> (extend) Edit backend at the bottom. 99 | 100 | ![connect_local_to_cloud_backend](images/connect_local_to_cloud_backend.png) 101 | 102 | 103 | 4. Paste this command into your terminal at the root of your repo (when prompted accept defaults for runtime and source path) 104 | 105 | ``` 106 | amplify pull --appId --envName 107 | 108 | ? Do you want to use an AWS profile? Yes 109 | ? Please choose the profile you want to use default 110 | Amplify AppID found: xxxxxx1234sd. Amplify App name is: aws-appsync-refarch-microservices} 111 | Backend environment master found in Amplify Console app: aws-appsync-refarch-microservices 112 | ? Choose your default editor: Visual Studio Code 113 | ? Choose the type of app that you're building javascript 114 | Please tell us about your project 115 | ? What javascript framework are you using react 116 | ? Source Directory Path: src 117 | ? Distribution Directory Path: build 118 | ? Build Command: npm run-script build 119 | ? Start Command: npm run-script start 120 | 121 | ? Do you plan on modifying this backend? Yes 122 | 123 | Successfully pulled backend environment master from the cloud. 124 | Run 'amplify pull' to sync upstream changes. 125 | 126 | ``` 127 | 128 | 5. Start and work on your front end locally. This will connect to the backend deployed in AWS. 129 | 130 | ``` 131 | npm start 132 | ``` 133 | 134 | 135 | ## Clean up 136 | 137 | To clean up the project, you can simply use; 138 | 139 | ```bash 140 | amplify delete 141 | ``` -------------------------------------------------------------------------------- /amplify.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | backend: 3 | phases: 4 | build: 5 | commands: 6 | - echo "# update aws cli " 7 | - npm install -g @aws-amplify/cli 8 | - echo "# install jq" 9 | - yum install jq -y 10 | - echo "# cd orderapi" 11 | - cd orderapi 12 | - ./deploy_OrderApi.sh 13 | - echo "# Get Order API Endpoint" 14 | - export GRAPHQL_ENDPOINT=$(jq -r '.api[(.api | keys)[0]].output.GraphQLAPIEndpointOutput' ./amplify/#current-cloud-backend/amplify-meta.json) 15 | - echo "# Get domain name from endpoint" 16 | - export GRAPHQL_ENDPOINT=$(echo $GRAPHQL_ENDPOINT | cut -d / -f 1-3) 17 | - echo "# Print domain name" 18 | - echo $GRAPHQL_ENDPOINT 19 | - echo "# Back to root folder" 20 | - cd .. 21 | - echo "# Pass order API endpoint to Store app" 22 | - jq '.OrderServiceEndpoint = $newVal' --arg newVal $GRAPHQL_ENDPOINT ./amplify/backend/api/StoreApi/parameters.json > ./amplify/backend/api/StoreApi/parameters_tmp.json && mv ./amplify/backend/api/StoreApi/parameters_tmp.json ./amplify/backend/api/StoreApi/parameters.json 23 | - amplifyPush --simple 24 | frontend: 25 | phases: 26 | preBuild: 27 | commands: 28 | - npm install 29 | build: 30 | commands: 31 | - npm run build 32 | artifacts: 33 | baseDirectory: build 34 | files: 35 | - "**/*" 36 | cache: 37 | paths: 38 | - node_modules/**/* 39 | -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "storeapp", 3 | "version": "2.0", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "build", 10 | "BuildCommand": "npm run-script build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSyncApiName": "StoreApi", 3 | "DynamoDBBillingMode": "PAY_PER_REQUEST", 4 | "DynamoDBEnableServerSideEncryption": "false", 5 | "AuthCognitoUserPoolId": { 6 | "Fn::GetAtt": [ 7 | "authstoreapp039c491c", 8 | "Outputs.UserPoolId" 9 | ] 10 | }, 11 | "UserServiceEndpoint": { 12 | "Fn::GetAtt": [ 13 | "userservice", 14 | "Outputs.UserServiceEndpoint" 15 | ] 16 | }, 17 | "UserServiceApiId": { 18 | "Fn::GetAtt": [ 19 | "userservice", 20 | "Outputs.UserServiceApiId" 21 | ] 22 | }, 23 | "PaymentServiceEndpoint": { 24 | "Fn::GetAtt": [ 25 | "paymentservice", 26 | "Outputs.PaymentServiceEndpoint" 27 | ] 28 | }, 29 | "PaymentServiceConsumerSecurityGroup": { 30 | "Fn::GetAtt": [ 31 | "paymentservice", 32 | "Outputs.ConsumerSecurityGroup" 33 | ] 34 | }, 35 | "PaymentServiceConsumerSubnet01": { 36 | "Fn::GetAtt": [ 37 | "paymentservice", 38 | "Outputs.ConsumerSubnet01" 39 | ] 40 | }, 41 | "PaymentServiceConsumerSubnet02": { 42 | "Fn::GetAtt": [ 43 | "paymentservice", 44 | "Outputs.ConsumerSubnet02" 45 | ] 46 | }, 47 | "CognitoUserPoolId": { 48 | "Fn::GetAtt": [ 49 | "authstoreapp039c491c", 50 | "Outputs.UserPoolId" 51 | ] 52 | }, 53 | "OrderServiceEndpoint": "https://www.amazon.com" 54 | } -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Mutation.addOrder.req.vtl: -------------------------------------------------------------------------------- 1 | #** 2 | Make an arbitrary HTTP call when this field is listed in a query selection set. 3 | The "relativePath" is the resource path relative to the root URL provided when you 4 | created the HTTP data source. Pass a "params" object to forward headers, body, 5 | and query parameters to the HTTP endpoint. 6 | *# 7 | #if($context.identity.sub == $context.args.userId) 8 | #set($order = "mutation AddOrder { 9 | addOrder(input : { 10 | userId: ""$context.args.userId"" 11 | status: PENDING 12 | orderDateTime: ""$context.args.orderDateTime"" 13 | details: ""$context.args.details"" 14 | orderId: ""$util.autoId()"" 15 | }) { 16 | userId 17 | status 18 | orderDateTime 19 | details 20 | orderId 21 | } 22 | }") 23 | 24 | { 25 | "version": "2018-05-29", 26 | "method": "POST", 27 | "resourcePath": "/graphql", 28 | "params":{ 29 | "body": { 30 | "query": "$util.escapeJavaScript($order)" 31 | }, 32 | "headers":{ 33 | "Content-Type": "application/json", 34 | "x-access-token" : "$context.request.headers.x-access-token" 35 | } 36 | } 37 | } 38 | #else 39 | $utils.unauthorized() 40 | #end -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Mutation.addOrder.res.vtl: -------------------------------------------------------------------------------- 1 | ## Raise a GraphQL field error in case of a datasource invocation error 2 | #if($ctx.error) 3 | $util.error($ctx.error.message, $ctx.error.type) 4 | #end 5 | ## if the response status code is not 200, then return an error. Else return the body ** 6 | #if($ctx.result.statusCode == 200) 7 | ## If response is 200, return the body. 8 | $util.toJson($util.parseJson($ctx.result.body).data.addOrder) 9 | #else 10 | ## If response is not 200, append the response to error block. 11 | $utils.appendError($ctx.result.body, $ctx.result.statusCode) 12 | #end -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Mutation.addPaymentAccount.req.vtl: -------------------------------------------------------------------------------- 1 | #** 2 | The value of 'payload' after the template has been evaluated 3 | will be passed as the event to AWS Lambda. 4 | *# 5 | #if($context.identity.sub == $context.args.userId) 6 | { 7 | "version" : "2017-02-28", 8 | "operation": "Invoke", 9 | "payload": { 10 | "userId" : $util.toJson($context.args.userId), 11 | "type" : $util.toJson($context.args.paymentAccountType), 12 | "details": $util.toJson($context.args.paymentAccountDetails), 13 | "resolverType" : "Mutation", 14 | "fieldName" : "addPaymentAccount" 15 | 16 | } 17 | } 18 | #else 19 | $utils.unauthorized() 20 | #end -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Mutation.addPaymentAccount.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Query.getPaymentAccounts.req.vtl: -------------------------------------------------------------------------------- 1 | #** 2 | The value of 'payload' after the template has been evaluated 3 | will be passed as the event to AWS Lambda. 4 | *# 5 | #if($context.identity.sub == $context.args.userId) 6 | { 7 | "version" : "2017-02-28", 8 | "operation": "Invoke", 9 | "payload": { 10 | "userId" : "$context.args.userId", 11 | "resolverType" : "Query", 12 | "fieldName" : "getPaymentAccounts" 13 | } 14 | } 15 | #else 16 | $utils.unauthorized() 17 | #end -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Query.getPaymentAccounts.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Query.getUserInfo.req.vtl: -------------------------------------------------------------------------------- 1 | #** 2 | Make an arbitrary HTTP call when this field is listed in a query selection set. 3 | The "relativePath" is the resource path relative to the root URL provided when you 4 | created the HTTP data source. Pass a "params" object to forward headers, body, 5 | and query parameters to the HTTP endpoint. 6 | *# 7 | #if($context.identity.username == $context.args.userName) 8 | { 9 | "version": "2018-05-29", 10 | "method": "GET", 11 | "resourcePath": "/master/user/$context.args.userName", 12 | "params": { 13 | "headers":{ 14 | "Content-Type":"application/xml", 15 | "x-access-token" : "$context.request.headers.x-access-token" 16 | } 17 | } 18 | } 19 | #else 20 | $utils.unauthorized() 21 | #end -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Query.getUserInfo.res.vtl: -------------------------------------------------------------------------------- 1 | ## return the body 2 | #if($ctx.result.statusCode == 200) 3 | ##if response is 200 4 | ## Because the response is of type XML, we are going to convert 5 | ## the result body as a map and only get the User object. 6 | $utils.toJson($utils.xml.toMap($ctx.result.body).body.userDetails) 7 | #else 8 | ##if response is not 200, return response 9 | $ctx.result.body 10 | #end -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Query.listRecentOrders.req.vtl: -------------------------------------------------------------------------------- 1 | #** 2 | Make an arbitrary HTTP call when this field is listed in a query selection set. 3 | The "relativePath" is the resource path relative to the root URL provided when you 4 | created the HTTP data source. Pass a "params" object to forward headers, body, 5 | and query parameters to the HTTP endpoint. 6 | *# 7 | #if($context.identity.sub == $context.args.userId) 8 | #set($payload = "query ListOrders { 9 | listOrders( 10 | userId: ""$context.args.userId"" 11 | orderDateTimeStatus: { 12 | le : { 13 | orderDateTime: ""$context.args.orderDateTime"" 14 | } 15 | } 16 | limit: 10 17 | 18 | ) { 19 | items { 20 | userId 21 | status 22 | orderDateTime 23 | details 24 | orderId 25 | } 26 | nextToken 27 | } 28 | }") 29 | 30 | { 31 | "version": "2018-05-29", 32 | "method": "POST", 33 | "resourcePath": "/graphql", 34 | "params":{ 35 | "body": { 36 | "query": "$util.escapeJavaScript($payload)" 37 | }, 38 | "headers":{ 39 | "Content-Type": "application/json", 40 | "x-access-token" : "$context.request.headers.x-access-token" 41 | 42 | } 43 | } 44 | } 45 | #else 46 | $utils.unauthorized() 47 | #end 48 | -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Query.listRecentOrders.res.vtl: -------------------------------------------------------------------------------- 1 | ## Raise a GraphQL field error in case of a datasource invocation error 2 | #if($ctx.error) 3 | $util.error($ctx.error.message, $ctx.error.type) 4 | #end 5 | ## if the response status code is not 200, then return an error. Else return the body ** 6 | #if($ctx.result.statusCode == 200) 7 | ## If response is 200, return the body. 8 | $util.toJson($util.parseJson($ctx.result.body).data.listOrders.items) 9 | #else 10 | ## If response is not 200, append the response to error block. 11 | $utils.appendError($ctx.result.body, $ctx.result.statusCode) 12 | #end -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Query.listRecentOrdersByStatus.req.vtl: -------------------------------------------------------------------------------- 1 | #** 2 | Make an arbitrary HTTP call when this field is listed in a query selection set. 3 | The "relativePath" is the resource path relative to the root URL provided when you 4 | created the HTTP data source. Pass a "params" object to forward headers, body, 5 | and query parameters to the HTTP endpoint. 6 | *# 7 | #if($context.identity.sub == $context.args.userId) 8 | #set($payload = "query listRecentOrdersByStatus { 9 | listOrders( 10 | userId: ""$context.args.userId"" 11 | orderDateTimeStatus: { 12 | le : { 13 | orderDateTime: ""$context.args.orderDateTime"" 14 | } 15 | } 16 | filter: { 17 | status : { 18 | eq : ""$ctx.args.status"" 19 | } 20 | } 21 | limit: 10 22 | 23 | ) { 24 | items { 25 | userId 26 | status 27 | orderDateTime 28 | details 29 | orderId 30 | } 31 | nextToken 32 | } 33 | }") 34 | 35 | { 36 | "version": "2018-05-29", 37 | "method": "POST", 38 | "resourcePath": "/graphql", 39 | "params":{ 40 | "body": { 41 | "query": "$util.escapeJavaScript($payload)", 42 | }, 43 | "headers":{ 44 | "Content-Type": "application/json", 45 | "x-access-token" : "$context.request.headers.x-access-token" 46 | 47 | } 48 | } 49 | } 50 | #else 51 | $utils.unauthorized() 52 | #end 53 | -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/resolvers/Query.listRecentOrdersByStatus.res.vtl: -------------------------------------------------------------------------------- 1 | ## Raise a GraphQL field error in case of a datasource invocation error 2 | #if($ctx.error) 3 | $util.error($ctx.error.message, $ctx.error.type) 4 | #end 5 | ## if the response status code is not 200, then return an error. Else return the body ** 6 | #if($ctx.result.statusCode == 200) 7 | ## If response is 200, return the body. 8 | $util.toJson($util.parseJson($ctx.result.body).data.listRecentOrdersByStatus) 9 | #else 10 | ## If response is not 200, append the response to error block. 11 | $utils.appendError($ctx.result.body, $ctx.result.statusCode) 12 | #end -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/schema.graphql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | mutation: Mutation 4 | subscription: Subscription 5 | } 6 | 7 | type User { 8 | userName: ID! 9 | email: String 10 | phoneNumber: String 11 | } 12 | 13 | type Order { 14 | userId: ID! 15 | status: OrderStatus 16 | orderDateTime: String! 17 | details: String! 18 | orderId: String 19 | } 20 | 21 | 22 | type PaymentAccount { 23 | userId: ID! 24 | type: String! 25 | details: String! 26 | } 27 | 28 | type Query { 29 | getPaymentAccounts(userId: ID!): [PaymentAccount] 30 | getUserInfo(userName: ID!): User 31 | listRecentOrders(userId: ID!, orderDateTime: String!) : [Order] 32 | listRecentOrdersByStatus(userId: ID!, orderDateTime: String!, status: OrderStatus! ) : [Order] 33 | 34 | } 35 | 36 | enum OrderStatus { 37 | DELIVERED 38 | IN_TRANSIT 39 | PENDING 40 | PROCESSING 41 | CANCELLED 42 | } 43 | 44 | type Mutation { 45 | addPaymentAccount(userId: ID!, paymentAccountType: String!, paymentAccountDetails: String!): PaymentAccount 46 | addOrder(userId: ID!, orderDateTime: String!, details: String!) : Order 47 | } 48 | 49 | type Subscription { 50 | addedPaymentAccount: PaymentAccount 51 | @aws_subscribe(mutations: ["addPaymentAccount"]) 52 | 53 | addedOrder: Order 54 | @aws_subscribe(mutations: ["addOrder"]) 55 | 56 | } 57 | -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/stacks/CustomResources.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiId": { 7 | "Type": "String", 8 | "Description": "The id of the AppSync API associated with this project." 9 | }, 10 | "AppSyncApiName": { 11 | "Type": "String", 12 | "Description": "The name of the AppSync API", 13 | "Default": "AppSyncSimpleTransform" 14 | }, 15 | "env": { 16 | "Type": "String", 17 | "Description": "The environment name. e.g. Dev, Test, or Production", 18 | "Default": "NONE" 19 | }, 20 | "S3DeploymentBucket": { 21 | "Type": "String", 22 | "Description": "The S3 bucket containing all deployment assets for the project." 23 | }, 24 | "S3DeploymentRootKey": { 25 | "Type": "String", 26 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root of the deployment directory." 27 | }, 28 | "PaymentServiceEndpoint": { 29 | "Type": "String", 30 | "Description": "Payment service endpoint" 31 | }, 32 | "PaymentServiceConsumerSecurityGroup": { 33 | "Type": "String", 34 | "Description": "Attach this to payment service consumer" 35 | }, 36 | "PaymentServiceConsumerSubnet01": { 37 | "Type": "String", 38 | "Description": "Put payment service consumer in this private subnet 01" 39 | }, 40 | "PaymentServiceConsumerSubnet02": { 41 | "Type": "String", 42 | "Description": "Put payment service consumer in this private subnet 02" 43 | }, 44 | "UserServiceEndpoint": { 45 | "Type": "String", 46 | "Description" : "User service endpoint" 47 | }, 48 | "UserServiceApiId": { 49 | "Type": "String", 50 | "Description" : "User service Api ID" 51 | }, 52 | "OrderServiceEndpoint" : { 53 | "Type" : "String", 54 | "Description" : "Order Service Endpoint" 55 | } 56 | }, 57 | "Resources": { 58 | "UserServiceDataSource": { 59 | "Type": "AWS::AppSync::DataSource", 60 | "Properties": { 61 | "ApiId": { 62 | "Ref": "AppSyncApiId" 63 | }, 64 | "Description": "User service data source", 65 | "HttpConfig": { 66 | "Endpoint": { 67 | "Ref": "UserServiceEndpoint" 68 | }, 69 | "AuthorizationConfig": { 70 | "AuthorizationType": "AWS_IAM", 71 | "AwsIamConfig": { 72 | "SigningRegion": { "Fn::Sub" : "${AWS::Region}" }, 73 | "SigningServiceName": "execute-api" 74 | } 75 | } 76 | }, 77 | "Name": { "Fn::Sub" : "${env}UserService" }, 78 | "Type": "HTTP", 79 | "ServiceRoleArn" : { 80 | "Fn::GetAtt": [ 81 | "UserServiceDataSourceIamRole", 82 | "Arn" 83 | ] 84 | } 85 | } 86 | }, 87 | "UserServiceDataSourceIamRole": { 88 | "Type": "AWS::IAM::Role", 89 | "Properties": { 90 | "AssumeRolePolicyDocument": { 91 | "Version": "2012-10-17", 92 | "Statement": [ 93 | { 94 | "Effect": "Allow", 95 | "Principal": { 96 | "Service": [ 97 | "appsync.amazonaws.com" 98 | ] 99 | }, 100 | "Action": [ 101 | "sts:AssumeRole" 102 | ] 103 | } 104 | ] 105 | }, 106 | "Description": "Role assigned to UserServiceDataSource", 107 | "Policies": [ 108 | { 109 | "PolicyDocument" : { 110 | "Version" : "2012-10-17", 111 | "Statement" : [ 112 | { 113 | "Action" : "execute-api:Invoke", 114 | "Effect": "Allow", 115 | "Resource" : { "Fn::Sub" : "arn:aws:execute-api:${AWS::Region}:*:${UserServiceApiId}/${env}/*" } 116 | } 117 | ] 118 | }, 119 | "PolicyName" : "UserServiceDataSourceIamPolicy" 120 | } 121 | ] 122 | } 123 | }, 124 | "QueryGetUserInfoResolver": { 125 | "Type": "AWS::AppSync::Resolver", 126 | "Properties": { 127 | "ApiId": { 128 | "Ref": "AppSyncApiId" 129 | }, 130 | "DataSourceName": { 131 | "Fn::GetAtt": [ 132 | "UserServiceDataSource", 133 | "Name" 134 | ] 135 | }, 136 | "FieldName": "getUserInfo", 137 | "Kind": "UNIT", 138 | "TypeName": "Query", 139 | "RequestMappingTemplateS3Location": { 140 | "Fn::Sub": "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.getUserInfo.req.vtl" 141 | }, 142 | "ResponseMappingTemplateS3Location": { 143 | "Fn::Sub": "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.getUserInfo.res.vtl" 144 | } 145 | } 146 | }, 147 | "PaymentServiceDataSource": { 148 | "Type": "AWS::AppSync::DataSource", 149 | "Properties": { 150 | "ApiId": { 151 | "Ref": "AppSyncApiId" 152 | }, 153 | "Description": "Payment Service data source", 154 | "LambdaConfig": { 155 | "LambdaFunctionArn": { 156 | "Fn::GetAtt": [ 157 | "PaymentServiceDataSourceLambda", 158 | "Arn" 159 | ] 160 | } 161 | }, 162 | "Name": { 163 | "Fn::Sub": "${env}PaymentService" 164 | }, 165 | "Type": "AWS_LAMBDA", 166 | "ServiceRoleArn": { 167 | "Fn::GetAtt": [ 168 | "PaymentServiceDataSourceIamRole", 169 | "Arn" 170 | ] 171 | } 172 | } 173 | }, 174 | "PaymentServiceDataSourceLambda": { 175 | "Type": "AWS::Lambda::Function", 176 | "Properties": { 177 | "Code": { 178 | "ZipFile": { 179 | "Fn::Join": [ 180 | "\n", 181 | [ 182 | "import os", 183 | "import logging", 184 | "import sys", 185 | "import json", 186 | "import urllib3", 187 | "", 188 | "http = urllib3.PoolManager()", 189 | "", 190 | "LOG_LEVEL = os.environ.get('LOG_LEVEL', '').upper()", 191 | "if LOG_LEVEL == '':", 192 | " LOG_LEVEL = 'INFO'", 193 | "", 194 | "logger = logging.getLogger()", 195 | "logger.setLevel(LOG_LEVEL)", 196 | "", 197 | "def main(event, context):", 198 | " logging.info('Incoming Event: {0}'.format(event))", 199 | " response_body = {}", 200 | "", 201 | " if event['resolverType'] == 'Query':", 202 | " get_payment_accounts_request = http.request('GET', os.environ['PAYMENT_SERVICE_ENDPOINT'] + (os.environ['PATH_GET_PAYMENT_ACCOUNTS']).replace('userId', event['userId']))", 203 | "", 204 | " logging.info('ResponseCode: {0}'.format(get_payment_accounts_request.status))", 205 | " if get_payment_accounts_request.status == 200:", 206 | " response_body = json.loads(get_payment_accounts_request.data)", 207 | " else:", 208 | " logging.error('Error: {0}'.format(sys.exc_info()))", 209 | " raise Exception('Payment Details not found')", 210 | "", 211 | " elif event['resolverType'] == 'Mutation':", 212 | "", 213 | " request_body = {}", 214 | " request_body['userId'] = event['userId']", 215 | " request_body['type'] = event['type']", 216 | " request_body['details'] = event['details']", 217 | 218 | " add_payment_account_request = http.request(", 219 | " 'POST',", 220 | " os.environ['PAYMENT_SERVICE_ENDPOINT'] + os.environ['PATH_ADD_PAYMENT_ACCOUNT'],", 221 | " body=json.dumps(request_body).encode('utf-8'),", 222 | " headers={'Content-Type': 'application/json'})", 223 | 224 | " logging.info('Response: {0}'.format(add_payment_account_request.data))", 225 | 226 | " if add_payment_account_request.status == 200:", 227 | " response_body = json.loads(add_payment_account_request.data)", 228 | " else:", 229 | " logging.error('Error: {0}'.format(sys.exc_info()))", 230 | " raise Exception('Unable to save payment details. Please try again.')", 231 | "", 232 | " return response_body" 233 | ] 234 | ] 235 | } 236 | }, 237 | "Environment": { 238 | "Variables": { 239 | "PAYMENT_SERVICE_ENDPOINT": { 240 | "Ref": "PaymentServiceEndpoint" 241 | }, 242 | "PATH_ADD_PAYMENT_ACCOUNT": "/payments/account", 243 | "PATH_GET_PAYMENT_ACCOUNTS": "/payments/accounts/userId", 244 | "LOG_LEVEL": "INFO" 245 | } 246 | }, 247 | "FunctionName": { 248 | "Fn::Sub": "${env}-PaymentServiceDataSource" 249 | }, 250 | "Handler": "index.main", 251 | "MemorySize": 512, 252 | "Role": { 253 | "Fn::GetAtt": [ 254 | "PaymentServiceDataSourceLambdaIamRole", 255 | "Arn" 256 | ] 257 | }, 258 | "Runtime": "python3.7", 259 | "Timeout": 30, 260 | "VpcConfig": { 261 | "SecurityGroupIds": [ 262 | { 263 | "Ref": "PaymentServiceConsumerSecurityGroup" 264 | } 265 | ], 266 | "SubnetIds": [ 267 | { 268 | "Ref": "PaymentServiceConsumerSubnet01" 269 | }, 270 | { 271 | "Ref": "PaymentServiceConsumerSubnet02" 272 | } 273 | ] 274 | } 275 | } 276 | }, 277 | "PaymentServiceDataSourceLambdaIamRole": { 278 | "Type": "AWS::IAM::Role", 279 | "Properties": { 280 | "AssumeRolePolicyDocument": { 281 | "Version": "2012-10-17", 282 | "Statement": [ 283 | { 284 | "Effect": "Allow", 285 | "Principal": { 286 | "Service": [ 287 | "lambda.amazonaws.com" 288 | ] 289 | }, 290 | "Action": [ 291 | "sts:AssumeRole" 292 | ] 293 | } 294 | ] 295 | }, 296 | "Description": "Role assigned to PaymentServiceDataSource Lambda", 297 | "ManagedPolicyArns": [ 298 | "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", 299 | "arn:aws:iam::aws:policy/service-role/AWSLambdaENIManagementAccess" 300 | ] 301 | } 302 | }, 303 | "PaymentServiceDataSourceIamRole": { 304 | "Type": "AWS::IAM::Role", 305 | "Properties": { 306 | "AssumeRolePolicyDocument": { 307 | "Version": "2012-10-17", 308 | "Statement": [ 309 | { 310 | "Effect": "Allow", 311 | "Principal": { 312 | "Service": [ 313 | "appsync.amazonaws.com" 314 | ] 315 | }, 316 | "Action": [ 317 | "sts:AssumeRole" 318 | ] 319 | } 320 | ] 321 | }, 322 | "Description": "Role assigned to PaymentServiceDataSource", 323 | "Policies": [ 324 | { 325 | "PolicyDocument": { 326 | "Version": "2012-10-17", 327 | "Statement": [ 328 | { 329 | "Action": "lambda:InvokeFunction", 330 | "Effect": "Allow", 331 | "Resource": { 332 | "Fn::GetAtt": [ 333 | "PaymentServiceDataSourceLambda", 334 | "Arn" 335 | ] 336 | } 337 | } 338 | ] 339 | }, 340 | "PolicyName": "AppSyncLambdaInvokePolicy" 341 | } 342 | ] 343 | } 344 | }, 345 | "QueryGetPaymentMethodsResolver": { 346 | "Type": "AWS::AppSync::Resolver", 347 | "Properties": { 348 | "ApiId": { 349 | "Ref": "AppSyncApiId" 350 | }, 351 | "DataSourceName": { 352 | "Fn::GetAtt": [ 353 | "PaymentServiceDataSource", 354 | "Name" 355 | ] 356 | }, 357 | "FieldName": "getPaymentAccounts", 358 | "Kind": "UNIT", 359 | "TypeName": "Query", 360 | "RequestMappingTemplateS3Location": { 361 | "Fn::Sub": [ 362 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.getPaymentAccounts.req.vtl", 363 | { 364 | "S3DeploymentBucket": { 365 | "Ref": "S3DeploymentBucket" 366 | }, 367 | "S3DeploymentRootKey": { 368 | "Ref": "S3DeploymentRootKey" 369 | } 370 | } 371 | ] 372 | }, 373 | "ResponseMappingTemplateS3Location": { 374 | "Fn::Sub": [ 375 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.getPaymentAccounts.res.vtl", 376 | { 377 | "S3DeploymentBucket": { 378 | "Ref": "S3DeploymentBucket" 379 | }, 380 | "S3DeploymentRootKey": { 381 | "Ref": "S3DeploymentRootKey" 382 | } 383 | } 384 | ] 385 | } 386 | } 387 | }, 388 | "MutationAddPaymentResolver": { 389 | "Type": "AWS::AppSync::Resolver", 390 | "Properties": { 391 | "ApiId": { 392 | "Ref": "AppSyncApiId" 393 | }, 394 | "DataSourceName": { 395 | "Fn::GetAtt": [ 396 | "PaymentServiceDataSource", 397 | "Name" 398 | ] 399 | }, 400 | "FieldName": "addPaymentAccount", 401 | "Kind": "UNIT", 402 | "TypeName": "Mutation", 403 | "RequestMappingTemplateS3Location": { 404 | "Fn::Sub": [ 405 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.addPaymentAccount.req.vtl", 406 | { 407 | "S3DeploymentBucket": { 408 | "Ref": "S3DeploymentBucket" 409 | }, 410 | "S3DeploymentRootKey": { 411 | "Ref": "S3DeploymentRootKey" 412 | } 413 | } 414 | ] 415 | }, 416 | "ResponseMappingTemplateS3Location": { 417 | "Fn::Sub": [ 418 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.addPaymentAccount.res.vtl", 419 | { 420 | "S3DeploymentBucket": { 421 | "Ref": "S3DeploymentBucket" 422 | }, 423 | "S3DeploymentRootKey": { 424 | "Ref": "S3DeploymentRootKey" 425 | } 426 | } 427 | ] 428 | } 429 | } 430 | }, 431 | "OrderServiceDataSource": { 432 | "Type": "AWS::AppSync::DataSource", 433 | "Properties": { 434 | "ApiId": { 435 | "Ref": "AppSyncApiId" 436 | }, 437 | "Description": "Order service data source", 438 | "HttpConfig": { 439 | "Endpoint": { 440 | "Ref": "OrderServiceEndpoint" 441 | }, 442 | "AuthorizationConfig": { 443 | "AuthorizationType": "AWS_IAM", 444 | "AwsIamConfig": { 445 | "SigningRegion": { "Fn::Sub" : "${AWS::Region}" }, 446 | "SigningServiceName": "appsync" 447 | } 448 | } 449 | }, 450 | "Name": { "Fn::Sub" : "${env}OrderService" }, 451 | "Type": "HTTP", 452 | "ServiceRoleArn" : { 453 | "Fn::GetAtt": [ 454 | "OrderServiceDataSourceIamRole", 455 | "Arn" 456 | ] 457 | } 458 | } 459 | }, 460 | "OrderServiceDataSourceIamRole": { 461 | "Type": "AWS::IAM::Role", 462 | "Properties": { 463 | "AssumeRolePolicyDocument": { 464 | "Version": "2012-10-17", 465 | "Statement": [ 466 | { 467 | "Effect": "Allow", 468 | "Principal": { 469 | "Service": [ 470 | "appsync.amazonaws.com" 471 | ] 472 | }, 473 | "Action": [ 474 | "sts:AssumeRole" 475 | ] 476 | } 477 | ] 478 | }, 479 | "Description": "Role assigned to OrderServiceDataSource", 480 | "Policies": [ 481 | { 482 | "PolicyDocument" : { 483 | "Version" : "2012-10-17", 484 | "Statement" : [ 485 | { 486 | "Action" : "appsync:GraphQL", 487 | "Effect": "Allow", 488 | "Resource" : { "Fn::Sub" : "arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:apis/*" } 489 | } 490 | ] 491 | }, 492 | "PolicyName" : "OrderServiceDataSourceIamPolicy" 493 | } 494 | ] 495 | } 496 | }, 497 | "QueryListRecentOrdersResolver": { 498 | "Type": "AWS::AppSync::Resolver", 499 | "Properties": { 500 | "ApiId": { 501 | "Ref": "AppSyncApiId" 502 | }, 503 | "DataSourceName": { 504 | "Fn::GetAtt": [ 505 | "OrderServiceDataSource", 506 | "Name" 507 | ] 508 | }, 509 | "FieldName": "listRecentOrders", 510 | "Kind": "UNIT", 511 | "TypeName": "Query", 512 | "RequestMappingTemplateS3Location": { 513 | "Fn::Sub": "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.listRecentOrders.req.vtl" 514 | }, 515 | "ResponseMappingTemplateS3Location": { 516 | "Fn::Sub": "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.listRecentOrders.res.vtl" 517 | } 518 | } 519 | }, 520 | "QueryListRecentOrdersByStatusResolver": { 521 | "Type": "AWS::AppSync::Resolver", 522 | "Properties": { 523 | "ApiId": { 524 | "Ref": "AppSyncApiId" 525 | }, 526 | "DataSourceName": { 527 | "Fn::GetAtt": [ 528 | "OrderServiceDataSource", 529 | "Name" 530 | ] 531 | }, 532 | "FieldName": "listRecentOrdersByStatus", 533 | "Kind": "UNIT", 534 | "TypeName": "Query", 535 | "RequestMappingTemplateS3Location": { 536 | "Fn::Sub": "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.listRecentOrdersByStatus.req.vtl" 537 | }, 538 | "ResponseMappingTemplateS3Location": { 539 | "Fn::Sub": "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.listRecentOrdersByStatus.res.vtl" 540 | } 541 | } 542 | }, 543 | "MutationAddOrderResolver": { 544 | "Type": "AWS::AppSync::Resolver", 545 | "Properties": { 546 | "ApiId": { 547 | "Ref": "AppSyncApiId" 548 | }, 549 | "DataSourceName": { 550 | "Fn::GetAtt": [ 551 | "OrderServiceDataSource", 552 | "Name" 553 | ] 554 | }, 555 | "FieldName": "addOrder", 556 | "Kind": "UNIT", 557 | "TypeName": "Mutation", 558 | "RequestMappingTemplateS3Location": { 559 | "Fn::Sub": "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.addOrder.req.vtl" 560 | }, 561 | "ResponseMappingTemplateS3Location": { 562 | "Fn::Sub": "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.addOrder.res.vtl" 563 | } 564 | } 565 | } 566 | 567 | }, 568 | "Conditions": { 569 | "HasEnvironmentParameter": { 570 | "Fn::Not": [ 571 | { 572 | "Fn::Equals": [ 573 | { 574 | "Ref": "env" 575 | }, 576 | "NONE" 577 | ] 578 | } 579 | ] 580 | }, 581 | "AlwaysFalse": { 582 | "Fn::Equals": [ 583 | "true", 584 | "false" 585 | ] 586 | } 587 | }, 588 | "Outputs": { 589 | "EmptyOutput": { 590 | "Description": "An empty output. You may delete this if you have at least one resource above.", 591 | "Value": "" 592 | } 593 | } 594 | } -------------------------------------------------------------------------------- /amplify/backend/api/StoreApi/transform.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 4 3 | } -------------------------------------------------------------------------------- /amplify/backend/auth/storeapp039c491c/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "identityPoolName": "storeapp039c491c_identitypool_039c491c", 3 | "allowUnauthenticatedIdentities": false, 4 | "resourceNameTruncated": "storea039c491c", 5 | "userPoolName": "storeapp039c491c_userpool_039c491c", 6 | "autoVerifiedAttributes": [ 7 | "email" 8 | ], 9 | "mfaConfiguration": "OFF", 10 | "mfaTypes": [ 11 | "SMS Text Message" 12 | ], 13 | "smsAuthenticationMessage": "Your authentication code is {####}", 14 | "smsVerificationMessage": "Your verification code is {####}", 15 | "emailVerificationSubject": "Your verification code", 16 | "emailVerificationMessage": "Your verification code is {####}", 17 | "defaultPasswordPolicy": false, 18 | "passwordPolicyMinLength": 8, 19 | "passwordPolicyCharacters": [], 20 | "requiredAttributes": [ 21 | "email" 22 | ], 23 | "userpoolClientGenerateSecret": true, 24 | "userpoolClientRefreshTokenValidity": 30, 25 | "userpoolClientWriteAttributes": [ 26 | "email" 27 | ], 28 | "userpoolClientReadAttributes": [ 29 | "email" 30 | ], 31 | "userpoolClientLambdaRole": "storea039c491c_userpoolclient_lambda_role", 32 | "userpoolClientSetAttributes": false, 33 | "resourceName": "storeapp039c491c", 34 | "authSelections": "identityPoolAndUserPool", 35 | "authRoleArn": { 36 | "Fn::GetAtt": [ 37 | "AuthRole", 38 | "Arn" 39 | ] 40 | }, 41 | "unauthRoleArn": { 42 | "Fn::GetAtt": [ 43 | "UnauthRole", 44 | "Arn" 45 | ] 46 | }, 47 | "useDefault": "default", 48 | "dependsOn": [] 49 | } -------------------------------------------------------------------------------- /amplify/backend/auth/storeapp039c491c/storeapp039c491c-cloudformation-template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | 3 | Parameters: 4 | env: 5 | Type: String 6 | authRoleArn: 7 | Type: String 8 | unauthRoleArn: 9 | Type: String 10 | 11 | 12 | 13 | 14 | identityPoolName: 15 | Type: String 16 | 17 | allowUnauthenticatedIdentities: 18 | Type: String 19 | 20 | resourceNameTruncated: 21 | Type: String 22 | 23 | userPoolName: 24 | Type: String 25 | 26 | autoVerifiedAttributes: 27 | Type: CommaDelimitedList 28 | 29 | mfaConfiguration: 30 | Type: String 31 | 32 | mfaTypes: 33 | Type: CommaDelimitedList 34 | 35 | smsAuthenticationMessage: 36 | Type: String 37 | 38 | smsVerificationMessage: 39 | Type: String 40 | 41 | emailVerificationSubject: 42 | Type: String 43 | 44 | emailVerificationMessage: 45 | Type: String 46 | 47 | defaultPasswordPolicy: 48 | Type: String 49 | 50 | passwordPolicyMinLength: 51 | Type: Number 52 | 53 | passwordPolicyCharacters: 54 | Type: CommaDelimitedList 55 | 56 | requiredAttributes: 57 | Type: CommaDelimitedList 58 | 59 | userpoolClientGenerateSecret: 60 | Type: String 61 | 62 | userpoolClientRefreshTokenValidity: 63 | Type: Number 64 | 65 | userpoolClientWriteAttributes: 66 | Type: CommaDelimitedList 67 | 68 | userpoolClientReadAttributes: 69 | Type: CommaDelimitedList 70 | 71 | userpoolClientLambdaRole: 72 | Type: String 73 | 74 | userpoolClientSetAttributes: 75 | Type: String 76 | 77 | resourceName: 78 | Type: String 79 | 80 | authSelections: 81 | Type: String 82 | 83 | useDefault: 84 | Type: String 85 | 86 | dependsOn: 87 | Type: CommaDelimitedList 88 | 89 | Conditions: 90 | ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ] 91 | 92 | Resources: 93 | 94 | 95 | # BEGIN SNS ROLE RESOURCE 96 | SNSRole: 97 | # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process 98 | Type: AWS::IAM::Role 99 | Properties: 100 | RoleName: !If [ShouldNotCreateEnvResources, 'storea039c491c_sns-role', !Join ['',['storea039c491c_sns-role', '-', !Ref env]]] 101 | AssumeRolePolicyDocument: 102 | Version: "2012-10-17" 103 | Statement: 104 | - Sid: "" 105 | Effect: "Allow" 106 | Principal: 107 | Service: "cognito-idp.amazonaws.com" 108 | Action: 109 | - "sts:AssumeRole" 110 | Condition: 111 | StringEquals: 112 | sts:ExternalId: storea039c491c_role_external_id 113 | Policies: 114 | - 115 | PolicyName: storea039c491c-sns-policy 116 | PolicyDocument: 117 | Version: "2012-10-17" 118 | Statement: 119 | - 120 | Effect: "Allow" 121 | Action: 122 | - "sns:Publish" 123 | Resource: "*" 124 | # BEGIN USER POOL RESOURCES 125 | UserPool: 126 | # Created upon user selection 127 | # Depends on SNS Role for Arn if MFA is enabled 128 | Type: AWS::Cognito::UserPool 129 | UpdateReplacePolicy: Retain 130 | Properties: 131 | UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]] 132 | 133 | Schema: 134 | 135 | - 136 | Name: email 137 | Required: true 138 | Mutable: true 139 | 140 | 141 | 142 | 143 | AutoVerifiedAttributes: !Ref autoVerifiedAttributes 144 | 145 | 146 | EmailVerificationMessage: !Ref emailVerificationMessage 147 | EmailVerificationSubject: !Ref emailVerificationSubject 148 | 149 | Policies: 150 | PasswordPolicy: 151 | MinimumLength: !Ref passwordPolicyMinLength 152 | RequireLowercase: false 153 | RequireNumbers: false 154 | RequireSymbols: false 155 | RequireUppercase: false 156 | 157 | MfaConfiguration: !Ref mfaConfiguration 158 | SmsVerificationMessage: !Ref smsVerificationMessage 159 | SmsConfiguration: 160 | SnsCallerArn: !GetAtt SNSRole.Arn 161 | ExternalId: storea039c491c_role_external_id 162 | 163 | 164 | UserPoolClientWeb: 165 | # Created provide application access to user pool 166 | # Depends on UserPool for ID reference 167 | Type: "AWS::Cognito::UserPoolClient" 168 | Properties: 169 | ClientName: storea039c491c_app_clientWeb 170 | 171 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 172 | UserPoolId: !Ref UserPool 173 | DependsOn: UserPool 174 | UserPoolClient: 175 | # Created provide application access to user pool 176 | # Depends on UserPool for ID reference 177 | Type: "AWS::Cognito::UserPoolClient" 178 | Properties: 179 | ClientName: storea039c491c_app_client 180 | 181 | GenerateSecret: !Ref userpoolClientGenerateSecret 182 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 183 | UserPoolId: !Ref UserPool 184 | DependsOn: UserPool 185 | # BEGIN USER POOL LAMBDA RESOURCES 186 | UserPoolClientRole: 187 | # Created to execute Lambda which gets userpool app client config values 188 | Type: 'AWS::IAM::Role' 189 | Properties: 190 | RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 191 | AssumeRolePolicyDocument: 192 | Version: '2012-10-17' 193 | Statement: 194 | - Effect: Allow 195 | Principal: 196 | Service: 197 | - lambda.amazonaws.com 198 | Action: 199 | - 'sts:AssumeRole' 200 | DependsOn: UserPoolClient 201 | UserPoolClientLambda: 202 | # Lambda which gets userpool app client config values 203 | # Depends on UserPool for id 204 | # Depends on UserPoolClientRole for role ARN 205 | Type: 'AWS::Lambda::Function' 206 | Properties: 207 | Code: 208 | ZipFile: !Join 209 | - |+ 210 | - - 'const response = require(''cfn-response'');' 211 | - 'const aws = require(''aws-sdk'');' 212 | - 'const identity = new aws.CognitoIdentityServiceProvider();' 213 | - 'exports.handler = (event, context, callback) => {' 214 | - ' if (event.RequestType == ''Delete'') { ' 215 | - ' response.send(event, context, response.SUCCESS, {})' 216 | - ' }' 217 | - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {' 218 | - ' const params = {' 219 | - ' ClientId: event.ResourceProperties.clientId,' 220 | - ' UserPoolId: event.ResourceProperties.userpoolId' 221 | - ' };' 222 | - ' identity.describeUserPoolClient(params).promise()' 223 | - ' .then((res) => {' 224 | - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});' 225 | - ' })' 226 | - ' .catch((err) => {' 227 | - ' response.send(event, context, response.FAILED, {err});' 228 | - ' });' 229 | - ' }' 230 | - '};' 231 | Handler: index.handler 232 | Runtime: nodejs8.10 233 | Timeout: '300' 234 | Role: !GetAtt 235 | - UserPoolClientRole 236 | - Arn 237 | DependsOn: UserPoolClientRole 238 | UserPoolClientLambdaPolicy: 239 | # Sets userpool policy for the role that executes the Userpool Client Lambda 240 | # Depends on UserPool for Arn 241 | # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing 242 | Type: 'AWS::IAM::Policy' 243 | Properties: 244 | PolicyName: storea039c491c_userpoolclient_lambda_iam_policy 245 | Roles: 246 | - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 247 | PolicyDocument: 248 | Version: '2012-10-17' 249 | Statement: 250 | - Effect: Allow 251 | Action: 252 | - 'cognito-idp:DescribeUserPoolClient' 253 | Resource: !GetAtt UserPool.Arn 254 | DependsOn: UserPoolClientLambda 255 | UserPoolClientLogPolicy: 256 | # Sets log policy for the role that executes the Userpool Client Lambda 257 | # Depends on UserPool for Arn 258 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 259 | Type: 'AWS::IAM::Policy' 260 | Properties: 261 | PolicyName: storea039c491c_userpoolclient_lambda_log_policy 262 | Roles: 263 | - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 264 | PolicyDocument: 265 | Version: 2012-10-17 266 | Statement: 267 | - Effect: Allow 268 | Action: 269 | - 'logs:CreateLogGroup' 270 | - 'logs:CreateLogStream' 271 | - 'logs:PutLogEvents' 272 | Resource: !Sub 273 | - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:* 274 | - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda} 275 | DependsOn: UserPoolClientLambdaPolicy 276 | UserPoolClientInputs: 277 | # Values passed to Userpool client Lambda 278 | # Depends on UserPool for Id 279 | # Depends on UserPoolClient for Id 280 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 281 | Type: 'Custom::LambdaCallout' 282 | Properties: 283 | ServiceToken: !GetAtt UserPoolClientLambda.Arn 284 | clientId: !Ref UserPoolClient 285 | userpoolId: !Ref UserPool 286 | DependsOn: UserPoolClientLogPolicy 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | # BEGIN IDENTITY POOL RESOURCES 295 | 296 | 297 | IdentityPool: 298 | # Always created 299 | Type: AWS::Cognito::IdentityPool 300 | Properties: 301 | IdentityPoolName: !If [ShouldNotCreateEnvResources, 'storeapp039c491c_identitypool_039c491c', !Join ['',['storeapp039c491c_identitypool_039c491c', '__', !Ref env]]] 302 | 303 | CognitoIdentityProviders: 304 | - ClientId: !Ref UserPoolClient 305 | ProviderName: !Sub 306 | - cognito-idp.${region}.amazonaws.com/${client} 307 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 308 | - ClientId: !Ref UserPoolClientWeb 309 | ProviderName: !Sub 310 | - cognito-idp.${region}.amazonaws.com/${client} 311 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 312 | 313 | AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities 314 | 315 | 316 | DependsOn: UserPoolClientInputs 317 | 318 | 319 | IdentityPoolRoleMap: 320 | # Created to map Auth and Unauth roles to the identity pool 321 | # Depends on Identity Pool for ID ref 322 | Type: AWS::Cognito::IdentityPoolRoleAttachment 323 | Properties: 324 | IdentityPoolId: !Ref IdentityPool 325 | Roles: 326 | unauthenticated: !Ref unauthRoleArn 327 | authenticated: !Ref authRoleArn 328 | DependsOn: IdentityPool 329 | 330 | 331 | Outputs : 332 | 333 | IdentityPoolId: 334 | Value: !Ref 'IdentityPool' 335 | Description: Id for the identity pool 336 | IdentityPoolName: 337 | Value: !GetAtt IdentityPool.Name 338 | 339 | 340 | 341 | 342 | UserPoolId: 343 | Value: !Ref 'UserPool' 344 | Description: Id for the user pool 345 | UserPoolName: 346 | Value: !Ref userPoolName 347 | AppClientIDWeb: 348 | Value: !Ref 'UserPoolClientWeb' 349 | Description: The user pool app client id for web 350 | AppClientID: 351 | Value: !Ref 'UserPoolClient' 352 | Description: The user pool app client id 353 | AppClientSecret: 354 | Value: !GetAtt UserPoolClientInputs.appSecret 355 | 356 | 357 | 358 | 359 | 360 | 361 | -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "storeapp039c491c": { 4 | "service": "Cognito", 5 | "providerPlugin": "awscloudformation", 6 | "dependsOn": [] 7 | } 8 | }, 9 | "api": { 10 | "StoreApi": { 11 | "service": "AppSync", 12 | "providerPlugin": "awscloudformation", 13 | "output": { 14 | "authConfig": { 15 | "additionalAuthenticationProviders": [], 16 | "defaultAuthentication": { 17 | "authenticationType": "AMAZON_COGNITO_USER_POOLS", 18 | "userPoolConfig": { 19 | "userPoolId": "authstoreapp039c491c" 20 | } 21 | } 22 | } 23 | } 24 | } 25 | }, 26 | "payment": { 27 | "service": { 28 | "providerPlugin": "awscloudformation" 29 | } 30 | }, 31 | "user" : { 32 | "service" : { 33 | "providerPlugin": "awscloudformation" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /amplify/backend/payment/service/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Root stack for the Amplify AWS CloudFormation provider", 4 | "Parameters": { 5 | "VpcCidr": { 6 | "Description": "VPC CIDR Range", 7 | "Type": "String", 8 | "Default": "10.10.0.0/16" 9 | }, 10 | "FargateTaskSubnet01Cidr": { 11 | "Description": "FargateTaskSubnet01Cidr", 12 | "Type": "String", 13 | "Default": "10.10.1.0/24" 14 | }, 15 | "FargateTaskSubnet02Cidr": { 16 | "Description": "FargateTaskSubnet01Cidr", 17 | "Type": "String", 18 | "Default": "10.10.2.0/24" 19 | }, 20 | "LoadBalancerSubnet01Cidr": { 21 | "Description": "LoadBalancerSubnet01Cidr", 22 | "Type": "String", 23 | "Default": "10.10.3.0/24" 24 | }, 25 | "LoadBalancerSubnet02Cidr": { 26 | "Description": "LoadBalancerSubnet01Cidr", 27 | "Type": "String", 28 | "Default": "10.10.4.0/24" 29 | }, 30 | "env": { 31 | "Type": "String", 32 | "Description": "The environment name. e.g. Dev, Test, or Production", 33 | "Default": "NONE" 34 | } 35 | }, 36 | "Resources": { 37 | "VPC": { 38 | "Type": "AWS::EC2::VPC", 39 | "Properties": { 40 | "CidrBlock": { 41 | "Ref": "VpcCidr" 42 | }, 43 | "EnableDnsHostnames": true, 44 | "EnableDnsSupport": true, 45 | "Tags": [ 46 | { 47 | "Key": "Name", 48 | "Value": "PaymentServiceVpc" 49 | } 50 | ] 51 | } 52 | }, 53 | "FargateTaskSubnet01": { 54 | "Type": "AWS::EC2::Subnet", 55 | "Properties": { 56 | "AvailabilityZone": { 57 | "Fn::Select": [ 58 | 0, 59 | { 60 | "Fn::GetAZs": "" 61 | } 62 | ] 63 | }, 64 | "CidrBlock": { 65 | "Ref": "FargateTaskSubnet01Cidr" 66 | }, 67 | "Tags": [ 68 | { 69 | "Key": "Name", 70 | "Value": "FargateTaskSubnet01" 71 | } 72 | ], 73 | "VpcId": { 74 | "Ref": "VPC" 75 | } 76 | } 77 | }, 78 | "FargateTaskSubnet02": { 79 | "Type": "AWS::EC2::Subnet", 80 | "Properties": { 81 | "AvailabilityZone": { 82 | "Fn::Select": [ 83 | 1, 84 | { 85 | "Fn::GetAZs": "" 86 | } 87 | ] 88 | }, 89 | "CidrBlock": { 90 | "Ref": "FargateTaskSubnet02Cidr" 91 | }, 92 | "Tags": [ 93 | { 94 | "Key": "Name", 95 | "Value": "FargateTaskSubnet02" 96 | } 97 | ], 98 | "VpcId": { 99 | "Ref": "VPC" 100 | } 101 | } 102 | }, 103 | "LoadBalancerSubnet01": { 104 | "Type": "AWS::EC2::Subnet", 105 | "Properties": { 106 | "AvailabilityZone": { 107 | "Fn::Select": [ 108 | 0, 109 | { 110 | "Fn::GetAZs": "" 111 | } 112 | ] 113 | }, 114 | "CidrBlock": { 115 | "Ref": "LoadBalancerSubnet01Cidr" 116 | }, 117 | "Tags": [ 118 | { 119 | "Key": "Name", 120 | "Value": "LoadBalancerSubnet01" 121 | } 122 | ], 123 | "VpcId": { 124 | "Ref": "VPC" 125 | } 126 | } 127 | }, 128 | "LoadBalancerSubnet02": { 129 | "Type": "AWS::EC2::Subnet", 130 | "Properties": { 131 | "AvailabilityZone": { 132 | "Fn::Select": [ 133 | 1, 134 | { 135 | "Fn::GetAZs": "" 136 | } 137 | ] 138 | }, 139 | "CidrBlock": { 140 | "Ref": "LoadBalancerSubnet02Cidr" 141 | }, 142 | "Tags": [ 143 | { 144 | "Key": "Name", 145 | "Value": "LoadBalancerSubnet02" 146 | } 147 | ], 148 | "VpcId": { 149 | "Ref": "VPC" 150 | } 151 | } 152 | }, 153 | "FargateTaskSubnetRouteTable": { 154 | "Type": "AWS::EC2::RouteTable", 155 | "Properties": { 156 | "Tags": [ 157 | { 158 | "Key": "Name", 159 | "Value": "FargateTaskSubnetRouteTable" 160 | } 161 | ], 162 | "VpcId": { 163 | "Ref": "VPC" 164 | } 165 | } 166 | }, 167 | "FargateTaskSubnetRouteTableAssociation01": { 168 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 169 | "Properties": { 170 | "RouteTableId": { 171 | "Ref": "FargateTaskSubnetRouteTable" 172 | }, 173 | "SubnetId": { 174 | "Ref": "FargateTaskSubnet01" 175 | } 176 | } 177 | }, 178 | "FargateTaskSubnetRouteTableAssociation02": { 179 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 180 | "Properties": { 181 | "RouteTableId": { 182 | "Ref": "FargateTaskSubnetRouteTable" 183 | }, 184 | "SubnetId": { 185 | "Ref": "FargateTaskSubnet02" 186 | } 187 | } 188 | }, 189 | "FargateTaskSecurityGroup": { 190 | "Type": "AWS::EC2::SecurityGroup", 191 | "Properties": { 192 | "VpcId": { 193 | "Ref": "VPC" 194 | }, 195 | "GroupDescription": "security group associate with fargate task", 196 | "SecurityGroupIngress": [ 197 | { 198 | "IpProtocol": "tcp", 199 | "FromPort": "7000", 200 | "ToPort": "7000", 201 | "SourceSecurityGroupId": { 202 | "Ref": "LoadBalancerSecurityGroup" 203 | } 204 | } 205 | ], 206 | "Tags": [ 207 | { 208 | "Key": "Name", 209 | "Value": "FargateTaskSecurityGroup" 210 | } 211 | ] 212 | } 213 | }, 214 | "LoadBalancerSecurityGroup": { 215 | "Type": "AWS::EC2::SecurityGroup", 216 | "Properties": { 217 | "VpcId": { 218 | "Ref": "VPC" 219 | }, 220 | "GroupDescription": "security group associate with load balancer", 221 | "SecurityGroupIngress": [ 222 | { 223 | "IpProtocol": "tcp", 224 | "FromPort": "80", 225 | "ToPort": "80", 226 | "SourceSecurityGroupId": { 227 | "Ref": "ConsumerSecurityGroup" 228 | } 229 | } 230 | ], 231 | "Tags": [ 232 | { 233 | "Key": "Name", 234 | "Value": "LoadBalancerSecurityGroup" 235 | } 236 | ] 237 | } 238 | }, 239 | "ConsumerSecurityGroup": { 240 | "Type": "AWS::EC2::SecurityGroup", 241 | "Properties": { 242 | "VpcId": { 243 | "Ref": "VPC" 244 | }, 245 | "GroupDescription": "security group associated with LoadBalancer client", 246 | "Tags": [ 247 | { 248 | "Key": "Name", 249 | "Value": "ConsumerSecurityGroup" 250 | } 251 | ] 252 | } 253 | }, 254 | "EndpointSecurityGroup": { 255 | "Type": "AWS::EC2::SecurityGroup", 256 | "Properties": { 257 | "VpcId": { 258 | "Ref": "VPC" 259 | }, 260 | "GroupDescription": "EndpointSecurityGroup", 261 | "SecurityGroupIngress": [ 262 | { 263 | "IpProtocol": "tcp", 264 | "FromPort": "443", 265 | "ToPort": "443", 266 | "CidrIp": { 267 | "Ref": "VpcCidr" 268 | } 269 | } 270 | ], 271 | "Tags": [ 272 | { 273 | "Key": "Name", 274 | "Value": "EndpointSecurityGroup" 275 | } 276 | ] 277 | } 278 | }, 279 | "ECRPrivateEndpoint": { 280 | "Type": "AWS::EC2::VPCEndpoint", 281 | "Properties": { 282 | "PrivateDnsEnabled": true, 283 | "SecurityGroupIds": [ 284 | { 285 | "Ref": "EndpointSecurityGroup" 286 | } 287 | ], 288 | "ServiceName": { 289 | "Fn::Sub": "com.amazonaws.${AWS::Region}.ecr.dkr" 290 | }, 291 | "SubnetIds": [ 292 | { 293 | "Ref": "FargateTaskSubnet01" 294 | }, 295 | { 296 | "Ref": "FargateTaskSubnet02" 297 | } 298 | ], 299 | "VpcEndpointType": "Interface", 300 | "VpcId": { 301 | "Ref": "VPC" 302 | } 303 | } 304 | }, 305 | "S3GatewayEndpoint": { 306 | "Type": "AWS::EC2::VPCEndpoint", 307 | "Properties": { 308 | "RouteTableIds": [ 309 | { 310 | "Ref": "FargateTaskSubnetRouteTable" 311 | } 312 | ], 313 | "ServiceName": { 314 | "Fn::Sub": "com.amazonaws.${AWS::Region}.s3" 315 | }, 316 | "VpcEndpointType": "Gateway", 317 | "VpcId": { 318 | "Ref": "VPC" 319 | } 320 | } 321 | }, 322 | "DDBGatewayEndpoint": { 323 | "Type": "AWS::EC2::VPCEndpoint", 324 | "Properties": { 325 | "RouteTableIds": [ 326 | { 327 | "Ref": "FargateTaskSubnetRouteTable" 328 | } 329 | ], 330 | "ServiceName": { 331 | "Fn::Sub": "com.amazonaws.${AWS::Region}.dynamodb" 332 | }, 333 | "VpcEndpointType": "Gateway", 334 | "VpcId": { 335 | "Ref": "VPC" 336 | } 337 | } 338 | }, 339 | "LogsPrivateEndpoint": { 340 | "Type": "AWS::EC2::VPCEndpoint", 341 | "Properties": { 342 | "PrivateDnsEnabled": true, 343 | "SecurityGroupIds": [ 344 | { 345 | "Ref": "EndpointSecurityGroup" 346 | } 347 | ], 348 | "ServiceName": { 349 | "Fn::Sub": "com.amazonaws.${AWS::Region}.logs" 350 | }, 351 | "SubnetIds": [ 352 | { 353 | "Ref": "LoadBalancerSubnet01" 354 | }, 355 | { 356 | "Ref": "LoadBalancerSubnet02" 357 | } 358 | ], 359 | "VpcEndpointType": "Interface", 360 | "VpcId": { 361 | "Ref": "VPC" 362 | } 363 | } 364 | }, 365 | "LoadBalancer": { 366 | "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", 367 | "Properties": { 368 | "SecurityGroups": [ 369 | { 370 | "Ref": "LoadBalancerSecurityGroup" 371 | } 372 | ], 373 | "Subnets": [ 374 | { 375 | "Ref": "LoadBalancerSubnet01" 376 | }, 377 | { 378 | "Ref": "LoadBalancerSubnet02" 379 | } 380 | ], 381 | "Scheme": "internal" 382 | } 383 | }, 384 | "HTTPlistener": { 385 | "Type": "AWS::ElasticLoadBalancingV2::Listener", 386 | "Properties": { 387 | "DefaultActions": [ 388 | { 389 | "Type": "fixed-response", 390 | "FixedResponseConfig": { 391 | "ContentType": "application/json", 392 | "StatusCode": "404" 393 | } 394 | } 395 | ], 396 | "LoadBalancerArn": { 397 | "Ref": "LoadBalancer" 398 | }, 399 | "Port": 80, 400 | "Protocol": "HTTP" 401 | } 402 | }, 403 | "PaymentTaskTargetGroup": { 404 | "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", 405 | "DependsOn": [ 406 | "LoadBalancer" 407 | ], 408 | "Properties": { 409 | "Name": { 410 | "Fn::Sub": "${env}-PaymentTaskTargetGroup" 411 | }, 412 | "HealthCheckIntervalSeconds": 5, 413 | "HealthCheckTimeoutSeconds": 2, 414 | "HealthyThresholdCount": 2, 415 | "HealthCheckPath": "/payments/health", 416 | "HealthCheckPort": 7000, 417 | "HealthCheckProtocol": "HTTP", 418 | "TargetType": "ip", 419 | "Port": 7000, 420 | "Protocol": "HTTP", 421 | "UnhealthyThresholdCount": 2, 422 | "VpcId": { 423 | "Ref": "VPC" 424 | }, 425 | "TargetGroupAttributes": [ 426 | { 427 | "Key": "deregistration_delay.timeout_seconds", 428 | "Value": "10" 429 | } 430 | ] 431 | } 432 | }, 433 | "LoadBalancerListenerRuleGET": { 434 | "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", 435 | "Properties": { 436 | "Actions": [ 437 | { 438 | "TargetGroupArn": { 439 | "Ref": "PaymentTaskTargetGroup" 440 | }, 441 | "Type": "forward" 442 | } 443 | ], 444 | "Conditions": [ 445 | { 446 | "Field": "path-pattern", 447 | "PathPatternConfig": { 448 | "Values": [ 449 | "/payments/accounts/*" 450 | ] 451 | } 452 | }, 453 | { 454 | "Field": "http-request-method", 455 | "HttpRequestMethodConfig": { 456 | "Values": [ 457 | "GET" 458 | ] 459 | } 460 | } 461 | ], 462 | "ListenerArn": { 463 | "Ref": "HTTPlistener" 464 | }, 465 | "Priority": 1 466 | } 467 | }, 468 | "LoadBalancerListenerRulePOST": { 469 | "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", 470 | "Properties": { 471 | "Actions": [ 472 | { 473 | "TargetGroupArn": { 474 | "Ref": "PaymentTaskTargetGroup" 475 | }, 476 | "Type": "forward" 477 | } 478 | ], 479 | "Conditions": [ 480 | { 481 | "Field": "path-pattern", 482 | "PathPatternConfig": { 483 | "Values": [ 484 | "/payments/account" 485 | ] 486 | } 487 | }, 488 | { 489 | "Field": "http-request-method", 490 | "HttpRequestMethodConfig": { 491 | "Values": [ 492 | "POST" 493 | ] 494 | } 495 | } 496 | ], 497 | "ListenerArn": { 498 | "Ref": "HTTPlistener" 499 | }, 500 | "Priority": 2 501 | } 502 | }, 503 | "PaymentTaskLogGroup": { 504 | "Type": "AWS::Logs::LogGroup", 505 | "Properties": { 506 | "RetentionInDays": 7 507 | } 508 | }, 509 | "PaymentServiceDefinition": { 510 | "Type": "AWS::ECS::TaskDefinition", 511 | "DependsOn": [ 512 | "PaymentTaskLogGroup" 513 | ], 514 | "Properties": { 515 | "ContainerDefinitions": [ 516 | { 517 | "Name": "PaymentApp", 518 | "Image": { 519 | "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/paymentapp:v1" 520 | }, 521 | "Cpu": "512", 522 | "Memory": "1024", 523 | "Environment": [ 524 | { 525 | "Name": "PaymentAccountTableName", 526 | "Value": { 527 | "Ref": "PaymentAccountTable" 528 | } 529 | }, 530 | { 531 | "Name": "AWS_REGION", 532 | "Value": { 533 | "Fn::Sub": "${AWS::Region}" 534 | } 535 | } 536 | ], 537 | "PortMappings": [ 538 | { 539 | "ContainerPort": 7000 540 | } 541 | ], 542 | "LogConfiguration": { 543 | "LogDriver": "awslogs", 544 | "Options": { 545 | "awslogs-group": { 546 | "Ref": "PaymentTaskLogGroup" 547 | }, 548 | "awslogs-region": { 549 | "Fn::Sub": "${AWS::Region}" 550 | }, 551 | "awslogs-stream-prefix": "fargate" 552 | } 553 | } 554 | } 555 | ], 556 | "Cpu": "512", 557 | "Memory": "1024", 558 | "Family": { 559 | "Fn::Sub": "${env}-PaymentService" 560 | }, 561 | "NetworkMode": "awsvpc", 562 | "RequiresCompatibilities": [ 563 | "FARGATE" 564 | ], 565 | "Tags": [ 566 | { 567 | "Key": "Name", 568 | "Value": "PaymentServiceTaskDefinition" 569 | } 570 | ], 571 | "ExecutionRoleArn": { 572 | "Fn::GetAtt": [ 573 | "TaskExecutionRole", 574 | "Arn" 575 | ] 576 | }, 577 | "TaskRoleArn": { 578 | "Fn::GetAtt": [ 579 | "TaskIAMRole", 580 | "Arn" 581 | ] 582 | } 583 | } 584 | }, 585 | "ECSCluster": { 586 | "Type": "AWS::ECS::Cluster", 587 | "Properties": { 588 | "ClusterName": { 589 | "Fn::Sub": "${env}-PaymentServiceCluster" 590 | }, 591 | "Tags": [ 592 | { 593 | "Key": "Name", 594 | "Value": { 595 | "Fn::Sub": "${env}-PaymentServiceCluster" 596 | } 597 | } 598 | ] 599 | } 600 | }, 601 | "Service": { 602 | "Type": "AWS::ECS::Service", 603 | "DependsOn": [ 604 | "LoadBalancerListenerRuleGET", 605 | "LoadBalancerListenerRulePOST" 606 | ], 607 | "Properties": { 608 | "ServiceName": { 609 | "Fn::Sub": "${env}-PaymentService" 610 | }, 611 | "Cluster": { 612 | "Ref": "ECSCluster" 613 | }, 614 | "DeploymentConfiguration": { 615 | "MaximumPercent": 200, 616 | "MinimumHealthyPercent": 75 617 | }, 618 | "DesiredCount": 2, 619 | "HealthCheckGracePeriodSeconds": 10, 620 | "LaunchType": "FARGATE", 621 | "LoadBalancers": [ 622 | { 623 | "ContainerName": "PaymentApp", 624 | "ContainerPort": 7000, 625 | "TargetGroupArn": { 626 | "Ref": "PaymentTaskTargetGroup" 627 | } 628 | } 629 | ], 630 | "NetworkConfiguration": { 631 | "AwsvpcConfiguration": { 632 | "SecurityGroups": [ 633 | { 634 | "Ref": "FargateTaskSecurityGroup" 635 | } 636 | ], 637 | "Subnets": [ 638 | { 639 | "Ref": "FargateTaskSubnet01" 640 | }, 641 | { 642 | "Ref": "FargateTaskSubnet02" 643 | } 644 | ] 645 | } 646 | }, 647 | "TaskDefinition": { 648 | "Ref": "PaymentServiceDefinition" 649 | } 650 | } 651 | }, 652 | "TaskIAMRole": { 653 | "Type": "AWS::IAM::Role", 654 | "Properties": { 655 | "AssumeRolePolicyDocument": { 656 | "Version": "2012-10-17", 657 | "Statement": [ 658 | { 659 | "Effect": "Allow", 660 | "Principal": { 661 | "Service": [ 662 | "ecs-tasks.amazonaws.com" 663 | ] 664 | }, 665 | "Action": [ 666 | "sts:AssumeRole" 667 | ] 668 | } 669 | ] 670 | }, 671 | "Policies": [ 672 | { 673 | "PolicyName": "PaymentTaskDDBPolicy", 674 | "PolicyDocument": { 675 | "Version": "2012-10-17", 676 | "Statement": [ 677 | { 678 | "Effect": "Allow", 679 | "Action": [ 680 | "dynamodb:Query", 681 | "dynamodb:PutItem" 682 | ], 683 | "Resource": { 684 | "Fn::GetAtt": [ 685 | "PaymentAccountTable", 686 | "Arn" 687 | ] 688 | } 689 | } 690 | ] 691 | } 692 | } 693 | ] 694 | } 695 | }, 696 | "TaskExecutionRole": { 697 | "Type": "AWS::IAM::Role", 698 | "Properties": { 699 | "AssumeRolePolicyDocument": { 700 | "Version": "2012-10-17", 701 | "Statement": [ 702 | { 703 | "Effect": "Allow", 704 | "Principal": { 705 | "Service": [ 706 | "ecs-tasks.amazonaws.com" 707 | ] 708 | }, 709 | "Action": [ 710 | "sts:AssumeRole" 711 | ] 712 | } 713 | ] 714 | }, 715 | "Path": "/", 716 | "ManagedPolicyArns": [ 717 | "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 718 | ], 719 | "RoleName": { 720 | "Fn::Sub": "${env}-PaymentTaskExecutionRole" 721 | } 722 | } 723 | }, 724 | "PaymentAccountTable": { 725 | "Type": "AWS::DynamoDB::Table", 726 | "Properties": { 727 | "AttributeDefinitions": [ 728 | { 729 | "AttributeName": "userId", 730 | "AttributeType": "S" 731 | }, 732 | { 733 | "AttributeName": "type", 734 | "AttributeType": "S" 735 | } 736 | ], 737 | "KeySchema": [ 738 | { 739 | "AttributeName": "userId", 740 | "KeyType": "HASH" 741 | }, 742 | { 743 | "AttributeName": "type", 744 | "KeyType": "RANGE" 745 | } 746 | ], 747 | "BillingMode": "PAY_PER_REQUEST", 748 | "Tags": [ 749 | { 750 | "Key": "Env", 751 | "Value": { 752 | "Ref": "env" 753 | } 754 | } 755 | ] 756 | } 757 | } 758 | }, 759 | "Outputs": { 760 | "PaymentServiceEndpoint": { 761 | "Value": { 762 | "Fn::Sub": "http://${LoadBalancer.DNSName}" 763 | }, 764 | "Description": "Payment Service Endpoint" 765 | }, 766 | "ConsumerSubnet01": { 767 | "Value": { 768 | "Ref": "LoadBalancerSubnet01" 769 | }, 770 | "Description": "PrivateSubnet01 for consumers" 771 | }, 772 | "ConsumerSubnet02": { 773 | "Value": { 774 | "Ref": "LoadBalancerSubnet02" 775 | }, 776 | "Description": "PrivateSubnet02 for consumers" 777 | }, 778 | "ConsumerSecurityGroup": { 779 | "Value": { 780 | "Ref": "ConsumerSecurityGroup" 781 | }, 782 | "Description": "security group for consumers" 783 | }, 784 | "PaymenyMethodTableName": { 785 | "Value": { 786 | "Ref": "PaymentAccountTable" 787 | }, 788 | "Description": "Payment method Table name" 789 | } 790 | } 791 | } -------------------------------------------------------------------------------- /amplify/backend/user/service/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "CognitoUserPoolId": { 3 | "Fn::GetAtt": [ 4 | "authstoreapp039c491c", 5 | "Outputs.UserPoolId" 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /amplify/backend/user/service/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Root stack for the Amplify AWS CloudFormation provider", 4 | "Parameters": { 5 | "env": { 6 | "Type": "String", 7 | "Description": "The environment name. e.g. Dev, Test, or Production", 8 | "Default": "NONE" 9 | }, 10 | "CognitoUserPoolId": { 11 | "Type": "String", 12 | "Description": "Cognito User pool Id. User service will fetch details from this pool", 13 | "Default": "" 14 | } 15 | }, 16 | "Resources": { 17 | "UserApi": { 18 | "Type": "AWS::ApiGateway::RestApi", 19 | "Properties": { 20 | "Name": "Users API", 21 | "Description": "API used for retrieving User details", 22 | "EndpointConfiguration": { 23 | "Types": [ 24 | "REGIONAL" 25 | ] 26 | } 27 | } 28 | }, 29 | "UserInfoLambda": { 30 | "Type": "AWS::Lambda::Function", 31 | "Properties": { 32 | "Code": { 33 | "ZipFile": "\nimport json\nimport xml.etree.ElementTree as ET\nimport os\nimport logging \nimport boto3\nimport sys\n\ncognito_client = boto3.client('cognito-idp')\n\nLOG_LEVEL = os.environ.get('LOG_LEVEL', '').upper()\nif LOG_LEVEL == '':\n LOG_LEVEL = 'INFO'\n\nlogger = logging.getLogger()\nlogger.setLevel(LOG_LEVEL)\n\ndef lambda_handler(event,context):\n logging.info('Incoming Event: {0}'.format(event))\n body = ET.Element('body')\n userName = event['params']['path']['userName'] \n \n user_attributes = {}\n\n try:\n get_user_response = cognito_client.get_user(AccessToken=event['params']['header']['x-access-token'])\n logging.info('get_user_response: {0}'.format(get_user_response)) \n \n if get_user_response['ResponseMetadata']['HTTPStatusCode'] == 200:\n\n for attr in get_user_response['UserAttributes']:\n user_attributes[attr['Name']] = attr['Value']\n\n details = ET.SubElement(body, 'userDetails') \n \n ET.SubElement(details, 'userName').text = userName\n \n ET.SubElement(details, 'email').text = user_attributes['email']\n ET.SubElement(details, 'phoneNumber').text = user_attributes['phone_number'] \n\n except:\n error = ET.SubElement(body, 'error') \n ET.SubElement(error, 'message').text = str(sys.exc_info()[1])\n\n return ET.tostring(body)\n" 34 | }, 35 | "Description": "function to retrive User info", 36 | "FunctionName": { 37 | "Fn::Sub": "${env}-UserInfoLambda" 38 | }, 39 | "Handler": "index.lambda_handler", 40 | "MemorySize": 512, 41 | "Timeout": 5, 42 | "Role": { 43 | "Fn::GetAtt": [ 44 | "UserInfoLambdaExecutionRole", 45 | "Arn" 46 | ] 47 | }, 48 | "Runtime": "python3.7", 49 | "Environment": { 50 | "Variables": { 51 | "LOG_LEVEL": "INFO", 52 | "COGNITO_USER_POOL_ID": { 53 | "Ref": "CognitoUserPoolId" 54 | } 55 | } 56 | } 57 | } 58 | }, 59 | "UserInfoLambdaExecutionRole": { 60 | "Type": "AWS::IAM::Role", 61 | "Properties": { 62 | "AssumeRolePolicyDocument": { 63 | "Version": "2012-10-17", 64 | "Statement": [ 65 | { 66 | "Effect": "Allow", 67 | "Principal": { 68 | "Service": [ 69 | "lambda.amazonaws.com" 70 | ] 71 | }, 72 | "Action": [ 73 | "sts:AssumeRole" 74 | ] 75 | } 76 | ] 77 | }, 78 | "ManagedPolicyArns": [ 79 | "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 80 | ], 81 | "Policies": [ 82 | { 83 | "PolicyName": { 84 | "Fn::Sub": "${env}UserInfoLambdaPolicy" 85 | }, 86 | "PolicyDocument": { 87 | "Version": "2012-10-17", 88 | "Statement": [ 89 | { 90 | "Effect": "Allow", 91 | "Action": [ 92 | "cognito-idp:GetUser" 93 | ], 94 | "Resource": { 95 | "Fn::Sub": "arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${CognitoUserPoolId}" 96 | } 97 | } 98 | ] 99 | } 100 | } 101 | ] 102 | } 103 | }, 104 | "UserInfoLambdaPermission": { 105 | "Type": "AWS::Lambda::Permission", 106 | "Properties": { 107 | "Action": "lambda:invokeFunction", 108 | "FunctionName": { 109 | "Fn::GetAtt": [ 110 | "UserInfoLambda", 111 | "Arn" 112 | ] 113 | }, 114 | "Principal": "apigateway.amazonaws.com", 115 | "SourceArn": { 116 | "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${UserApi}/*" 117 | } 118 | } 119 | }, 120 | "UserApiStage": { 121 | "Type": "AWS::ApiGateway::Stage", 122 | "Properties": { 123 | "DeploymentId": { 124 | "Ref": "UserApiDeployment" 125 | }, 126 | "RestApiId": { 127 | "Ref": "UserApi" 128 | }, 129 | "StageName": { 130 | "Ref": "env" 131 | } 132 | } 133 | }, 134 | "UserApiDeployment": { 135 | "Type": "AWS::ApiGateway::Deployment", 136 | "DependsOn": [ 137 | "UserApiHistoryResourceGET" 138 | ], 139 | "Properties": { 140 | "RestApiId": { 141 | "Ref": "UserApi" 142 | } 143 | } 144 | }, 145 | "UserApiHistoryResource": { 146 | "Type": "AWS::ApiGateway::Resource", 147 | "Properties": { 148 | "RestApiId": { 149 | "Ref": "UserApi" 150 | }, 151 | "ParentId": { 152 | "Fn::GetAtt": [ 153 | "UserApi", 154 | "RootResourceId" 155 | ] 156 | }, 157 | "PathPart": "user" 158 | } 159 | }, 160 | "UserApiHistoryResourceParameter": { 161 | "Type": "AWS::ApiGateway::Resource", 162 | "Properties": { 163 | "RestApiId": { 164 | "Ref": "UserApi" 165 | }, 166 | "ParentId": { 167 | "Ref": "UserApiHistoryResource" 168 | }, 169 | "PathPart": "{userName}" 170 | } 171 | }, 172 | "UserApiHistoryResourceGET": { 173 | "Type": "AWS::ApiGateway::Method", 174 | "Properties": { 175 | "AuthorizationType": "AWS_IAM", 176 | "HttpMethod": "GET", 177 | "MethodResponses": [ 178 | { 179 | "StatusCode": 200, 180 | "ResponseParameters": { 181 | "method.response.header.Content-Type": true 182 | } 183 | } 184 | ], 185 | "Integration": { 186 | "Type": "AWS", 187 | "PassthroughBehavior": "WHEN_NO_TEMPLATES", 188 | "IntegrationHttpMethod": "POST", 189 | "Uri": { 190 | "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UserInfoLambda.Arn}/invocations" 191 | }, 192 | "RequestTemplates": { 193 | "application/xml": "#set($allParams = $input.params())\n{\n\"params\" : {\n#foreach($type in $allParams.keySet())\n #set($params = $allParams.get($type))\n\"$type\" : {\n #foreach($paramName in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\n #if($foreach.hasNext),#end\n #end\n}\n #if($foreach.hasNext),#end\n#end\n}\n}\n" 194 | }, 195 | "IntegrationResponses": [ 196 | { 197 | "StatusCode": 200, 198 | "ResponseTemplates": { 199 | "application/xml": "#set($inputRoot = $input.path('$')) \n$inputRoot" 200 | }, 201 | "ResponseParameters": { 202 | "method.response.header.Content-Type": "'application/xml'" 203 | } 204 | } 205 | ] 206 | }, 207 | "RequestParameters": { 208 | "method.request.path.userName": true 209 | }, 210 | "ResourceId": { 211 | "Ref": "UserApiHistoryResourceParameter" 212 | }, 213 | "RestApiId": { 214 | "Ref": "UserApi" 215 | } 216 | } 217 | } 218 | }, 219 | "Outputs": { 220 | "UserServiceEndpoint": { 221 | "Value": { 222 | "Fn::Sub": "https://${UserApi}.execute-api.${AWS::Region}.amazonaws.com" 223 | }, 224 | "Description": "User Service Endpoint" 225 | }, 226 | "UserServiceApiId": { 227 | "Value": { 228 | "Ref": "UserApi" 229 | }, 230 | "Description": "User API Id" 231 | } 232 | } 233 | } -------------------------------------------------------------------------------- /images/add_repository.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/e8aaa166e4b6fcfebf30293b061e10c4c1094a81/images/add_repository.png -------------------------------------------------------------------------------- /images/build_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/e8aaa166e4b6fcfebf30293b061e10c4c1094a81/images/build_settings.png -------------------------------------------------------------------------------- /images/connect_local_to_cloud_backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/e8aaa166e4b6fcfebf30293b061e10c4c1094a81/images/connect_local_to_cloud_backend.png -------------------------------------------------------------------------------- /images/solution_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/e8aaa166e4b6fcfebf30293b061e10c4c1094a81/images/solution_overview.png -------------------------------------------------------------------------------- /orderapi/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | #amplify 4 | amplify/\#current-cloud-backend 5 | amplify/.config/local-* 6 | amplify/mock-data 7 | amplify/backend/amplify-meta.json 8 | amplify/backend/awscloudformation 9 | build/ 10 | dist/ 11 | node_modules/ 12 | aws-exports.js 13 | awsconfiguration.json -------------------------------------------------------------------------------- /orderapi/.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | OrderApi: 3 | schemaPath: src/graphql/schema.json 4 | includes: 5 | - src/graphql/**/*.js 6 | excludes: 7 | - ./amplify/** 8 | extensions: 9 | amplify: 10 | codeGenTarget: javascript 11 | generatedFileName: '' 12 | docsFilePath: src/graphql 13 | extensions: 14 | amplify: 15 | version: 3 16 | -------------------------------------------------------------------------------- /orderapi/amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "orderapi", 3 | "version": "2.0", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "none", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "dist", 10 | "BuildCommand": "npm run-script build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /orderapi/amplify/backend/api/OrderApi/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSyncApiName": "OrderApi", 3 | "DynamoDBBillingMode": "PAY_PER_REQUEST", 4 | "DynamoDBEnableServerSideEncryption": "false" 5 | } -------------------------------------------------------------------------------- /orderapi/amplify/backend/api/OrderApi/schema.graphql: -------------------------------------------------------------------------------- 1 | type Order 2 | @model(mutations: { create : "addOrder", delete: "cancelOrder"}, subscriptions: {onCreate: ["addedOrder"], onDelete: ["cancelledOrder"]}) 3 | @auth(rules: [{allow: private, provider: iam}]) 4 | @key(fields: ["userId", "orderDateTime", "status"] ) { 5 | userId: ID! 6 | status: Status! 7 | orderDateTime: String! 8 | details: String! 9 | orderId: String! 10 | } 11 | 12 | 13 | enum Status { 14 | DELIVERED 15 | IN_TRANSIT 16 | PENDING 17 | PROCESSING 18 | CANCELLED 19 | } 20 | 21 | -------------------------------------------------------------------------------- /orderapi/amplify/backend/api/OrderApi/stacks/CustomResources.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiId": { 7 | "Type": "String", 8 | "Description": "The id of the AppSync API associated with this project." 9 | }, 10 | "AppSyncApiName": { 11 | "Type": "String", 12 | "Description": "The name of the AppSync API", 13 | "Default": "AppSyncSimpleTransform" 14 | }, 15 | "env": { 16 | "Type": "String", 17 | "Description": "The environment name. e.g. Dev, Test, or Production", 18 | "Default": "NONE" 19 | }, 20 | "S3DeploymentBucket": { 21 | "Type": "String", 22 | "Description": "The S3 bucket containing all deployment assets for the project." 23 | }, 24 | "S3DeploymentRootKey": { 25 | "Type": "String", 26 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory." 27 | } 28 | }, 29 | "Resources": { 30 | "EmptyResource": { 31 | "Type": "Custom::EmptyResource", 32 | "Condition": "AlwaysFalse" 33 | } 34 | }, 35 | "Conditions": { 36 | "HasEnvironmentParameter": { 37 | "Fn::Not": [ 38 | { 39 | "Fn::Equals": [ 40 | { 41 | "Ref": "env" 42 | }, 43 | "NONE" 44 | ] 45 | } 46 | ] 47 | }, 48 | "AlwaysFalse": { 49 | "Fn::Equals": [ 50 | "true", 51 | "false" 52 | ] 53 | } 54 | }, 55 | "Outputs": { 56 | "EmptyOutput": { 57 | "Description": "An empty output. You may delete this if you have at least one resource above.", 58 | "Value": "" 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /orderapi/amplify/backend/api/OrderApi/transform.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 4 3 | } -------------------------------------------------------------------------------- /orderapi/amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": { 3 | "OrderApi": { 4 | "service": "AppSync", 5 | "providerPlugin": "awscloudformation", 6 | "output": { 7 | "authConfig": { 8 | "additionalAuthenticationProviders": [], 9 | "defaultAuthentication": { 10 | "authenticationType": "AWS_IAM" 11 | } 12 | } 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /orderapi/deploy_OrderApi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | IFS='|' 4 | 5 | # if no provided environment name, use default env variable, then user override 6 | if [[ ${ENV} = "" ]]; 7 | then 8 | ENV=${AWS_BRANCH} 9 | fi 10 | if [[ ${USER_BRANCH} != "" ]]; 11 | then 12 | ENV=${USER_BRANCH} 13 | fi 14 | 15 | AWSCONFIG="{\ 16 | \"configLevel\":\"project\",\ 17 | \"useProfile\":true,\ 18 | \"profileName\":\"default\"\ 19 | }" 20 | 21 | AMPLIFY="{\ 22 | \"envName\":\"${ENV}\"\ 23 | }" 24 | 25 | PROVIDERS="{\ 26 | \"awscloudformation\":${AWSCONFIG}\ 27 | }" 28 | 29 | CODEGEN="{\ 30 | \"generateCode\":false,\ 31 | \"generateDocs\":false\ 32 | }" 33 | 34 | echo "# Getting Amplify CLI Cloud-Formation stack info from environment cache" 35 | export ORDERAPIINFO="$(envCache --get ORDERAPIINFO)" 36 | 37 | echo "# Start initializing Amplify environment: ${ENV}" 38 | if [[ -z ${ORDERAPIINFO} ]]; 39 | then 40 | echo "# Initializing new Amplify environment: ${ENV} (amplify init)" 41 | amplify init --amplify ${AMPLIFY} --providers ${PROVIDERS} --codegen ${CODEGEN} --yes; 42 | echo "# Environment ${ENV} details:" 43 | amplify env get --name ${ENV} 44 | else 45 | echo "ORDERAPIINFO="${ORDERAPIINFO} 46 | echo "# Importing Amplify environment: ${ENV} (amplify env import)" 47 | amplify env import --name ${ENV} --config "${ORDERAPIINFO}" --awsInfo ${AWSCONFIG} --yes; 48 | echo "# Initializing existing Amplify environment: ${ENV} (amplify init)" 49 | amplify init --amplify ${AMPLIFY} --providers ${PROVIDERS} --codegen ${CODEGEN} --yes; 50 | echo "# Environment ${ENV} details:" 51 | amplify env get --name ${ENV} 52 | fi 53 | echo "# Done initializing Amplify environment: ${ENV}" 54 | 55 | 56 | echo "# Store Amplify CLI Cloud-Formation stack info in environment cache" 57 | ORDERAPIINFO="$(amplify env get --json --name ${ENV})" 58 | envCache --set ORDERAPIINFO ${ORDERAPIINFO} 59 | echo "ORDERAPIINFO="${ORDERAPIINFO} 60 | 61 | echo "# Export API Endpoint" 62 | export GRAPHQL_ENDPOINT=$(jq -r '.api[(.api | keys)[0]].output.GraphQLAPIEndpointOutput' ./amplify/#current-cloud-backend/amplify-meta.json) 63 | 64 | 65 | echo "# GRAPHQL Endpoint="${GRAPHQL_ENDPOINT} 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /orderapi/src/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const addOrder = `mutation AddOrder($input: CreateOrderInput!) { 5 | addOrder(input: $input) { 6 | userId 7 | status 8 | orderDateTime 9 | details 10 | orderId 11 | } 12 | } 13 | `; 14 | export const cancelOrder = `mutation CancelOrder($input: DeleteOrderInput!) { 15 | cancelOrder(input: $input) { 16 | userId 17 | status 18 | orderDateTime 19 | details 20 | orderId 21 | } 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /orderapi/src/graphql/queries.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const getOrder = `query GetOrder($userId: ID!, $orderDateTime: String!, $status: Status!) { 5 | getOrder(userId: $userId, orderDateTime: $orderDateTime, status: $status) { 6 | userId 7 | status 8 | orderDateTime 9 | details 10 | orderId 11 | } 12 | } 13 | `; 14 | export const listOrders = `query ListOrders( 15 | $userId: ID 16 | $orderDateTimeStatus: ModelOrderPrimaryCompositeKeyConditionInput 17 | $filter: ModelOrderFilterInput 18 | $limit: Int 19 | $nextToken: String 20 | $sortDirection: ModelSortDirection 21 | ) { 22 | listOrders( 23 | userId: $userId 24 | orderDateTimeStatus: $orderDateTimeStatus 25 | filter: $filter 26 | limit: $limit 27 | nextToken: $nextToken 28 | sortDirection: $sortDirection 29 | ) { 30 | items { 31 | userId 32 | status 33 | orderDateTime 34 | details 35 | orderId 36 | } 37 | nextToken 38 | } 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /orderapi/src/graphql/subscriptions.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const addedOrder = `subscription AddedOrder { 5 | addedOrder { 6 | userId 7 | status 8 | orderDateTime 9 | details 10 | orderId 11 | } 12 | } 13 | `; 14 | export const cancelledOrder = `subscription CancelledOrder { 15 | cancelledOrder { 16 | userId 17 | status 18 | orderDateTime 19 | details 20 | orderId 21 | } 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storeapp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "apollo-link": "^1.2.13", 7 | "apollo-link-http": "^1.5.16", 8 | "aws-amplify": "^1.2.2", 9 | "aws-amplify-react": "^2.5.2", 10 | "aws-appsync": "^2.0.1", 11 | "glamor": "^2.20.40", 12 | "graphql-tag": "^2.10.1", 13 | "react": "^16.10.2", 14 | "react-dom": "^16.10.2", 15 | "react-router-dom": "^5.1.2", 16 | "react-scripts": "3.2.0" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /paymentapp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-alpine 2 | 3 | COPY /src/* /src/ 4 | COPY requirements.txt /tmp/ 5 | 6 | RUN apk update \ 7 | && apk upgrade \ 8 | #&& apk add bash \ 9 | #&& apk add curl \ 10 | && pip install -r /tmp/requirements.txt \ 11 | && rm -rf /var/cache/apk/* \ 12 | && rm -rf /tmp 13 | 14 | CMD [ "python3", "/src/app.py" ] 15 | -------------------------------------------------------------------------------- /paymentapp/requirements.txt: -------------------------------------------------------------------------------- 1 | flask>=1.0.4 2 | boto3>=1.9 -------------------------------------------------------------------------------- /paymentapp/src/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import boto3 4 | import sys 5 | import logging 6 | 7 | from flask import Flask, make_response, request 8 | from flask.logging import create_logger 9 | 10 | 11 | app = Flask(__name__) 12 | log = create_logger(app) 13 | 14 | 15 | 16 | ddb_client = boto3.client('dynamodb', region_name=os.environ['AWS_REGION']) 17 | 18 | @app.route("/payments/health", methods=['GET'] ) 19 | def checkHealth(): 20 | response = make_response(json.dumps({"message" : "ok"}), 200) 21 | response.headers['Content-Type'] = 'application/json' 22 | return response 23 | 24 | @app.route("/payments/accounts/", methods=['GET'] ) 25 | def getPaymentMethod(userId): 26 | log.info('Incoming Params: {0}'.format(request)) 27 | 28 | # default is 404: No Records Found 29 | response_code=404 30 | response_body=None 31 | try: 32 | query_response = ddb_client.query( 33 | TableName=os.environ['PaymentAccountTableName'], 34 | Limit=10, 35 | ConsistentRead=False, 36 | KeyConditionExpression='userId = :userId', 37 | ExpressionAttributeValues={ 38 | ":userId": {"S": userId} 39 | } 40 | ) 41 | 42 | log.info('query_response: {0}'.format(query_response)) 43 | 44 | if query_response['ResponseMetadata']['HTTPStatusCode'] == 200 and len(query_response['Items']) > 0: 45 | response_code = 200 46 | response_body = [] 47 | for item in query_response['Items']: 48 | response_body.append ( 49 | { 50 | "userId" : item['userId']['S'], 51 | "type" : item['type']['S'], 52 | "details" : item['details']['S'] 53 | } 54 | ) 55 | except: 56 | log.error('Unknown Exceptions: {0}'.format(sys.exc_info())) 57 | response_code = 500 58 | 59 | 60 | response = make_response(json.dumps(response_body), response_code) 61 | response.headers['Content-Type'] = 'application/json' 62 | return response 63 | 64 | @app.route("/payments/account", methods=['POST'] ) 65 | def addPaymentMethod(): 66 | log.info('Incoming Params: {0}'.format(request)) 67 | 68 | try: 69 | payload = request.get_json() 70 | 71 | response_code = 400 72 | response_body= payload 73 | 74 | log.info('put_item_request: {0}'.format(payload)) 75 | 76 | put_item_response = ddb_client.put_item( 77 | TableName=os.environ['PaymentAccountTableName'], 78 | Item={ 79 | 'userId': { 80 | 'S': payload['userId'] 81 | }, 82 | 'type' : { 83 | 'S': payload['type'] 84 | }, 85 | 'details' : { 86 | 'S' : payload['details'] 87 | } 88 | } 89 | ) 90 | log.info('put_item_response: {0}'.format(put_item_response)) 91 | 92 | 93 | if put_item_response['ResponseMetadata']['HTTPStatusCode'] == 200: 94 | response_code = 200 95 | 96 | except: 97 | log.error('Exceptions: {0}'.format(sys.exc_info())) 98 | response_code = 500 99 | 100 | 101 | response = make_response(json.dumps(response_body), response_code) 102 | response.headers['Content-Type'] = 'application/json' 103 | 104 | return response 105 | 106 | if __name__ == "__main__": 107 | app.run(host='0.0.0.0', port=7000) 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/e8aaa166e4b6fcfebf30293b061e10c4c1094a81/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/e8aaa166e4b6fcfebf30293b061e10c4c1094a81/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/e8aaa166e4b6fcfebf30293b061e10c4c1094a81/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | } 8 | 9 | .App-header { 10 | background-color: #282c34; 11 | min-height: 100vh; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | font-size: calc(10px + 2vmin); 17 | color: white; 18 | } 19 | 20 | .App-link { 21 | color: #09d3ac; 22 | } 23 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import { css } from 'glamor' 4 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' 5 | import Nav from './Nav' 6 | import Amplify, { Auth } from 'aws-amplify'; 7 | import awsconfig from './aws-exports'; 8 | import { withAuthenticator } from 'aws-amplify-react'; 9 | import AWSAppSyncClient, { createAppSyncLink }from 'aws-appsync'; 10 | 11 | import { ApolloLink } from "apollo-link"; 12 | import { createHttpLink } from "apollo-link-http"; 13 | 14 | import gql from 'graphql-tag' 15 | import * as queries from './graphql/queries' 16 | import * as mutations from './graphql/mutations' 17 | import * as subscriptions from './graphql/subscriptions' 18 | 19 | Amplify.configure(awsconfig); 20 | 21 | const AppSyncConfig = { 22 | url: awsconfig.aws_appsync_graphqlEndpoint, 23 | region: awsconfig.aws_appsync_region, 24 | auth: { 25 | type: awsconfig.aws_appsync_authenticationType, 26 | // Get logged-in users credential 27 | jwtToken: async () => (await Auth.currentSession()).getAccessToken().getJwtToken() 28 | } 29 | }; 30 | 31 | class App extends React.PureComponent { 32 | constructor(props) { 33 | super(props); 34 | this.state = { 35 | appsync_client: null 36 | } 37 | } 38 | componentDidMount() { 39 | Auth.currentSession().then((session) => { 40 | const client = new AWSAppSyncClient(AppSyncConfig, { 41 | link: createAppSyncLink({ 42 | ...AppSyncConfig, 43 | resultsFetcherLink: ApolloLink.from([ 44 | createHttpLink({ 45 | uri: AppSyncConfig.url, 46 | headers: { 47 | "x-access-token": session.getAccessToken().getJwtToken() 48 | } 49 | }) 50 | ]) 51 | }) 52 | }); 53 | 54 | this.setState({ appsync_client: client }) 55 | }) 56 | 57 | } 58 | 59 | render() { 60 | if (!this.state.appsync_client) { 61 | return ( 62 |
63 | 64 |
65 | ) 66 | } else { 67 | return ( 68 |
69 | 70 |
71 |
79 |
80 |
81 | ); 82 | 83 | } 84 | 85 | } 86 | } 87 | 88 | export default withAuthenticator(App, true); 89 | 90 | 91 | class User extends Component { 92 | 93 | constructor(props) { 94 | super(props); 95 | this.state = { 96 | userName: '', 97 | email: '', 98 | phoneNumber: '', 99 | statusMessage: 'Fetching user information ' 100 | } 101 | } 102 | 103 | componentDidMount() { 104 | 105 | 106 | (async () => { 107 | await this.props.appsync_client.hydrated(); 108 | this.props.appsync_client.watchQuery({ 109 | query: gql(queries.getUserInfo), 110 | variables: { 111 | userName: Auth.user.signInUserSession.accessToken.payload.username 112 | } 113 | }) 114 | .subscribe({ 115 | next: ({ data , loading }) => { 116 | if(!loading){ 117 | this.setState({ 118 | userName: data.getUserInfo.userName, 119 | email: data.getUserInfo.email, 120 | phoneNumber : data.getUserInfo.phoneNumber, 121 | statusMessage: '' 122 | }) 123 | } 124 | 125 | }, 126 | error: (e) => { 127 | console.error(e) 128 | this.setState({ 129 | statusMessage: 'Unable to get user information.' 130 | }) 131 | } 132 | }) 133 | 134 | })(); 135 | 136 | } 137 | 138 | render() { 139 | return ( 140 | 141 | 142 |
143 | 144 | 145 | 146 | 147 | 148 | 149 |
150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 |
Your Profile
User Name{this.state.userName}
Email Address{this.state.email}
Phone Number{this.state.phoneNumber}
171 |
172 | ); 173 | } 174 | } 175 | 176 | class Order extends Component { 177 | 178 | constructor(props) { 179 | super(props); 180 | this.state = { 181 | ordersList: [], 182 | neworderDetails: '' , 183 | ordersFoundFlag :false, 184 | statusMessage: 'Fetching your recently placed orders ' 185 | }; 186 | 187 | this.handleChange = this.handleChange.bind(this); 188 | this.handleSubmit = this.handleSubmit.bind(this); 189 | 190 | this.orders_subscription = null ; 191 | } 192 | 193 | componentDidMount() { 194 | let today = new Date(); 195 | 196 | (async () => { 197 | await this.props.appsync_client.hydrated(); 198 | 199 | this.props.appsync_client.watchQuery({ 200 | query: gql(queries.listRecentOrders), 201 | variables: { 202 | userId: Auth.user.attributes.sub, 203 | orderDateTime: today.toISOString() 204 | } 205 | }).subscribe({ 206 | next: ({ data , loading }) => { 207 | 208 | if(!loading) { 209 | 210 | if(data.listRecentOrders != null && data.listRecentOrders.length > 0) { 211 | this.setState({ 212 | ordersList: data.listRecentOrders, 213 | ordersFoundFlag: true, 214 | statusMessage: '' 215 | }) 216 | 217 | }else { 218 | this.setState({statusMessage : 'Looks like you have not placed any orders yet.' }) 219 | } 220 | } 221 | }, 222 | error: (e) => { 223 | console.error(e) 224 | this.setState({statusMessage : 'Could not get order details.' }) 225 | } 226 | }) 227 | 228 | })(); 229 | 230 | 231 | (async () => { 232 | this.orders_subscription = this.props.appsync_client.subscribe({ query: gql(subscriptions.addedOrder) }).subscribe({ 233 | next: subscriptionResponse => { 234 | let tmp = this.state.ordersList 235 | this.setState({ 236 | ordersList: tmp.concat(subscriptionResponse.data.addedOrder) 237 | }) 238 | }, 239 | error: e => { 240 | console.error(e); 241 | 242 | } 243 | }); 244 | })(); 245 | 246 | } 247 | 248 | componentWillUnmount() { 249 | this.orders_subscription.unsubscribe(); 250 | } 251 | 252 | 253 | 254 | render() { 255 | const listItems = this.state.ordersList.map((order, index) => 256 | 257 | 258 | {order.orderId} 259 | 260 | {order.details} 261 | {(new Date((order.orderDateTime))).toUTCString()} 262 | 263 | ); 264 | 265 | return ( 266 |
267 | 268 | 269 | 270 | 271 | 272 | 273 |
274 | 275 |
276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 |
Place Order
Order Details
289 | 290 |
291 | 292 |
293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | {listItems} 306 | 307 |
Recent Orders
Order IdOrder DetailsPlaced on
308 | 309 |
310 | ); 311 | } 312 | 313 | handleChange(e) { 314 | this.setState({ neworderDetails: e.target.value }); 315 | } 316 | 317 | handleSubmit(e) { 318 | e.preventDefault(); 319 | if (!this.state.neworderDetails.length) { 320 | return; 321 | } 322 | let today = new Date(); 323 | 324 | this.props.appsync_client.mutate({ 325 | mutation: gql(mutations.addOrder), 326 | variables: { 327 | userId: Auth.user.attributes.sub, 328 | orderDateTime: today.toISOString(), 329 | details: this.state.neworderDetails 330 | } 331 | }).then( 332 | response => { 333 | if (response.data.addOrder.orderId) { 334 | this.setState({statusMessage : 'Successfully placed your order.' }) 335 | } 336 | else { 337 | this.setState({statusMessage : 'Sorry could not place your order. Please try again.' }) 338 | } 339 | 340 | } 341 | ) 342 | 343 | } 344 | 345 | } 346 | 347 | 348 | class Payment extends Component { 349 | 350 | 351 | constructor(props) { 352 | super(props); 353 | this.state = { 354 | paymentAccountsList: [], 355 | paymentAccountDetails: '', 356 | paymentAccountType: '', 357 | statusMessage: 'Fetching your payment account settings ' 358 | }; 359 | this.handleSubmit = this.handleSubmit.bind(this); 360 | this.handleChange = this.handleChange.bind(this); 361 | 362 | this.payments_subscription = null ; 363 | 364 | } 365 | 366 | componentDidMount() { 367 | (async () => { 368 | await this.props.appsync_client.hydrated(); 369 | 370 | this.props.appsync_client.watchQuery({ 371 | query: gql(queries.getPaymentAccounts), 372 | variables: { 373 | userId: Auth.user.attributes.sub 374 | } 375 | }).subscribe({ 376 | next: ({ data , loading }) => { 377 | if(!loading) { 378 | if(data.getPaymentAccounts != null && data.getPaymentAccounts.length > 0) { 379 | this.setState({ 380 | paymentAccountsList: data.getPaymentAccounts, 381 | statusMessage: '' 382 | }) 383 | 384 | } 385 | } 386 | 387 | }, 388 | error: (e) => { 389 | console.error(e) 390 | this.setState({statusMessage : 'Looks like you have not set up any Payment account yet' }) 391 | 392 | } 393 | }) 394 | })(); 395 | 396 | (async () => { 397 | this.payments_subscription = this.props.appsync_client.subscribe({ query: gql(subscriptions.addedPaymentAccount) }).subscribe({ 398 | next: subscriptionResponse => { 399 | let tmp = this.state.paymentAccountsList.filter(item => item.type !== subscriptionResponse.data.addedPaymentAccount.type ) 400 | this.setState({ 401 | paymentAccountsList: tmp.concat(subscriptionResponse.data.addedPaymentAccount) 402 | }) 403 | }, 404 | error: e => { 405 | console.error(e); 406 | } 407 | }); 408 | })(); 409 | } 410 | 411 | componentWillUnmount() { 412 | this.payments_subscription.unsubscribe(); 413 | } 414 | 415 | 416 | 417 | render() { 418 | 419 | const renderPaymentAccountsList = this.state.paymentAccountsList.map((payment, index) => 420 | 421 | 422 | 423 | {payment.type} 424 | {payment.details} 425 | 426 | 427 | ); 428 | 429 | return ( 430 |
431 | 432 | 433 | 434 | 435 | 436 | 437 |
438 | 439 |
440 | 441 | 442 | 443 | 444 | 445 | 446 | 456 | 457 | 458 | 459 | 460 |
Add Payment Method
447 | 455 |
461 | 462 |
463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | {renderPaymentAccountsList} 474 | 475 |
Payment Accounts
Account TypeDetails
476 | 477 |
478 | ); 479 | } 480 | 481 | handleChange(e){ 482 | const value = e.target.value; 483 | this.setState({ 484 | [e.target.name]: value 485 | }); 486 | 487 | } 488 | 489 | handleSubmit(e) { 490 | e.preventDefault(); 491 | if (!this.state.paymentAccountDetails.length || !this.state.paymentAccountType.length) { 492 | return; 493 | } 494 | 495 | 496 | this.props.appsync_client.mutate({ 497 | mutation: gql(mutations.addPaymentAccount), 498 | variables: { 499 | userId: Auth.user.attributes.sub, 500 | paymentAccountType: this.state.paymentAccountType, 501 | paymentAccountDetails: this.state.paymentAccountDetails 502 | } 503 | }).then( 504 | response => { 505 | if (response.data.addPaymentAccount.type) { 506 | this.setState({statusMessage : 'Successfully updated payment settings' }) 507 | } 508 | else { 509 | this.setState({statusMessage : 'Sorry could not update payment setting' }) 510 | } 511 | } 512 | ); 513 | 514 | 515 | 516 | } 517 | } 518 | 519 | const styles = { 520 | header: { 521 | fontSize: 20, 522 | textAlign: 'left', 523 | color: 'white', 524 | background: 'darkorange' 525 | }, 526 | container: { 527 | textAlign: 'left', 528 | position: 'relative', 529 | left: '100px', 530 | top: '50px' 531 | }, 532 | th: { 533 | textAlign: 'left', 534 | fontSize: 20, 535 | position: 'relative', 536 | left: '100px', 537 | top: '50px', 538 | height: '50px', 539 | color: 'black', 540 | scope: 'row' 541 | 542 | }, 543 | 544 | td_label: { 545 | textAlign: 'left', 546 | fontSize: 20, 547 | position: 'relative', 548 | left: '100px', 549 | top: '50px', 550 | color: 'white', 551 | backgroundColor: 'darkorange' 552 | }, 553 | td: { 554 | textAlign: 'left', 555 | position: 'relative', 556 | left: '100px', 557 | top: '50px' 558 | }, 559 | status: { 560 | textAlign: 'left', 561 | position: 'relative', 562 | left: '100px', 563 | top: '50px', 564 | color : 'darkblue' 565 | } 566 | 567 | } -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/Nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { css } from 'glamor' 4 | 5 | export default class Nav extends React.Component { 6 | render() { 7 | return ( 8 |
9 |

Store Application

10 | Profile 11 | Orders 12 | Payment 13 | 14 |
15 | ) 16 | } 17 | } 18 | 19 | const styles = { 20 | link: { 21 | textDecoration: 'none', 22 | marginLeft: 15, 23 | color: 'white', 24 | ':hover': { 25 | textDecoration: 'underline' 26 | } 27 | }, 28 | container: { 29 | display: 'flex', 30 | backgroundColor: 'darkorange', 31 | padding: '0px 30px', 32 | alignItems: 'center' 33 | }, 34 | heading: { 35 | color: 'white', 36 | paddingRight: 20 37 | } 38 | } -------------------------------------------------------------------------------- /src/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const addPaymentAccount = `mutation AddPaymentAccount( 5 | $userId: ID! 6 | $paymentAccountType: String! 7 | $paymentAccountDetails: String! 8 | ) { 9 | addPaymentAccount( 10 | userId: $userId 11 | paymentAccountType: $paymentAccountType 12 | paymentAccountDetails: $paymentAccountDetails 13 | ) { 14 | userId 15 | type 16 | details 17 | } 18 | } 19 | `; 20 | export const addOrder = `mutation AddOrder($userId: ID!, $orderDateTime: String!, $details: String!) { 21 | addOrder(userId: $userId, orderDateTime: $orderDateTime, details: $details) { 22 | userId 23 | status 24 | orderDateTime 25 | details 26 | orderId 27 | } 28 | } 29 | `; 30 | -------------------------------------------------------------------------------- /src/graphql/queries.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const getPaymentAccounts = `query GetPaymentAccounts($userId: ID!) { 5 | getPaymentAccounts(userId: $userId) { 6 | userId 7 | type 8 | details 9 | } 10 | } 11 | `; 12 | export const getUserInfo = `query GetUserInfo($userName: ID!) { 13 | getUserInfo(userName: $userName) { 14 | userName 15 | email 16 | phoneNumber 17 | } 18 | } 19 | `; 20 | export const listRecentOrders = `query ListRecentOrders($userId: ID!, $orderDateTime: String!) { 21 | listRecentOrders(userId: $userId, orderDateTime: $orderDateTime) { 22 | userId 23 | status 24 | orderDateTime 25 | details 26 | orderId 27 | } 28 | } 29 | `; 30 | export const listRecentOrdersByStatus = `query ListRecentOrdersByStatus( 31 | $userId: ID! 32 | $orderDateTime: String! 33 | $status: OrderStatus! 34 | ) { 35 | listRecentOrdersByStatus( 36 | userId: $userId 37 | orderDateTime: $orderDateTime 38 | status: $status 39 | ) { 40 | userId 41 | status 42 | orderDateTime 43 | details 44 | orderId 45 | } 46 | } 47 | `; 48 | -------------------------------------------------------------------------------- /src/graphql/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "__schema" : { 4 | "queryType" : { 5 | "name" : "Query" 6 | }, 7 | "mutationType" : { 8 | "name" : "Mutation" 9 | }, 10 | "subscriptionType" : { 11 | "name" : "Subscription" 12 | }, 13 | "types" : [ { 14 | "kind" : "OBJECT", 15 | "name" : "Query", 16 | "description" : null, 17 | "fields" : [ { 18 | "name" : "getPaymentAccounts", 19 | "description" : null, 20 | "args" : [ { 21 | "name" : "userId", 22 | "description" : null, 23 | "type" : { 24 | "kind" : "NON_NULL", 25 | "name" : null, 26 | "ofType" : { 27 | "kind" : "SCALAR", 28 | "name" : "ID", 29 | "ofType" : null 30 | } 31 | }, 32 | "defaultValue" : null 33 | } ], 34 | "type" : { 35 | "kind" : "LIST", 36 | "name" : null, 37 | "ofType" : { 38 | "kind" : "OBJECT", 39 | "name" : "PaymentAccount", 40 | "ofType" : null 41 | } 42 | }, 43 | "isDeprecated" : false, 44 | "deprecationReason" : null 45 | }, { 46 | "name" : "getUserInfo", 47 | "description" : null, 48 | "args" : [ { 49 | "name" : "userName", 50 | "description" : null, 51 | "type" : { 52 | "kind" : "NON_NULL", 53 | "name" : null, 54 | "ofType" : { 55 | "kind" : "SCALAR", 56 | "name" : "ID", 57 | "ofType" : null 58 | } 59 | }, 60 | "defaultValue" : null 61 | } ], 62 | "type" : { 63 | "kind" : "OBJECT", 64 | "name" : "User", 65 | "ofType" : null 66 | }, 67 | "isDeprecated" : false, 68 | "deprecationReason" : null 69 | }, { 70 | "name" : "listRecentOrders", 71 | "description" : null, 72 | "args" : [ { 73 | "name" : "userId", 74 | "description" : null, 75 | "type" : { 76 | "kind" : "NON_NULL", 77 | "name" : null, 78 | "ofType" : { 79 | "kind" : "SCALAR", 80 | "name" : "ID", 81 | "ofType" : null 82 | } 83 | }, 84 | "defaultValue" : null 85 | }, { 86 | "name" : "orderDateTime", 87 | "description" : null, 88 | "type" : { 89 | "kind" : "NON_NULL", 90 | "name" : null, 91 | "ofType" : { 92 | "kind" : "SCALAR", 93 | "name" : "String", 94 | "ofType" : null 95 | } 96 | }, 97 | "defaultValue" : null 98 | } ], 99 | "type" : { 100 | "kind" : "LIST", 101 | "name" : null, 102 | "ofType" : { 103 | "kind" : "OBJECT", 104 | "name" : "Order", 105 | "ofType" : null 106 | } 107 | }, 108 | "isDeprecated" : false, 109 | "deprecationReason" : null 110 | }, { 111 | "name" : "listRecentOrdersByStatus", 112 | "description" : null, 113 | "args" : [ { 114 | "name" : "userId", 115 | "description" : null, 116 | "type" : { 117 | "kind" : "NON_NULL", 118 | "name" : null, 119 | "ofType" : { 120 | "kind" : "SCALAR", 121 | "name" : "ID", 122 | "ofType" : null 123 | } 124 | }, 125 | "defaultValue" : null 126 | }, { 127 | "name" : "orderDateTime", 128 | "description" : null, 129 | "type" : { 130 | "kind" : "NON_NULL", 131 | "name" : null, 132 | "ofType" : { 133 | "kind" : "SCALAR", 134 | "name" : "String", 135 | "ofType" : null 136 | } 137 | }, 138 | "defaultValue" : null 139 | }, { 140 | "name" : "status", 141 | "description" : null, 142 | "type" : { 143 | "kind" : "NON_NULL", 144 | "name" : null, 145 | "ofType" : { 146 | "kind" : "ENUM", 147 | "name" : "OrderStatus", 148 | "ofType" : null 149 | } 150 | }, 151 | "defaultValue" : null 152 | } ], 153 | "type" : { 154 | "kind" : "LIST", 155 | "name" : null, 156 | "ofType" : { 157 | "kind" : "OBJECT", 158 | "name" : "Order", 159 | "ofType" : null 160 | } 161 | }, 162 | "isDeprecated" : false, 163 | "deprecationReason" : null 164 | } ], 165 | "inputFields" : null, 166 | "interfaces" : [ ], 167 | "enumValues" : null, 168 | "possibleTypes" : null 169 | }, { 170 | "kind" : "OBJECT", 171 | "name" : "PaymentAccount", 172 | "description" : null, 173 | "fields" : [ { 174 | "name" : "userId", 175 | "description" : null, 176 | "args" : [ ], 177 | "type" : { 178 | "kind" : "NON_NULL", 179 | "name" : null, 180 | "ofType" : { 181 | "kind" : "SCALAR", 182 | "name" : "ID", 183 | "ofType" : null 184 | } 185 | }, 186 | "isDeprecated" : false, 187 | "deprecationReason" : null 188 | }, { 189 | "name" : "type", 190 | "description" : null, 191 | "args" : [ ], 192 | "type" : { 193 | "kind" : "NON_NULL", 194 | "name" : null, 195 | "ofType" : { 196 | "kind" : "SCALAR", 197 | "name" : "String", 198 | "ofType" : null 199 | } 200 | }, 201 | "isDeprecated" : false, 202 | "deprecationReason" : null 203 | }, { 204 | "name" : "details", 205 | "description" : null, 206 | "args" : [ ], 207 | "type" : { 208 | "kind" : "NON_NULL", 209 | "name" : null, 210 | "ofType" : { 211 | "kind" : "SCALAR", 212 | "name" : "String", 213 | "ofType" : null 214 | } 215 | }, 216 | "isDeprecated" : false, 217 | "deprecationReason" : null 218 | } ], 219 | "inputFields" : null, 220 | "interfaces" : [ ], 221 | "enumValues" : null, 222 | "possibleTypes" : null 223 | }, { 224 | "kind" : "SCALAR", 225 | "name" : "ID", 226 | "description" : "Built-in ID", 227 | "fields" : null, 228 | "inputFields" : null, 229 | "interfaces" : null, 230 | "enumValues" : null, 231 | "possibleTypes" : null 232 | }, { 233 | "kind" : "SCALAR", 234 | "name" : "String", 235 | "description" : "Built-in String", 236 | "fields" : null, 237 | "inputFields" : null, 238 | "interfaces" : null, 239 | "enumValues" : null, 240 | "possibleTypes" : null 241 | }, { 242 | "kind" : "OBJECT", 243 | "name" : "User", 244 | "description" : null, 245 | "fields" : [ { 246 | "name" : "userName", 247 | "description" : null, 248 | "args" : [ ], 249 | "type" : { 250 | "kind" : "NON_NULL", 251 | "name" : null, 252 | "ofType" : { 253 | "kind" : "SCALAR", 254 | "name" : "ID", 255 | "ofType" : null 256 | } 257 | }, 258 | "isDeprecated" : false, 259 | "deprecationReason" : null 260 | }, { 261 | "name" : "email", 262 | "description" : null, 263 | "args" : [ ], 264 | "type" : { 265 | "kind" : "SCALAR", 266 | "name" : "String", 267 | "ofType" : null 268 | }, 269 | "isDeprecated" : false, 270 | "deprecationReason" : null 271 | }, { 272 | "name" : "phoneNumber", 273 | "description" : null, 274 | "args" : [ ], 275 | "type" : { 276 | "kind" : "SCALAR", 277 | "name" : "String", 278 | "ofType" : null 279 | }, 280 | "isDeprecated" : false, 281 | "deprecationReason" : null 282 | } ], 283 | "inputFields" : null, 284 | "interfaces" : [ ], 285 | "enumValues" : null, 286 | "possibleTypes" : null 287 | }, { 288 | "kind" : "OBJECT", 289 | "name" : "Order", 290 | "description" : null, 291 | "fields" : [ { 292 | "name" : "userId", 293 | "description" : null, 294 | "args" : [ ], 295 | "type" : { 296 | "kind" : "NON_NULL", 297 | "name" : null, 298 | "ofType" : { 299 | "kind" : "SCALAR", 300 | "name" : "ID", 301 | "ofType" : null 302 | } 303 | }, 304 | "isDeprecated" : false, 305 | "deprecationReason" : null 306 | }, { 307 | "name" : "status", 308 | "description" : null, 309 | "args" : [ ], 310 | "type" : { 311 | "kind" : "ENUM", 312 | "name" : "OrderStatus", 313 | "ofType" : null 314 | }, 315 | "isDeprecated" : false, 316 | "deprecationReason" : null 317 | }, { 318 | "name" : "orderDateTime", 319 | "description" : null, 320 | "args" : [ ], 321 | "type" : { 322 | "kind" : "NON_NULL", 323 | "name" : null, 324 | "ofType" : { 325 | "kind" : "SCALAR", 326 | "name" : "String", 327 | "ofType" : null 328 | } 329 | }, 330 | "isDeprecated" : false, 331 | "deprecationReason" : null 332 | }, { 333 | "name" : "details", 334 | "description" : null, 335 | "args" : [ ], 336 | "type" : { 337 | "kind" : "NON_NULL", 338 | "name" : null, 339 | "ofType" : { 340 | "kind" : "SCALAR", 341 | "name" : "String", 342 | "ofType" : null 343 | } 344 | }, 345 | "isDeprecated" : false, 346 | "deprecationReason" : null 347 | }, { 348 | "name" : "orderId", 349 | "description" : null, 350 | "args" : [ ], 351 | "type" : { 352 | "kind" : "SCALAR", 353 | "name" : "String", 354 | "ofType" : null 355 | }, 356 | "isDeprecated" : false, 357 | "deprecationReason" : null 358 | } ], 359 | "inputFields" : null, 360 | "interfaces" : [ ], 361 | "enumValues" : null, 362 | "possibleTypes" : null 363 | }, { 364 | "kind" : "ENUM", 365 | "name" : "OrderStatus", 366 | "description" : null, 367 | "fields" : null, 368 | "inputFields" : null, 369 | "interfaces" : null, 370 | "enumValues" : [ { 371 | "name" : "DELIVERED", 372 | "description" : null, 373 | "isDeprecated" : false, 374 | "deprecationReason" : null 375 | }, { 376 | "name" : "IN_TRANSIT", 377 | "description" : null, 378 | "isDeprecated" : false, 379 | "deprecationReason" : null 380 | }, { 381 | "name" : "PENDING", 382 | "description" : null, 383 | "isDeprecated" : false, 384 | "deprecationReason" : null 385 | }, { 386 | "name" : "PROCESSING", 387 | "description" : null, 388 | "isDeprecated" : false, 389 | "deprecationReason" : null 390 | }, { 391 | "name" : "CANCELLED", 392 | "description" : null, 393 | "isDeprecated" : false, 394 | "deprecationReason" : null 395 | } ], 396 | "possibleTypes" : null 397 | }, { 398 | "kind" : "OBJECT", 399 | "name" : "Mutation", 400 | "description" : null, 401 | "fields" : [ { 402 | "name" : "addPaymentAccount", 403 | "description" : null, 404 | "args" : [ { 405 | "name" : "userId", 406 | "description" : null, 407 | "type" : { 408 | "kind" : "NON_NULL", 409 | "name" : null, 410 | "ofType" : { 411 | "kind" : "SCALAR", 412 | "name" : "ID", 413 | "ofType" : null 414 | } 415 | }, 416 | "defaultValue" : null 417 | }, { 418 | "name" : "paymentAccountType", 419 | "description" : null, 420 | "type" : { 421 | "kind" : "NON_NULL", 422 | "name" : null, 423 | "ofType" : { 424 | "kind" : "SCALAR", 425 | "name" : "String", 426 | "ofType" : null 427 | } 428 | }, 429 | "defaultValue" : null 430 | }, { 431 | "name" : "paymentAccountDetails", 432 | "description" : null, 433 | "type" : { 434 | "kind" : "NON_NULL", 435 | "name" : null, 436 | "ofType" : { 437 | "kind" : "SCALAR", 438 | "name" : "String", 439 | "ofType" : null 440 | } 441 | }, 442 | "defaultValue" : null 443 | } ], 444 | "type" : { 445 | "kind" : "OBJECT", 446 | "name" : "PaymentAccount", 447 | "ofType" : null 448 | }, 449 | "isDeprecated" : false, 450 | "deprecationReason" : null 451 | }, { 452 | "name" : "addOrder", 453 | "description" : null, 454 | "args" : [ { 455 | "name" : "userId", 456 | "description" : null, 457 | "type" : { 458 | "kind" : "NON_NULL", 459 | "name" : null, 460 | "ofType" : { 461 | "kind" : "SCALAR", 462 | "name" : "ID", 463 | "ofType" : null 464 | } 465 | }, 466 | "defaultValue" : null 467 | }, { 468 | "name" : "orderDateTime", 469 | "description" : null, 470 | "type" : { 471 | "kind" : "NON_NULL", 472 | "name" : null, 473 | "ofType" : { 474 | "kind" : "SCALAR", 475 | "name" : "String", 476 | "ofType" : null 477 | } 478 | }, 479 | "defaultValue" : null 480 | }, { 481 | "name" : "details", 482 | "description" : null, 483 | "type" : { 484 | "kind" : "NON_NULL", 485 | "name" : null, 486 | "ofType" : { 487 | "kind" : "SCALAR", 488 | "name" : "String", 489 | "ofType" : null 490 | } 491 | }, 492 | "defaultValue" : null 493 | } ], 494 | "type" : { 495 | "kind" : "OBJECT", 496 | "name" : "Order", 497 | "ofType" : null 498 | }, 499 | "isDeprecated" : false, 500 | "deprecationReason" : null 501 | } ], 502 | "inputFields" : null, 503 | "interfaces" : [ ], 504 | "enumValues" : null, 505 | "possibleTypes" : null 506 | }, { 507 | "kind" : "OBJECT", 508 | "name" : "Subscription", 509 | "description" : null, 510 | "fields" : [ { 511 | "name" : "addedPaymentAccount", 512 | "description" : null, 513 | "args" : [ ], 514 | "type" : { 515 | "kind" : "OBJECT", 516 | "name" : "PaymentAccount", 517 | "ofType" : null 518 | }, 519 | "isDeprecated" : false, 520 | "deprecationReason" : null 521 | }, { 522 | "name" : "addedOrder", 523 | "description" : null, 524 | "args" : [ ], 525 | "type" : { 526 | "kind" : "OBJECT", 527 | "name" : "Order", 528 | "ofType" : null 529 | }, 530 | "isDeprecated" : false, 531 | "deprecationReason" : null 532 | } ], 533 | "inputFields" : null, 534 | "interfaces" : [ ], 535 | "enumValues" : null, 536 | "possibleTypes" : null 537 | }, { 538 | "kind" : "OBJECT", 539 | "name" : "__Schema", 540 | "description" : "A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, the entry points for query, mutation, and subscription operations.", 541 | "fields" : [ { 542 | "name" : "types", 543 | "description" : "A list of all types supported by this server.", 544 | "args" : [ ], 545 | "type" : { 546 | "kind" : "NON_NULL", 547 | "name" : null, 548 | "ofType" : { 549 | "kind" : "LIST", 550 | "name" : null, 551 | "ofType" : { 552 | "kind" : "NON_NULL", 553 | "name" : null, 554 | "ofType" : { 555 | "kind" : "OBJECT", 556 | "name" : "__Type", 557 | "ofType" : null 558 | } 559 | } 560 | } 561 | }, 562 | "isDeprecated" : false, 563 | "deprecationReason" : null 564 | }, { 565 | "name" : "queryType", 566 | "description" : "The type that query operations will be rooted at.", 567 | "args" : [ ], 568 | "type" : { 569 | "kind" : "NON_NULL", 570 | "name" : null, 571 | "ofType" : { 572 | "kind" : "OBJECT", 573 | "name" : "__Type", 574 | "ofType" : null 575 | } 576 | }, 577 | "isDeprecated" : false, 578 | "deprecationReason" : null 579 | }, { 580 | "name" : "mutationType", 581 | "description" : "If this server supports mutation, the type that mutation operations will be rooted at.", 582 | "args" : [ ], 583 | "type" : { 584 | "kind" : "OBJECT", 585 | "name" : "__Type", 586 | "ofType" : null 587 | }, 588 | "isDeprecated" : false, 589 | "deprecationReason" : null 590 | }, { 591 | "name" : "directives", 592 | "description" : "'A list of all directives supported by this server.", 593 | "args" : [ ], 594 | "type" : { 595 | "kind" : "NON_NULL", 596 | "name" : null, 597 | "ofType" : { 598 | "kind" : "LIST", 599 | "name" : null, 600 | "ofType" : { 601 | "kind" : "NON_NULL", 602 | "name" : null, 603 | "ofType" : { 604 | "kind" : "OBJECT", 605 | "name" : "__Directive", 606 | "ofType" : null 607 | } 608 | } 609 | } 610 | }, 611 | "isDeprecated" : false, 612 | "deprecationReason" : null 613 | }, { 614 | "name" : "subscriptionType", 615 | "description" : "'If this server support subscription, the type that subscription operations will be rooted at.", 616 | "args" : [ ], 617 | "type" : { 618 | "kind" : "OBJECT", 619 | "name" : "__Type", 620 | "ofType" : null 621 | }, 622 | "isDeprecated" : false, 623 | "deprecationReason" : null 624 | } ], 625 | "inputFields" : null, 626 | "interfaces" : [ ], 627 | "enumValues" : null, 628 | "possibleTypes" : null 629 | }, { 630 | "kind" : "OBJECT", 631 | "name" : "__Type", 632 | "description" : null, 633 | "fields" : [ { 634 | "name" : "kind", 635 | "description" : null, 636 | "args" : [ ], 637 | "type" : { 638 | "kind" : "NON_NULL", 639 | "name" : null, 640 | "ofType" : { 641 | "kind" : "ENUM", 642 | "name" : "__TypeKind", 643 | "ofType" : null 644 | } 645 | }, 646 | "isDeprecated" : false, 647 | "deprecationReason" : null 648 | }, { 649 | "name" : "name", 650 | "description" : null, 651 | "args" : [ ], 652 | "type" : { 653 | "kind" : "SCALAR", 654 | "name" : "String", 655 | "ofType" : null 656 | }, 657 | "isDeprecated" : false, 658 | "deprecationReason" : null 659 | }, { 660 | "name" : "description", 661 | "description" : null, 662 | "args" : [ ], 663 | "type" : { 664 | "kind" : "SCALAR", 665 | "name" : "String", 666 | "ofType" : null 667 | }, 668 | "isDeprecated" : false, 669 | "deprecationReason" : null 670 | }, { 671 | "name" : "fields", 672 | "description" : null, 673 | "args" : [ { 674 | "name" : "includeDeprecated", 675 | "description" : null, 676 | "type" : { 677 | "kind" : "SCALAR", 678 | "name" : "Boolean", 679 | "ofType" : null 680 | }, 681 | "defaultValue" : "false" 682 | } ], 683 | "type" : { 684 | "kind" : "LIST", 685 | "name" : null, 686 | "ofType" : { 687 | "kind" : "NON_NULL", 688 | "name" : null, 689 | "ofType" : { 690 | "kind" : "OBJECT", 691 | "name" : "__Field", 692 | "ofType" : null 693 | } 694 | } 695 | }, 696 | "isDeprecated" : false, 697 | "deprecationReason" : null 698 | }, { 699 | "name" : "interfaces", 700 | "description" : null, 701 | "args" : [ ], 702 | "type" : { 703 | "kind" : "LIST", 704 | "name" : null, 705 | "ofType" : { 706 | "kind" : "NON_NULL", 707 | "name" : null, 708 | "ofType" : { 709 | "kind" : "OBJECT", 710 | "name" : "__Type", 711 | "ofType" : null 712 | } 713 | } 714 | }, 715 | "isDeprecated" : false, 716 | "deprecationReason" : null 717 | }, { 718 | "name" : "possibleTypes", 719 | "description" : null, 720 | "args" : [ ], 721 | "type" : { 722 | "kind" : "LIST", 723 | "name" : null, 724 | "ofType" : { 725 | "kind" : "NON_NULL", 726 | "name" : null, 727 | "ofType" : { 728 | "kind" : "OBJECT", 729 | "name" : "__Type", 730 | "ofType" : null 731 | } 732 | } 733 | }, 734 | "isDeprecated" : false, 735 | "deprecationReason" : null 736 | }, { 737 | "name" : "enumValues", 738 | "description" : null, 739 | "args" : [ { 740 | "name" : "includeDeprecated", 741 | "description" : null, 742 | "type" : { 743 | "kind" : "SCALAR", 744 | "name" : "Boolean", 745 | "ofType" : null 746 | }, 747 | "defaultValue" : "false" 748 | } ], 749 | "type" : { 750 | "kind" : "LIST", 751 | "name" : null, 752 | "ofType" : { 753 | "kind" : "NON_NULL", 754 | "name" : null, 755 | "ofType" : { 756 | "kind" : "OBJECT", 757 | "name" : "__EnumValue", 758 | "ofType" : null 759 | } 760 | } 761 | }, 762 | "isDeprecated" : false, 763 | "deprecationReason" : null 764 | }, { 765 | "name" : "inputFields", 766 | "description" : null, 767 | "args" : [ ], 768 | "type" : { 769 | "kind" : "LIST", 770 | "name" : null, 771 | "ofType" : { 772 | "kind" : "NON_NULL", 773 | "name" : null, 774 | "ofType" : { 775 | "kind" : "OBJECT", 776 | "name" : "__InputValue", 777 | "ofType" : null 778 | } 779 | } 780 | }, 781 | "isDeprecated" : false, 782 | "deprecationReason" : null 783 | }, { 784 | "name" : "ofType", 785 | "description" : null, 786 | "args" : [ ], 787 | "type" : { 788 | "kind" : "OBJECT", 789 | "name" : "__Type", 790 | "ofType" : null 791 | }, 792 | "isDeprecated" : false, 793 | "deprecationReason" : null 794 | } ], 795 | "inputFields" : null, 796 | "interfaces" : [ ], 797 | "enumValues" : null, 798 | "possibleTypes" : null 799 | }, { 800 | "kind" : "ENUM", 801 | "name" : "__TypeKind", 802 | "description" : "An enum describing what kind of type a given __Type is", 803 | "fields" : null, 804 | "inputFields" : null, 805 | "interfaces" : null, 806 | "enumValues" : [ { 807 | "name" : "SCALAR", 808 | "description" : "Indicates this type is a scalar.", 809 | "isDeprecated" : false, 810 | "deprecationReason" : null 811 | }, { 812 | "name" : "OBJECT", 813 | "description" : "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 814 | "isDeprecated" : false, 815 | "deprecationReason" : null 816 | }, { 817 | "name" : "INTERFACE", 818 | "description" : "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 819 | "isDeprecated" : false, 820 | "deprecationReason" : null 821 | }, { 822 | "name" : "UNION", 823 | "description" : "Indicates this type is a union. `possibleTypes` is a valid field.", 824 | "isDeprecated" : false, 825 | "deprecationReason" : null 826 | }, { 827 | "name" : "ENUM", 828 | "description" : "Indicates this type is an enum. `enumValues` is a valid field.", 829 | "isDeprecated" : false, 830 | "deprecationReason" : null 831 | }, { 832 | "name" : "INPUT_OBJECT", 833 | "description" : "Indicates this type is an input object. `inputFields` is a valid field.", 834 | "isDeprecated" : false, 835 | "deprecationReason" : null 836 | }, { 837 | "name" : "LIST", 838 | "description" : "Indicates this type is a list. `ofType` is a valid field.", 839 | "isDeprecated" : false, 840 | "deprecationReason" : null 841 | }, { 842 | "name" : "NON_NULL", 843 | "description" : "Indicates this type is a non-null. `ofType` is a valid field.", 844 | "isDeprecated" : false, 845 | "deprecationReason" : null 846 | } ], 847 | "possibleTypes" : null 848 | }, { 849 | "kind" : "OBJECT", 850 | "name" : "__Field", 851 | "description" : null, 852 | "fields" : [ { 853 | "name" : "name", 854 | "description" : null, 855 | "args" : [ ], 856 | "type" : { 857 | "kind" : "NON_NULL", 858 | "name" : null, 859 | "ofType" : { 860 | "kind" : "SCALAR", 861 | "name" : "String", 862 | "ofType" : null 863 | } 864 | }, 865 | "isDeprecated" : false, 866 | "deprecationReason" : null 867 | }, { 868 | "name" : "description", 869 | "description" : null, 870 | "args" : [ ], 871 | "type" : { 872 | "kind" : "SCALAR", 873 | "name" : "String", 874 | "ofType" : null 875 | }, 876 | "isDeprecated" : false, 877 | "deprecationReason" : null 878 | }, { 879 | "name" : "args", 880 | "description" : null, 881 | "args" : [ ], 882 | "type" : { 883 | "kind" : "NON_NULL", 884 | "name" : null, 885 | "ofType" : { 886 | "kind" : "LIST", 887 | "name" : null, 888 | "ofType" : { 889 | "kind" : "NON_NULL", 890 | "name" : null, 891 | "ofType" : { 892 | "kind" : "OBJECT", 893 | "name" : "__InputValue", 894 | "ofType" : null 895 | } 896 | } 897 | } 898 | }, 899 | "isDeprecated" : false, 900 | "deprecationReason" : null 901 | }, { 902 | "name" : "type", 903 | "description" : null, 904 | "args" : [ ], 905 | "type" : { 906 | "kind" : "NON_NULL", 907 | "name" : null, 908 | "ofType" : { 909 | "kind" : "OBJECT", 910 | "name" : "__Type", 911 | "ofType" : null 912 | } 913 | }, 914 | "isDeprecated" : false, 915 | "deprecationReason" : null 916 | }, { 917 | "name" : "isDeprecated", 918 | "description" : null, 919 | "args" : [ ], 920 | "type" : { 921 | "kind" : "NON_NULL", 922 | "name" : null, 923 | "ofType" : { 924 | "kind" : "SCALAR", 925 | "name" : "Boolean", 926 | "ofType" : null 927 | } 928 | }, 929 | "isDeprecated" : false, 930 | "deprecationReason" : null 931 | }, { 932 | "name" : "deprecationReason", 933 | "description" : null, 934 | "args" : [ ], 935 | "type" : { 936 | "kind" : "SCALAR", 937 | "name" : "String", 938 | "ofType" : null 939 | }, 940 | "isDeprecated" : false, 941 | "deprecationReason" : null 942 | } ], 943 | "inputFields" : null, 944 | "interfaces" : [ ], 945 | "enumValues" : null, 946 | "possibleTypes" : null 947 | }, { 948 | "kind" : "OBJECT", 949 | "name" : "__InputValue", 950 | "description" : null, 951 | "fields" : [ { 952 | "name" : "name", 953 | "description" : null, 954 | "args" : [ ], 955 | "type" : { 956 | "kind" : "NON_NULL", 957 | "name" : null, 958 | "ofType" : { 959 | "kind" : "SCALAR", 960 | "name" : "String", 961 | "ofType" : null 962 | } 963 | }, 964 | "isDeprecated" : false, 965 | "deprecationReason" : null 966 | }, { 967 | "name" : "description", 968 | "description" : null, 969 | "args" : [ ], 970 | "type" : { 971 | "kind" : "SCALAR", 972 | "name" : "String", 973 | "ofType" : null 974 | }, 975 | "isDeprecated" : false, 976 | "deprecationReason" : null 977 | }, { 978 | "name" : "type", 979 | "description" : null, 980 | "args" : [ ], 981 | "type" : { 982 | "kind" : "NON_NULL", 983 | "name" : null, 984 | "ofType" : { 985 | "kind" : "OBJECT", 986 | "name" : "__Type", 987 | "ofType" : null 988 | } 989 | }, 990 | "isDeprecated" : false, 991 | "deprecationReason" : null 992 | }, { 993 | "name" : "defaultValue", 994 | "description" : null, 995 | "args" : [ ], 996 | "type" : { 997 | "kind" : "SCALAR", 998 | "name" : "String", 999 | "ofType" : null 1000 | }, 1001 | "isDeprecated" : false, 1002 | "deprecationReason" : null 1003 | } ], 1004 | "inputFields" : null, 1005 | "interfaces" : [ ], 1006 | "enumValues" : null, 1007 | "possibleTypes" : null 1008 | }, { 1009 | "kind" : "SCALAR", 1010 | "name" : "Boolean", 1011 | "description" : "Built-in Boolean", 1012 | "fields" : null, 1013 | "inputFields" : null, 1014 | "interfaces" : null, 1015 | "enumValues" : null, 1016 | "possibleTypes" : null 1017 | }, { 1018 | "kind" : "OBJECT", 1019 | "name" : "__EnumValue", 1020 | "description" : null, 1021 | "fields" : [ { 1022 | "name" : "name", 1023 | "description" : null, 1024 | "args" : [ ], 1025 | "type" : { 1026 | "kind" : "NON_NULL", 1027 | "name" : null, 1028 | "ofType" : { 1029 | "kind" : "SCALAR", 1030 | "name" : "String", 1031 | "ofType" : null 1032 | } 1033 | }, 1034 | "isDeprecated" : false, 1035 | "deprecationReason" : null 1036 | }, { 1037 | "name" : "description", 1038 | "description" : null, 1039 | "args" : [ ], 1040 | "type" : { 1041 | "kind" : "SCALAR", 1042 | "name" : "String", 1043 | "ofType" : null 1044 | }, 1045 | "isDeprecated" : false, 1046 | "deprecationReason" : null 1047 | }, { 1048 | "name" : "isDeprecated", 1049 | "description" : null, 1050 | "args" : [ ], 1051 | "type" : { 1052 | "kind" : "NON_NULL", 1053 | "name" : null, 1054 | "ofType" : { 1055 | "kind" : "SCALAR", 1056 | "name" : "Boolean", 1057 | "ofType" : null 1058 | } 1059 | }, 1060 | "isDeprecated" : false, 1061 | "deprecationReason" : null 1062 | }, { 1063 | "name" : "deprecationReason", 1064 | "description" : null, 1065 | "args" : [ ], 1066 | "type" : { 1067 | "kind" : "SCALAR", 1068 | "name" : "String", 1069 | "ofType" : null 1070 | }, 1071 | "isDeprecated" : false, 1072 | "deprecationReason" : null 1073 | } ], 1074 | "inputFields" : null, 1075 | "interfaces" : [ ], 1076 | "enumValues" : null, 1077 | "possibleTypes" : null 1078 | }, { 1079 | "kind" : "OBJECT", 1080 | "name" : "__Directive", 1081 | "description" : null, 1082 | "fields" : [ { 1083 | "name" : "name", 1084 | "description" : null, 1085 | "args" : [ ], 1086 | "type" : { 1087 | "kind" : "SCALAR", 1088 | "name" : "String", 1089 | "ofType" : null 1090 | }, 1091 | "isDeprecated" : false, 1092 | "deprecationReason" : null 1093 | }, { 1094 | "name" : "description", 1095 | "description" : null, 1096 | "args" : [ ], 1097 | "type" : { 1098 | "kind" : "SCALAR", 1099 | "name" : "String", 1100 | "ofType" : null 1101 | }, 1102 | "isDeprecated" : false, 1103 | "deprecationReason" : null 1104 | }, { 1105 | "name" : "locations", 1106 | "description" : null, 1107 | "args" : [ ], 1108 | "type" : { 1109 | "kind" : "LIST", 1110 | "name" : null, 1111 | "ofType" : { 1112 | "kind" : "NON_NULL", 1113 | "name" : null, 1114 | "ofType" : { 1115 | "kind" : "ENUM", 1116 | "name" : "__DirectiveLocation", 1117 | "ofType" : null 1118 | } 1119 | } 1120 | }, 1121 | "isDeprecated" : false, 1122 | "deprecationReason" : null 1123 | }, { 1124 | "name" : "args", 1125 | "description" : null, 1126 | "args" : [ ], 1127 | "type" : { 1128 | "kind" : "NON_NULL", 1129 | "name" : null, 1130 | "ofType" : { 1131 | "kind" : "LIST", 1132 | "name" : null, 1133 | "ofType" : { 1134 | "kind" : "NON_NULL", 1135 | "name" : null, 1136 | "ofType" : { 1137 | "kind" : "OBJECT", 1138 | "name" : "__InputValue", 1139 | "ofType" : null 1140 | } 1141 | } 1142 | } 1143 | }, 1144 | "isDeprecated" : false, 1145 | "deprecationReason" : null 1146 | }, { 1147 | "name" : "onOperation", 1148 | "description" : null, 1149 | "args" : [ ], 1150 | "type" : { 1151 | "kind" : "SCALAR", 1152 | "name" : "Boolean", 1153 | "ofType" : null 1154 | }, 1155 | "isDeprecated" : true, 1156 | "deprecationReason" : "Use `locations`." 1157 | }, { 1158 | "name" : "onFragment", 1159 | "description" : null, 1160 | "args" : [ ], 1161 | "type" : { 1162 | "kind" : "SCALAR", 1163 | "name" : "Boolean", 1164 | "ofType" : null 1165 | }, 1166 | "isDeprecated" : true, 1167 | "deprecationReason" : "Use `locations`." 1168 | }, { 1169 | "name" : "onField", 1170 | "description" : null, 1171 | "args" : [ ], 1172 | "type" : { 1173 | "kind" : "SCALAR", 1174 | "name" : "Boolean", 1175 | "ofType" : null 1176 | }, 1177 | "isDeprecated" : true, 1178 | "deprecationReason" : "Use `locations`." 1179 | } ], 1180 | "inputFields" : null, 1181 | "interfaces" : [ ], 1182 | "enumValues" : null, 1183 | "possibleTypes" : null 1184 | }, { 1185 | "kind" : "ENUM", 1186 | "name" : "__DirectiveLocation", 1187 | "description" : "An enum describing valid locations where a directive can be placed", 1188 | "fields" : null, 1189 | "inputFields" : null, 1190 | "interfaces" : null, 1191 | "enumValues" : [ { 1192 | "name" : "QUERY", 1193 | "description" : "Indicates the directive is valid on queries.", 1194 | "isDeprecated" : false, 1195 | "deprecationReason" : null 1196 | }, { 1197 | "name" : "MUTATION", 1198 | "description" : "Indicates the directive is valid on mutations.", 1199 | "isDeprecated" : false, 1200 | "deprecationReason" : null 1201 | }, { 1202 | "name" : "FIELD", 1203 | "description" : "Indicates the directive is valid on fields.", 1204 | "isDeprecated" : false, 1205 | "deprecationReason" : null 1206 | }, { 1207 | "name" : "FRAGMENT_DEFINITION", 1208 | "description" : "Indicates the directive is valid on fragment definitions.", 1209 | "isDeprecated" : false, 1210 | "deprecationReason" : null 1211 | }, { 1212 | "name" : "FRAGMENT_SPREAD", 1213 | "description" : "Indicates the directive is valid on fragment spreads.", 1214 | "isDeprecated" : false, 1215 | "deprecationReason" : null 1216 | }, { 1217 | "name" : "INLINE_FRAGMENT", 1218 | "description" : "Indicates the directive is valid on inline fragments.", 1219 | "isDeprecated" : false, 1220 | "deprecationReason" : null 1221 | }, { 1222 | "name" : "SCHEMA", 1223 | "description" : "Indicates the directive is valid on a schema SDL definition.", 1224 | "isDeprecated" : false, 1225 | "deprecationReason" : null 1226 | }, { 1227 | "name" : "SCALAR", 1228 | "description" : "Indicates the directive is valid on a scalar SDL definition.", 1229 | "isDeprecated" : false, 1230 | "deprecationReason" : null 1231 | }, { 1232 | "name" : "OBJECT", 1233 | "description" : "Indicates the directive is valid on an object SDL definition.", 1234 | "isDeprecated" : false, 1235 | "deprecationReason" : null 1236 | }, { 1237 | "name" : "FIELD_DEFINITION", 1238 | "description" : "Indicates the directive is valid on a field SDL definition.", 1239 | "isDeprecated" : false, 1240 | "deprecationReason" : null 1241 | }, { 1242 | "name" : "ARGUMENT_DEFINITION", 1243 | "description" : "Indicates the directive is valid on a field argument SDL definition.", 1244 | "isDeprecated" : false, 1245 | "deprecationReason" : null 1246 | }, { 1247 | "name" : "INTERFACE", 1248 | "description" : "Indicates the directive is valid on an interface SDL definition.", 1249 | "isDeprecated" : false, 1250 | "deprecationReason" : null 1251 | }, { 1252 | "name" : "UNION", 1253 | "description" : "Indicates the directive is valid on an union SDL definition.", 1254 | "isDeprecated" : false, 1255 | "deprecationReason" : null 1256 | }, { 1257 | "name" : "ENUM", 1258 | "description" : "Indicates the directive is valid on an enum SDL definition.", 1259 | "isDeprecated" : false, 1260 | "deprecationReason" : null 1261 | }, { 1262 | "name" : "ENUM_VALUE", 1263 | "description" : "Indicates the directive is valid on an enum value SDL definition.", 1264 | "isDeprecated" : false, 1265 | "deprecationReason" : null 1266 | }, { 1267 | "name" : "INPUT_OBJECT", 1268 | "description" : "Indicates the directive is valid on an input object SDL definition.", 1269 | "isDeprecated" : false, 1270 | "deprecationReason" : null 1271 | }, { 1272 | "name" : "INPUT_FIELD_DEFINITION", 1273 | "description" : "Indicates the directive is valid on an input object field SDL definition.", 1274 | "isDeprecated" : false, 1275 | "deprecationReason" : null 1276 | } ], 1277 | "possibleTypes" : null 1278 | } ], 1279 | "directives" : [ { 1280 | "name" : "include", 1281 | "description" : "Directs the executor to include this field or fragment only when the `if` argument is true", 1282 | "locations" : [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], 1283 | "args" : [ { 1284 | "name" : "if", 1285 | "description" : "Included when true.", 1286 | "type" : { 1287 | "kind" : "NON_NULL", 1288 | "name" : null, 1289 | "ofType" : { 1290 | "kind" : "SCALAR", 1291 | "name" : "Boolean", 1292 | "ofType" : null 1293 | } 1294 | }, 1295 | "defaultValue" : null 1296 | } ], 1297 | "onOperation" : false, 1298 | "onFragment" : true, 1299 | "onField" : true 1300 | }, { 1301 | "name" : "skip", 1302 | "description" : "Directs the executor to skip this field or fragment when the `if`'argument is true.", 1303 | "locations" : [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], 1304 | "args" : [ { 1305 | "name" : "if", 1306 | "description" : "Skipped when true.", 1307 | "type" : { 1308 | "kind" : "NON_NULL", 1309 | "name" : null, 1310 | "ofType" : { 1311 | "kind" : "SCALAR", 1312 | "name" : "Boolean", 1313 | "ofType" : null 1314 | } 1315 | }, 1316 | "defaultValue" : null 1317 | } ], 1318 | "onOperation" : false, 1319 | "onFragment" : true, 1320 | "onField" : true 1321 | }, { 1322 | "name" : "defer", 1323 | "description" : "This directive allows results to be deferred during execution", 1324 | "locations" : [ "FIELD" ], 1325 | "args" : [ ], 1326 | "onOperation" : false, 1327 | "onFragment" : false, 1328 | "onField" : true 1329 | }, { 1330 | "name" : "aws_iam", 1331 | "description" : "Tells the service this field/object has access authorized by sigv4 signing.", 1332 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ], 1333 | "args" : [ ], 1334 | "onOperation" : false, 1335 | "onFragment" : false, 1336 | "onField" : false 1337 | }, { 1338 | "name" : "aws_publish", 1339 | "description" : "Tells the service which subscriptions will be published to when this mutation is called. This directive is deprecated use @aws_susbscribe directive instead.", 1340 | "locations" : [ "FIELD_DEFINITION" ], 1341 | "args" : [ { 1342 | "name" : "subscriptions", 1343 | "description" : "List of subscriptions which will be published to when this mutation is called.", 1344 | "type" : { 1345 | "kind" : "LIST", 1346 | "name" : null, 1347 | "ofType" : { 1348 | "kind" : "SCALAR", 1349 | "name" : "String", 1350 | "ofType" : null 1351 | } 1352 | }, 1353 | "defaultValue" : null 1354 | } ], 1355 | "onOperation" : false, 1356 | "onFragment" : false, 1357 | "onField" : false 1358 | }, { 1359 | "name" : "aws_api_key", 1360 | "description" : "Tells the service this field/object has access authorized by an API key.", 1361 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ], 1362 | "args" : [ ], 1363 | "onOperation" : false, 1364 | "onFragment" : false, 1365 | "onField" : false 1366 | }, { 1367 | "name" : "aws_oidc", 1368 | "description" : "Tells the service this field/object has access authorized by an OIDC token.", 1369 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ], 1370 | "args" : [ ], 1371 | "onOperation" : false, 1372 | "onFragment" : false, 1373 | "onField" : false 1374 | }, { 1375 | "name" : "aws_cognito_user_pools", 1376 | "description" : "Tells the service this field/object has access authorized by a Cognito User Pools token.", 1377 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ], 1378 | "args" : [ { 1379 | "name" : "cognito_groups", 1380 | "description" : "List of cognito user pool groups which have access on this field", 1381 | "type" : { 1382 | "kind" : "LIST", 1383 | "name" : null, 1384 | "ofType" : { 1385 | "kind" : "SCALAR", 1386 | "name" : "String", 1387 | "ofType" : null 1388 | } 1389 | }, 1390 | "defaultValue" : null 1391 | } ], 1392 | "onOperation" : false, 1393 | "onFragment" : false, 1394 | "onField" : false 1395 | }, { 1396 | "name" : "deprecated", 1397 | "description" : null, 1398 | "locations" : [ "FIELD_DEFINITION", "ENUM_VALUE" ], 1399 | "args" : [ { 1400 | "name" : "reason", 1401 | "description" : null, 1402 | "type" : { 1403 | "kind" : "SCALAR", 1404 | "name" : "String", 1405 | "ofType" : null 1406 | }, 1407 | "defaultValue" : "\"No longer supported\"" 1408 | } ], 1409 | "onOperation" : false, 1410 | "onFragment" : false, 1411 | "onField" : false 1412 | }, { 1413 | "name" : "aws_auth", 1414 | "description" : "Directs the schema to enforce authorization on a field", 1415 | "locations" : [ "FIELD_DEFINITION" ], 1416 | "args" : [ { 1417 | "name" : "cognito_groups", 1418 | "description" : "List of cognito user pool groups which have access on this field", 1419 | "type" : { 1420 | "kind" : "LIST", 1421 | "name" : null, 1422 | "ofType" : { 1423 | "kind" : "SCALAR", 1424 | "name" : "String", 1425 | "ofType" : null 1426 | } 1427 | }, 1428 | "defaultValue" : null 1429 | } ], 1430 | "onOperation" : false, 1431 | "onFragment" : false, 1432 | "onField" : false 1433 | }, { 1434 | "name" : "aws_subscribe", 1435 | "description" : "Tells the service which mutation triggers this subscription.", 1436 | "locations" : [ "FIELD_DEFINITION" ], 1437 | "args" : [ { 1438 | "name" : "mutations", 1439 | "description" : "List of mutations which will trigger this subscription when they are called.", 1440 | "type" : { 1441 | "kind" : "LIST", 1442 | "name" : null, 1443 | "ofType" : { 1444 | "kind" : "SCALAR", 1445 | "name" : "String", 1446 | "ofType" : null 1447 | } 1448 | }, 1449 | "defaultValue" : null 1450 | } ], 1451 | "onOperation" : false, 1452 | "onFragment" : false, 1453 | "onField" : false 1454 | } ] 1455 | } 1456 | } 1457 | } -------------------------------------------------------------------------------- /src/graphql/subscriptions.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const addedPaymentAccount = `subscription AddedPaymentAccount { 5 | addedPaymentAccount { 6 | userId 7 | type 8 | details 9 | } 10 | } 11 | `; 12 | export const addedOrder = `subscription AddedOrder { 13 | addedOrder { 14 | userId 15 | status 16 | orderDateTime 17 | details 18 | orderId 19 | } 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------