├── paymentapp
├── requirements.txt
├── Dockerfile
└── src
│ └── app.py
├── amplify
├── backend
│ ├── api
│ │ └── StoreApi
│ │ │ ├── transform.conf.json
│ │ │ ├── resolvers
│ │ │ ├── Mutation.addPaymentAccount.res.vtl
│ │ │ ├── Query.getPaymentAccounts.res.vtl
│ │ │ ├── Query.getUserInfo.res.vtl
│ │ │ ├── Query.getPaymentAccounts.req.vtl
│ │ │ ├── Mutation.addOrder.res.vtl
│ │ │ ├── Query.listRecentOrders.res.vtl
│ │ │ ├── Query.listRecentOrdersByStatus.res.vtl
│ │ │ ├── Mutation.addPaymentAccount.req.vtl
│ │ │ ├── Query.getUserInfo.req.vtl
│ │ │ ├── Mutation.addOrder.req.vtl
│ │ │ ├── Query.listRecentOrders.req.vtl
│ │ │ └── Query.listRecentOrdersByStatus.req.vtl
│ │ │ ├── schema.graphql
│ │ │ ├── parameters.json
│ │ │ └── stacks
│ │ │ └── CustomResources.json
│ ├── user
│ │ └── service
│ │ │ ├── parameters.json
│ │ │ └── template.json
│ ├── backend-config.json
│ ├── auth
│ │ └── storeapp039c491c
│ │ │ ├── parameters.json
│ │ │ └── storeapp039c491c-cloudformation-template.yml
│ └── payment
│ │ └── service
│ │ └── template.json
└── .config
│ └── project-config.json
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── orderapi
├── amplify
│ ├── backend
│ │ ├── api
│ │ │ └── OrderApi
│ │ │ │ ├── transform.conf.json
│ │ │ │ ├── parameters.json
│ │ │ │ ├── schema.graphql
│ │ │ │ └── stacks
│ │ │ │ └── CustomResources.json
│ │ └── backend-config.json
│ └── .config
│ │ └── project-config.json
├── .gitignore
├── .graphqlconfig.yml
├── src
│ └── graphql
│ │ ├── subscriptions.js
│ │ ├── mutations.js
│ │ └── queries.js
└── deploy_OrderApi.sh
├── images
├── add_repository.png
├── build_settings.png
├── solution_overview.png
└── connect_local_to_cloud_backend.png
├── src
├── App.test.js
├── App.css
├── index.css
├── graphql
│ ├── subscriptions.js
│ ├── mutations.js
│ ├── queries.js
│ └── schema.json
├── index.js
├── Nav.js
├── serviceWorker.js
├── logo.svg
└── App.js
├── CODE_OF_CONDUCT.md
├── .gitignore
├── LICENSE
├── package.json
├── amplify.yml
├── CONTRIBUTING.md
└── README.md
/paymentapp/requirements.txt:
--------------------------------------------------------------------------------
1 | flask>=1.0.4
2 | boto3>=1.9
--------------------------------------------------------------------------------
/amplify/backend/api/StoreApi/transform.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": 4
3 | }
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/orderapi/amplify/backend/api/OrderApi/transform.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": 4
3 | }
--------------------------------------------------------------------------------
/amplify/backend/api/StoreApi/resolvers/Mutation.addPaymentAccount.res.vtl:
--------------------------------------------------------------------------------
1 | $util.toJson($context.result)
--------------------------------------------------------------------------------
/amplify/backend/api/StoreApi/resolvers/Query.getPaymentAccounts.res.vtl:
--------------------------------------------------------------------------------
1 | $util.toJson($context.result)
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/images/add_repository.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/HEAD/images/add_repository.png
--------------------------------------------------------------------------------
/images/build_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/HEAD/images/build_settings.png
--------------------------------------------------------------------------------
/images/solution_overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/HEAD/images/solution_overview.png
--------------------------------------------------------------------------------
/images/connect_local_to_cloud_backend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-appsync-refarch-microservices/HEAD/images/connect_local_to_cloud_backend.png
--------------------------------------------------------------------------------
/orderapi/amplify/backend/api/OrderApi/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSyncApiName": "OrderApi",
3 | "DynamoDBBillingMode": "PAY_PER_REQUEST",
4 | "DynamoDBEnableServerSideEncryption": "false"
5 | }
--------------------------------------------------------------------------------
/amplify/backend/user/service/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "CognitoUserPoolId": {
3 | "Fn::GetAtt": [
4 | "authstoreapp039c491c",
5 | "Outputs.UserPoolId"
6 | ]
7 | }
8 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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.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
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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.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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AWS AppSync Microservices-Access-Layer Reference Architecture
2 |
3 | 
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 |
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 | 
71 |
72 | 4. Create new environment, branch and create an IAM role (with necessary permissions)
73 |
74 | 
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 | 
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 | ```
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
72 |
73 | } />
74 | } />
75 | } />
76 |
77 |
78 |
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 | | Your Profile |
155 |
156 |
157 | | User Name |
158 | {this.state.userName} |
159 |
160 |
161 | | Email Address |
162 | {this.state.email} |
163 |
164 |
165 | | Phone Number |
166 | {this.state.phoneNumber} |
167 |
168 |
169 |
170 |
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 |
291 |
292 |
293 |
294 |
295 |
296 |
297 | | Recent Orders |
298 |
299 |
300 | | Order Id |
301 |
302 | Order Details |
303 | Placed on |
304 |
305 | {listItems}
306 |
307 |
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 |
463 |
464 |
465 |
466 |
467 | | Payment Accounts |
468 |
469 |
470 | | Account Type |
471 | Details |
472 |
473 | {renderPaymentAccountsList}
474 |
475 |
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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------