├── amplify ├── backend │ ├── api │ │ └── amplifynestedsample │ │ │ ├── resolvers │ │ │ ├── Query.echo.res.vtl │ │ │ ├── Query.commentsForTodo.res.vtl │ │ │ ├── Query.echo.req.vtl │ │ │ ├── Query.nearbyTodos.res.vtl │ │ │ ├── Query.commentsForTodo.req.vtl │ │ │ └── Query.nearbyTodos.req.vtl │ │ │ ├── build │ │ │ ├── resolvers │ │ │ │ ├── Query.echo.res.vtl │ │ │ │ ├── Comment.todo.res.vtl │ │ │ │ ├── Query.getTodo.res.vtl │ │ │ │ ├── Query.listComments.res.vtl │ │ │ │ ├── Query.listTodos.res.vtl │ │ │ │ ├── Mutation.createTodo.res.vtl │ │ │ │ ├── Mutation.deleteTodo.res.vtl │ │ │ │ ├── Mutation.updateTodo.res.vtl │ │ │ │ ├── Query.getComment.res.vtl │ │ │ │ ├── Mutation.createComment.res.vtl │ │ │ │ ├── Mutation.deleteComment.res.vtl │ │ │ │ ├── Mutation.updateComment.res.vtl │ │ │ │ ├── Query.commentsForTodo.res.vtl │ │ │ │ ├── Todo.comments.res.vtl │ │ │ │ ├── Query.getTodo.req.vtl │ │ │ │ ├── Query.getComment.req.vtl │ │ │ │ ├── Comment.todo.req.vtl │ │ │ │ ├── Query.echo.req.vtl │ │ │ │ ├── Query.nearbyTodos.res.vtl │ │ │ │ ├── Query.searchTodos.res.vtl │ │ │ │ ├── Query.listTodos.req.vtl │ │ │ │ ├── Query.listComments.req.vtl │ │ │ │ ├── Query.commentsForTodo.req.vtl │ │ │ │ ├── Mutation.createTodo.req.vtl │ │ │ │ ├── Mutation.createComment.req.vtl │ │ │ │ ├── Query.nearbyTodos.req.vtl │ │ │ │ ├── Todo.comments.req.vtl │ │ │ │ ├── Query.searchTodos.req.vtl │ │ │ │ ├── Mutation.deleteTodo.req.vtl │ │ │ │ ├── Mutation.deleteComment.req.vtl │ │ │ │ ├── Mutation.updateTodo.req.vtl │ │ │ │ └── Mutation.updateComment.req.vtl │ │ │ ├── functions │ │ │ │ └── ElasticsearchStreamingLambdaFunction.zip │ │ │ ├── schema.graphql │ │ │ ├── stacks │ │ │ │ ├── CustomResources.json │ │ │ │ ├── ConnectionStack.json │ │ │ │ ├── SearchableStack.json │ │ │ │ ├── Todo.json │ │ │ │ └── Comment.json │ │ │ └── cloudformation-template.json │ │ │ ├── parameters.json │ │ │ ├── schema.graphql │ │ │ └── stacks │ │ │ └── CustomResources.json │ ├── function │ │ └── echofunction │ │ │ ├── src │ │ │ ├── event.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ └── index.js │ │ │ ├── dist │ │ │ ├── echofunction-1547763568-latest-build.zip │ │ │ ├── echofunction-1547769446-latest-build.zip │ │ │ └── echofunction-1547846145-latest-build.zip │ │ │ └── pongfunction-cloudformation-template.json │ ├── backend-config.json │ └── awscloudformation │ │ └── nested-cloudformation-stack.yml ├── .config │ └── project-config.json └── team-provider-info.json ├── .gitignore └── README.md /amplify/backend/api/amplifynestedsample/resolvers/Query.echo.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($ctx.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.echo.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($ctx.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Comment.todo.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.getTodo.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.listComments.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($ctx.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.listTodos.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($ctx.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.createTodo.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.deleteTodo.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.updateTodo.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.getComment.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.createComment.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.deleteComment.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.updateComment.res.vtl: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/function/echofunction/src/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "value1", 3 | "key2": "value2", 4 | "key3": "value3" 5 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/resolvers/Query.commentsForTodo.res.vtl: -------------------------------------------------------------------------------- 1 | ## Query.commentsForTodo.res.vtl ** 2 | 3 | $util.toJson($ctx.result) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.commentsForTodo.res.vtl: -------------------------------------------------------------------------------- 1 | ## Query.commentsForTodo.res.vtl ** 2 | 3 | $util.toJson($ctx.result) -------------------------------------------------------------------------------- /amplify/backend/function/echofunction/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "echofunction", 3 | "version": "2.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Todo.comments.res.vtl: -------------------------------------------------------------------------------- 1 | #if( !$result ) 2 | #set( $result = $ctx.result ) 3 | #end 4 | $util.toJson($result) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | #amplify 4 | amplify/\#current-cloud-backend 5 | amplify/.config/local-* 6 | amplify/backend/amplify-meta.json 7 | aws-exports.js 8 | awsconfiguration.json -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.getTodo.req.vtl: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2017-02-28", 3 | "operation": "GetItem", 4 | "key": { 5 | "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) 6 | } 7 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.getComment.req.vtl: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2017-02-28", 3 | "operation": "GetItem", 4 | "key": { 5 | "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) 6 | } 7 | } -------------------------------------------------------------------------------- /amplify/backend/function/echofunction/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "echofunction", 3 | "version": "2.0.0", 4 | "description": "Lambda function generated by Amplify", 5 | "main": "index.js", 6 | "license": "Apache-2.0" 7 | } 8 | -------------------------------------------------------------------------------- /amplify/backend/function/echofunction/dist/echofunction-1547763568-latest-build.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikeparisstuff/amplify-cli-nested-api-sample/HEAD/amplify/backend/function/echofunction/dist/echofunction-1547763568-latest-build.zip -------------------------------------------------------------------------------- /amplify/backend/function/echofunction/dist/echofunction-1547769446-latest-build.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikeparisstuff/amplify-cli-nested-api-sample/HEAD/amplify/backend/function/echofunction/dist/echofunction-1547769446-latest-build.zip -------------------------------------------------------------------------------- /amplify/backend/function/echofunction/dist/echofunction-1547846145-latest-build.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikeparisstuff/amplify-cli-nested-api-sample/HEAD/amplify/backend/function/echofunction/dist/echofunction-1547846145-latest-build.zip -------------------------------------------------------------------------------- /amplify/backend/function/echofunction/src/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context) { //eslint-disable-line 2 | console.log(`Echoing...\n${JSON.stringify(event, null, 4)}`); 3 | context.done(null, event.arguments.msg); 4 | }; 5 | -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSyncApiName": "amplifynestedsample", 3 | "S3DeploymentBucket": "amplifynestedsample-20190118130523-deployment", 4 | "S3DeploymentRootKey": "amplify-appsync-files/1547850263074" 5 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/functions/ElasticsearchStreamingLambdaFunction.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikeparisstuff/amplify-cli-nested-api-sample/HEAD/amplify/backend/api/amplifynestedsample/build/functions/ElasticsearchStreamingLambdaFunction.zip -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Comment.todo.req.vtl: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2017-02-28", 3 | "operation": "GetItem", 4 | "key": { 5 | "id": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.commentTodoId, "___xamznone____")) 6 | } 7 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/resolvers/Query.echo.req.vtl: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2017-02-28", 3 | "operation": "Invoke", 4 | "payload": { 5 | "type": "Query", 6 | "field": "echo", 7 | "arguments": $utils.toJson($context.arguments), 8 | "identity": $utils.toJson($context.identity), 9 | "source": $utils.toJson($context.source) 10 | } 11 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.echo.req.vtl: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2017-02-28", 3 | "operation": "Invoke", 4 | "payload": { 5 | "type": "Query", 6 | "field": "echo", 7 | "arguments": $utils.toJson($context.arguments), 8 | "identity": $utils.toJson($context.identity), 9 | "source": $utils.toJson($context.source) 10 | } 11 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/resolvers/Query.nearbyTodos.res.vtl: -------------------------------------------------------------------------------- 1 | #set( $items = [] ) 2 | #foreach( $entry in $context.result.hits.hits ) 3 | #if( !$foreach.hasNext ) 4 | #set( $nextToken = "$entry.sort.get(0)" ) 5 | #end 6 | $util.qr($items.add($entry.get("_source"))) 7 | #end 8 | $util.toJson({ 9 | "items": $items, 10 | "total": $ctx.result.hits.total, 11 | "nextToken": $nextToken 12 | }) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.nearbyTodos.res.vtl: -------------------------------------------------------------------------------- 1 | #set( $items = [] ) 2 | #foreach( $entry in $context.result.hits.hits ) 3 | #if( !$foreach.hasNext ) 4 | #set( $nextToken = "$entry.sort.get(0)" ) 5 | #end 6 | $util.qr($items.add($entry.get("_source"))) 7 | #end 8 | $util.toJson({ 9 | "items": $items, 10 | "total": $ctx.result.hits.total, 11 | "nextToken": $nextToken 12 | }) -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.searchTodos.res.vtl: -------------------------------------------------------------------------------- 1 | #set( $items = [] ) 2 | #foreach( $entry in $context.result.hits.hits ) 3 | #if( !$foreach.hasNext ) 4 | #set( $nextToken = "$entry.sort.get(0)" ) 5 | #end 6 | $util.qr($items.add($entry.get("_source"))) 7 | #end 8 | $util.toJson({ 9 | "items": $items, 10 | "total": $ctx.result.hits.total, 11 | "nextToken": $nextToken 12 | }) -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": { 3 | "amplifynestedsample": { 4 | "service": "AppSync", 5 | "providerPlugin": "awscloudformation", 6 | "output": { 7 | "securityType": "API_KEY" 8 | } 9 | } 10 | }, 11 | "function": { 12 | "echofunction": { 13 | "service": "Lambda", 14 | "providerPlugin": "awscloudformation", 15 | "build": true, 16 | "dependsOn": [] 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.listTodos.req.vtl: -------------------------------------------------------------------------------- 1 | #set( $limit = $util.defaultIfNull($context.args.limit, 10) ) 2 | { 3 | "version": "2017-02-28", 4 | "operation": "Scan", 5 | "filter": #if( $context.args.filter ) 6 | $util.transform.toDynamoDBFilterExpression($ctx.args.filter) 7 | #else 8 | null 9 | #end, 10 | "limit": $limit, 11 | "nextToken": #if( $context.args.nextToken ) 12 | "$context.args.nextToken" 13 | #else 14 | null 15 | #end 16 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.listComments.req.vtl: -------------------------------------------------------------------------------- 1 | #set( $limit = $util.defaultIfNull($context.args.limit, 10) ) 2 | { 3 | "version": "2017-02-28", 4 | "operation": "Scan", 5 | "filter": #if( $context.args.filter ) 6 | $util.transform.toDynamoDBFilterExpression($ctx.args.filter) 7 | #else 8 | null 9 | #end, 10 | "limit": $limit, 11 | "nextToken": #if( $context.args.nextToken ) 12 | "$context.args.nextToken" 13 | #else 14 | null 15 | #end 16 | } -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "amplifynestedsample", 3 | "version": "1.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 | } -------------------------------------------------------------------------------- /amplify/team-provider-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "awscloudformation": { 4 | "AuthRoleName": "amplifynestedsample-20190118130523-authRole", 5 | "UnauthRoleArn": "arn:aws:iam::158820598885:role/amplifynestedsample-20190118130523-unauthRole", 6 | "AuthRoleArn": "arn:aws:iam::158820598885:role/amplifynestedsample-20190118130523-authRole", 7 | "Region": "us-west-2", 8 | "DeploymentBucketName": "amplifynestedsample-20190118130523-deployment", 9 | "UnauthRoleName": "amplifynestedsample-20190118130523-unauthRole", 10 | "StackName": "amplifynestedsample-20190118130523", 11 | "StackId": "arn:aws:cloudformation:us-west-2:158820598885:stack/amplifynestedsample-20190118130523/c2c6f890-1b64-11e9-bbf7-02c241aeeb4e" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/resolvers/Query.commentsForTodo.req.vtl: -------------------------------------------------------------------------------- 1 | ## Query.commentsForTodo.req.vtl ** 2 | 3 | #set( $limit = $util.defaultIfNull($context.args.limit, 10) ) 4 | { 5 | "version": "2017-02-28", 6 | "operation": "Query", 7 | "query": { 8 | "expression": "#connectionAttribute = :connectionAttribute", 9 | "expressionNames": { 10 | "#connectionAttribute": "commentTodoId" 11 | }, 12 | "expressionValues": { 13 | ":connectionAttribute": { 14 | "S": "$context.args.todoId" 15 | } 16 | } 17 | }, 18 | "scanIndexForward": true, 19 | "limit": $limit, 20 | "nextToken": #if( $context.args.nextToken ) "$context.args.nextToken" #else null #end, 21 | "index": "gsi-TodoComments" 22 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.commentsForTodo.req.vtl: -------------------------------------------------------------------------------- 1 | ## Query.commentsForTodo.req.vtl ** 2 | 3 | #set( $limit = $util.defaultIfNull($context.args.limit, 10) ) 4 | { 5 | "version": "2017-02-28", 6 | "operation": "Query", 7 | "query": { 8 | "expression": "#connectionAttribute = :connectionAttribute", 9 | "expressionNames": { 10 | "#connectionAttribute": "commentTodoId" 11 | }, 12 | "expressionValues": { 13 | ":connectionAttribute": { 14 | "S": "$context.args.todoId" 15 | } 16 | } 17 | }, 18 | "scanIndexForward": true, 19 | "limit": $limit, 20 | "nextToken": #if( $context.args.nextToken ) "$context.args.nextToken" #else null #end, 21 | "index": "gsi-TodoComments" 22 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.createTodo.req.vtl: -------------------------------------------------------------------------------- 1 | ## [Start] Prepare DynamoDB PutItem Request. ** 2 | $util.qr($context.args.input.put("createdAt", $util.time.nowISO8601())) 3 | $util.qr($context.args.input.put("updatedAt", $util.time.nowISO8601())) 4 | $util.qr($context.args.input.put("__typename", "Todo")) 5 | { 6 | "version": "2017-02-28", 7 | "operation": "PutItem", 8 | "key": { 9 | "id": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.args.input.id, $util.autoId())) 10 | }, 11 | "attributeValues": $util.dynamodb.toMapValuesJson($context.args.input), 12 | "condition": { 13 | "expression": "attribute_not_exists(#id)", 14 | "expressionNames": { 15 | "#id": "id" 16 | } 17 | } 18 | } 19 | ## [End] Prepare DynamoDB PutItem Request. ** -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.createComment.req.vtl: -------------------------------------------------------------------------------- 1 | ## [Start] Prepare DynamoDB PutItem Request. ** 2 | $util.qr($context.args.input.put("createdAt", $util.time.nowISO8601())) 3 | $util.qr($context.args.input.put("updatedAt", $util.time.nowISO8601())) 4 | $util.qr($context.args.input.put("__typename", "Comment")) 5 | { 6 | "version": "2017-02-28", 7 | "operation": "PutItem", 8 | "key": { 9 | "id": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.args.input.id, $util.autoId())) 10 | }, 11 | "attributeValues": $util.dynamodb.toMapValuesJson($context.args.input), 12 | "condition": { 13 | "expression": "attribute_not_exists(#id)", 14 | "expressionNames": { 15 | "#id": "id" 16 | } 17 | } 18 | } 19 | ## [End] Prepare DynamoDB PutItem Request. ** -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/resolvers/Query.nearbyTodos.req.vtl: -------------------------------------------------------------------------------- 1 | #set( $indexPath = "/todo/doc/_search" ) 2 | #set( $distance = $util.defaultIfNull($ctx.args.km, 200) ) 3 | { 4 | "version": "2017-02-28", 5 | "operation": "GET", 6 | "path": "$indexPath.toLowerCase()", 7 | "params": { 8 | "body": { 9 | "query": { 10 | "bool" : { 11 | "must" : { 12 | "match_all" : {} 13 | }, 14 | "filter" : { 15 | "geo_distance" : { 16 | "distance" : "${distance}km", 17 | "location" : $util.toJson($ctx.args.location) 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.nearbyTodos.req.vtl: -------------------------------------------------------------------------------- 1 | #set( $indexPath = "/todo/doc/_search" ) 2 | #set( $distance = $util.defaultIfNull($ctx.args.km, 200) ) 3 | { 4 | "version": "2017-02-28", 5 | "operation": "GET", 6 | "path": "$indexPath.toLowerCase()", 7 | "params": { 8 | "body": { 9 | "query": { 10 | "bool" : { 11 | "must" : { 12 | "match_all" : {} 13 | }, 14 | "filter" : { 15 | "geo_distance" : { 16 | "distance" : "${distance}km", 17 | "location" : $util.toJson($ctx.args.location) 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/schema.graphql: -------------------------------------------------------------------------------- 1 | type Todo @model @searchable { 2 | id: ID! 3 | name: String! 4 | description: String 5 | comments: [Comment] @connection(name: "TodoComments", sortField: "createdAt") 6 | location: Location 7 | createdAt: AWSDateTime 8 | } 9 | type Comment @model { 10 | id: ID! 11 | content: String 12 | todo: Todo @connection(name: "TodoComments", sortField: "createdAt") 13 | createdAt: AWSDateTime 14 | } 15 | type Location { 16 | lat: Float 17 | lon: Float 18 | } 19 | input LocationInput { 20 | lat: Float 21 | lon: Float 22 | } 23 | type CommentConnection { 24 | items: [Comment] 25 | nextToken: String 26 | } 27 | type TodoConnection { 28 | items: [Todo] 29 | total: Int 30 | nextToken: String 31 | } 32 | type Query { 33 | commentsForTodo(todoId: ID!, limit: Int, nextToken: String): CommentConnection 34 | echo(msg: String): String 35 | nearbyTodos(location: LocationInput!, km: Int): TodoConnection 36 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Todo.comments.req.vtl: -------------------------------------------------------------------------------- 1 | #set( $limit = $util.defaultIfNull($context.args.limit, 10) ) 2 | { 3 | "version": "2017-02-28", 4 | "operation": "Query", 5 | "query": { 6 | "expression": "#connectionAttribute = :connectionAttribute", 7 | "expressionNames": { 8 | "#connectionAttribute": "commentTodoId" 9 | }, 10 | "expressionValues": { 11 | ":connectionAttribute": { 12 | "S": "$context.source.id" 13 | } 14 | } 15 | }, 16 | "scanIndexForward": #if( $context.args.sortDirection ) 17 | #if( $context.args.sortDirection == "ASC" ) 18 | true 19 | #else 20 | false 21 | #end 22 | #else 23 | true 24 | #end, 25 | "filter": #if( $context.args.filter ) 26 | $util.transform.toDynamoDBFilterExpression($ctx.args.filter) 27 | #else 28 | null 29 | #end, 30 | "limit": $limit, 31 | "nextToken": #if( $context.args.nextToken ) 32 | "$context.args.nextToken" 33 | #else 34 | null 35 | #end, 36 | "index": "gsi-TodoComments" 37 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Query.searchTodos.req.vtl: -------------------------------------------------------------------------------- 1 | #set( $indexPath = "/function toLowerCase() { [native code] }/doc/_search" ) 2 | { 3 | "version": "2017-02-28", 4 | "operation": "GET", 5 | "path": "$indexPath.toLowerCase()", 6 | "params": { 7 | "body": { 8 | "from": #if( $context.args.nextToken ) $context.args.nextToken #else 0 #end, 9 | "size": #if( $context.args.limit ) $context.args.limit #else 10 #end, 10 | "sort": #if( $context.args.sort ) 11 | [#if( !$util.isNullOrEmpty($context.args.sort.field) && !$util.isNullOrEmpty($context.args.sort.direction) ) 12 | { 13 | "$context.args.sort.field": { 14 | "order": "$context.args.sort.direction" 15 | } 16 | } 17 | #end, "_doc"] 18 | #else 19 | [] 20 | #end, 21 | "query": #if( $context.args.filter ) 22 | $util.transform.toElasticsearchQueryDSL($ctx.args.filter) 23 | #else 24 | { 25 | "match_all": {} 26 | } 27 | #end 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.deleteTodo.req.vtl: -------------------------------------------------------------------------------- 1 | #if( $authCondition ) 2 | #set( $condition = $authCondition ) 3 | $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#id)")) 4 | $util.qr($condition.expressionNames.put("#id", "id")) 5 | #else 6 | #set( $condition = { 7 | "expression": "attribute_exists(#id)", 8 | "expressionNames": { 9 | "#id": "id" 10 | } 11 | } ) 12 | #end 13 | #if( $versionedCondition ) 14 | $util.qr($condition.put("expression", "($condition.expression) AND $versionedCondition.expression")) 15 | $util.qr($condition.expressionNames.putAll($versionedCondition.expressionNames)) 16 | #set( $expressionValues = $util.defaultIfNull($condition.expressionValues, {}) ) 17 | $util.qr($expressionValues.putAll($versionedCondition.expressionValues)) 18 | #set( $condition.expressionValues = $expressionValues ) 19 | #end 20 | { 21 | "version": "2017-02-28", 22 | "operation": "DeleteItem", 23 | "key": { 24 | "id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id) 25 | }, 26 | "condition": $util.toJson($condition) 27 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.deleteComment.req.vtl: -------------------------------------------------------------------------------- 1 | #if( $authCondition ) 2 | #set( $condition = $authCondition ) 3 | $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#id)")) 4 | $util.qr($condition.expressionNames.put("#id", "id")) 5 | #else 6 | #set( $condition = { 7 | "expression": "attribute_exists(#id)", 8 | "expressionNames": { 9 | "#id": "id" 10 | } 11 | } ) 12 | #end 13 | #if( $versionedCondition ) 14 | $util.qr($condition.put("expression", "($condition.expression) AND $versionedCondition.expression")) 15 | $util.qr($condition.expressionNames.putAll($versionedCondition.expressionNames)) 16 | #set( $expressionValues = $util.defaultIfNull($condition.expressionValues, {}) ) 17 | $util.qr($expressionValues.putAll($versionedCondition.expressionValues)) 18 | #set( $condition.expressionValues = $expressionValues ) 19 | #end 20 | { 21 | "version": "2017-02-28", 22 | "operation": "DeleteItem", 23 | "key": { 24 | "id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id) 25 | }, 26 | "condition": $util.toJson($condition) 27 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.updateTodo.req.vtl: -------------------------------------------------------------------------------- 1 | #if( $authCondition && $authCondition.expression != "" ) 2 | #set( $condition = $authCondition ) 3 | $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#id)")) 4 | $util.qr($condition.expressionNames.put("#id", "id")) 5 | #else 6 | #set( $condition = { 7 | "expression": "attribute_exists(#id)", 8 | "expressionNames": { 9 | "#id": "id" 10 | }, 11 | "expressionValues": {} 12 | } ) 13 | #end 14 | ## Automatically set the updatedAt timestamp. ** 15 | $util.qr($context.args.input.put("updatedAt", $util.time.nowISO8601())) 16 | $util.qr($context.args.input.put("__typename", "Todo")) 17 | ## Update condition if type is @versioned ** 18 | #if( $versionedCondition ) 19 | $util.qr($condition.put("expression", "($condition.expression) AND $versionedCondition.expression")) 20 | $util.qr($condition.expressionNames.putAll($versionedCondition.expressionNames)) 21 | $util.qr($condition.expressionValues.putAll($versionedCondition.expressionValues)) 22 | #end 23 | #set( $expNames = {} ) 24 | #set( $expValues = {} ) 25 | #set( $expSet = {} ) 26 | #set( $expAdd = {} ) 27 | #set( $expRemove = [] ) 28 | #foreach( $entry in $util.map.copyAndRemoveAllKeys($context.args.input, ["id"]).entrySet() ) 29 | #if( $util.isNull($entry.value) ) 30 | #set( $discard = $expRemove.add("#$entry.key") ) 31 | $util.qr($expNames.put("#$entry.key", "$entry.key")) 32 | #else 33 | $util.qr($expSet.put("#$entry.key", ":$entry.key")) 34 | $util.qr($expNames.put("#$entry.key", "$entry.key")) 35 | $util.qr($expValues.put(":$entry.key", $util.dynamodb.toDynamoDB($entry.value))) 36 | #end 37 | #end 38 | #set( $expression = "" ) 39 | #if( !$expSet.isEmpty() ) 40 | #set( $expression = "SET" ) 41 | #foreach( $entry in $expSet.entrySet() ) 42 | #set( $expression = "$expression $entry.key = $entry.value" ) 43 | #if( $foreach.hasNext() ) 44 | #set( $expression = "$expression," ) 45 | #end 46 | #end 47 | #end 48 | #if( !$expAdd.isEmpty() ) 49 | #set( $expression = "$expression ADD" ) 50 | #foreach( $entry in $expAdd.entrySet() ) 51 | #set( $expression = "$expression $entry.key $entry.value" ) 52 | #if( $foreach.hasNext() ) 53 | #set( $expression = "$expression," ) 54 | #end 55 | #end 56 | #end 57 | #if( !$expRemove.isEmpty() ) 58 | #set( $expression = "$expression REMOVE" ) 59 | #foreach( $entry in $expRemove ) 60 | #set( $expression = "$expression $entry" ) 61 | #if( $foreach.hasNext() ) 62 | #set( $expression = "$expression," ) 63 | #end 64 | #end 65 | #end 66 | #set( $update = {} ) 67 | $util.qr($update.put("expression", "$expression")) 68 | #if( !$expNames.isEmpty() ) 69 | $util.qr($update.put("expressionNames", $expNames)) 70 | #end 71 | #if( !$expValues.isEmpty() ) 72 | $util.qr($update.put("expressionValues", $expValues)) 73 | #end 74 | { 75 | "version": "2017-02-28", 76 | "operation": "UpdateItem", 77 | "key": { 78 | "id": { 79 | "S": "$context.args.input.id" 80 | } 81 | }, 82 | "update": $util.toJson($update), 83 | "condition": $util.toJson($condition) 84 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/resolvers/Mutation.updateComment.req.vtl: -------------------------------------------------------------------------------- 1 | #if( $authCondition && $authCondition.expression != "" ) 2 | #set( $condition = $authCondition ) 3 | $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#id)")) 4 | $util.qr($condition.expressionNames.put("#id", "id")) 5 | #else 6 | #set( $condition = { 7 | "expression": "attribute_exists(#id)", 8 | "expressionNames": { 9 | "#id": "id" 10 | }, 11 | "expressionValues": {} 12 | } ) 13 | #end 14 | ## Automatically set the updatedAt timestamp. ** 15 | $util.qr($context.args.input.put("updatedAt", $util.time.nowISO8601())) 16 | $util.qr($context.args.input.put("__typename", "Comment")) 17 | ## Update condition if type is @versioned ** 18 | #if( $versionedCondition ) 19 | $util.qr($condition.put("expression", "($condition.expression) AND $versionedCondition.expression")) 20 | $util.qr($condition.expressionNames.putAll($versionedCondition.expressionNames)) 21 | $util.qr($condition.expressionValues.putAll($versionedCondition.expressionValues)) 22 | #end 23 | #set( $expNames = {} ) 24 | #set( $expValues = {} ) 25 | #set( $expSet = {} ) 26 | #set( $expAdd = {} ) 27 | #set( $expRemove = [] ) 28 | #foreach( $entry in $util.map.copyAndRemoveAllKeys($context.args.input, ["id"]).entrySet() ) 29 | #if( $util.isNull($entry.value) ) 30 | #set( $discard = $expRemove.add("#$entry.key") ) 31 | $util.qr($expNames.put("#$entry.key", "$entry.key")) 32 | #else 33 | $util.qr($expSet.put("#$entry.key", ":$entry.key")) 34 | $util.qr($expNames.put("#$entry.key", "$entry.key")) 35 | $util.qr($expValues.put(":$entry.key", $util.dynamodb.toDynamoDB($entry.value))) 36 | #end 37 | #end 38 | #set( $expression = "" ) 39 | #if( !$expSet.isEmpty() ) 40 | #set( $expression = "SET" ) 41 | #foreach( $entry in $expSet.entrySet() ) 42 | #set( $expression = "$expression $entry.key = $entry.value" ) 43 | #if( $foreach.hasNext() ) 44 | #set( $expression = "$expression," ) 45 | #end 46 | #end 47 | #end 48 | #if( !$expAdd.isEmpty() ) 49 | #set( $expression = "$expression ADD" ) 50 | #foreach( $entry in $expAdd.entrySet() ) 51 | #set( $expression = "$expression $entry.key $entry.value" ) 52 | #if( $foreach.hasNext() ) 53 | #set( $expression = "$expression," ) 54 | #end 55 | #end 56 | #end 57 | #if( !$expRemove.isEmpty() ) 58 | #set( $expression = "$expression REMOVE" ) 59 | #foreach( $entry in $expRemove ) 60 | #set( $expression = "$expression $entry" ) 61 | #if( $foreach.hasNext() ) 62 | #set( $expression = "$expression," ) 63 | #end 64 | #end 65 | #end 66 | #set( $update = {} ) 67 | $util.qr($update.put("expression", "$expression")) 68 | #if( !$expNames.isEmpty() ) 69 | $util.qr($update.put("expressionNames", $expNames)) 70 | #end 71 | #if( !$expValues.isEmpty() ) 72 | $util.qr($update.put("expressionValues", $expValues)) 73 | #end 74 | { 75 | "version": "2017-02-28", 76 | "operation": "UpdateItem", 77 | "key": { 78 | "id": { 79 | "S": "$context.args.input.id" 80 | } 81 | }, 82 | "update": $util.toJson($update), 83 | "condition": $util.toJson($condition) 84 | } -------------------------------------------------------------------------------- /amplify/backend/function/echofunction/pongfunction-cloudformation-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Lambda resource stack creation using Amplify CLI", 4 | "Parameters": { 5 | "env": { 6 | "Type": "String" 7 | } 8 | }, 9 | "Conditions": { 10 | "ShouldNotCreateEnvResources": { 11 | "Fn::Equals": [ 12 | { 13 | "Ref": "env" 14 | }, 15 | "NONE" 16 | ] 17 | } 18 | }, 19 | "Resources": { 20 | "LambdaFunction": { 21 | "Type": "AWS::Lambda::Function", 22 | "Properties": { 23 | "Handler": "index.handler", 24 | "FunctionName": { 25 | "Fn::If": [ 26 | "ShouldNotCreateEnvResources", 27 | "echofunction", 28 | { 29 | "Fn::Join": [ 30 | "", 31 | [ 32 | "echofunction", 33 | "-", 34 | { 35 | "Ref": "env" 36 | } 37 | ] 38 | ] 39 | } 40 | ] 41 | }, 42 | "Environment": { 43 | "Variables": { 44 | "ENV": { 45 | "Ref": "env" 46 | } 47 | } 48 | }, 49 | "Role": { 50 | "Fn::GetAtt": [ 51 | "LambdaExecutionRole", 52 | "Arn" 53 | ] 54 | }, 55 | "Runtime": "nodejs8.10", 56 | "Timeout": "25", 57 | "Code": { 58 | "S3Bucket": "amplifynestedsample-20190118130523-deployment", 59 | "S3Key": "amplify-builds/echofunction-1547846145-latest-build.zip" 60 | } 61 | } 62 | }, 63 | "LambdaExecutionRole": { 64 | "Type": "AWS::IAM::Role", 65 | "Properties": { 66 | "RoleName": { 67 | "Fn::If": [ 68 | "ShouldNotCreateEnvResources", 69 | "amplifynestedsampleLambdaRole56e036f1", 70 | { 71 | "Fn::Join": [ 72 | "", 73 | [ 74 | "amplifynestedsampleLambdaRole56e036f1", 75 | "-", 76 | { 77 | "Ref": "env" 78 | } 79 | ] 80 | ] 81 | } 82 | ] 83 | }, 84 | "AssumeRolePolicyDocument": { 85 | "Version": "2012-10-17", 86 | "Statement": [ 87 | { 88 | "Effect": "Allow", 89 | "Principal": { 90 | "Service": [ 91 | "lambda.amazonaws.com" 92 | ] 93 | }, 94 | "Action": [ 95 | "sts:AssumeRole" 96 | ] 97 | } 98 | ] 99 | } 100 | } 101 | }, 102 | "lambdaexecutionpolicy": { 103 | "DependsOn": [ 104 | "LambdaExecutionRole" 105 | ], 106 | "Type": "AWS::IAM::Policy", 107 | "Properties": { 108 | "PolicyName": "lambda-execution-policy", 109 | "Roles": [ 110 | { 111 | "Ref": "LambdaExecutionRole" 112 | } 113 | ], 114 | "PolicyDocument": { 115 | "Version": "2012-10-17", 116 | "Statement": [ 117 | { 118 | "Effect": "Allow", 119 | "Action": [ 120 | "logs:CreateLogGroup", 121 | "logs:CreateLogStream", 122 | "logs:PutLogEvents" 123 | ], 124 | "Resource": { 125 | "Fn::Sub": [ 126 | "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*", 127 | { 128 | "region": { 129 | "Ref": "AWS::Region" 130 | }, 131 | "account": { 132 | "Ref": "AWS::AccountId" 133 | }, 134 | "lambda": { 135 | "Ref": "LambdaFunction" 136 | } 137 | } 138 | ] 139 | } 140 | } 141 | ] 142 | } 143 | } 144 | } 145 | }, 146 | "Outputs": { 147 | "Name": { 148 | "Value": { 149 | "Ref": "LambdaFunction" 150 | } 151 | }, 152 | "Arn": { 153 | "Value": { 154 | "Fn::GetAtt": [ 155 | "LambdaFunction", 156 | "Arn" 157 | ] 158 | } 159 | }, 160 | "Region": { 161 | "Value": { 162 | "Ref": "AWS::Region" 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /amplify/backend/awscloudformation/nested-cloudformation-stack.yml: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Root stack for the Amplify AWS CloudFormation provider", 4 | "Parameters": { 5 | "DeploymentBucketName": { 6 | "Description": "Name of the common deployment bucket provided by the parent stack", 7 | "Type": "String", 8 | "Default": "DeploymentBucket" 9 | }, 10 | "AuthRoleName": { 11 | "Type": "String", 12 | "Default": "AuthRoleName" 13 | }, 14 | "UnauthRoleName": { 15 | "Type": "String", 16 | "Default": "UnauthRoleName" 17 | } 18 | }, 19 | "Resources": { 20 | "DeploymentBucket": { 21 | "Type": "AWS::S3::Bucket", 22 | "DeletionPolicy": "Retain", 23 | "Properties": { 24 | "BucketName": { 25 | "Ref": "DeploymentBucketName" 26 | } 27 | } 28 | }, 29 | "AuthRole": { 30 | "Type": "AWS::IAM::Role", 31 | "Properties": { 32 | "RoleName": { 33 | "Ref": "AuthRoleName" 34 | }, 35 | "AssumeRolePolicyDocument": { 36 | "Version": "2012-10-17", 37 | "Statement": [ 38 | { 39 | "Sid": "", 40 | "Effect": "Allow", 41 | "Principal": { 42 | "Federated": "cognito-identity.amazonaws.com" 43 | }, 44 | "Action": "sts:AssumeRoleWithWebIdentity", 45 | "Condition": { 46 | "ForAnyValue:StringLike": { 47 | "cognito-identity.amazonaws.com:amr": "authenticated" 48 | } 49 | } 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | "UnauthRole": { 56 | "Type": "AWS::IAM::Role", 57 | "Properties": { 58 | "RoleName": { 59 | "Ref": "UnauthRoleName" 60 | }, 61 | "AssumeRolePolicyDocument": { 62 | "Version": "2012-10-17", 63 | "Statement": [ 64 | { 65 | "Sid": "", 66 | "Effect": "Allow", 67 | "Principal": { 68 | "Federated": "cognito-identity.amazonaws.com" 69 | }, 70 | "Action": "sts:AssumeRoleWithWebIdentity", 71 | "Condition": { 72 | "ForAnyValue:StringLike": { 73 | "cognito-identity.amazonaws.com:amr": "unauthenticated" 74 | } 75 | } 76 | } 77 | ] 78 | } 79 | } 80 | }, 81 | "apiamplifynestedsample": { 82 | "Type": "AWS::CloudFormation::Stack", 83 | "Properties": { 84 | "TemplateURL": "https://s3.amazonaws.com/amplifynestedsample-20190118130523-deployment/amplify-cfn-templates/api/cloudformation-template.json", 85 | "Parameters": { 86 | "AppSyncApiName": "amplifynestedsample", 87 | "S3DeploymentBucket": "amplifynestedsample-20190118130523-deployment", 88 | "S3DeploymentRootKey": "amplify-appsync-files/1547850263074", 89 | "env": "test" 90 | } 91 | } 92 | }, 93 | "functionechofunction": { 94 | "Type": "AWS::CloudFormation::Stack", 95 | "Properties": { 96 | "TemplateURL": "https://s3.amazonaws.com/amplifynestedsample-20190118130523-deployment/amplify-cfn-templates/function/pongfunction-cloudformation-template.json", 97 | "Parameters": { 98 | "env": "test" 99 | } 100 | } 101 | } 102 | }, 103 | "Outputs": { 104 | "Region": { 105 | "Description": "CloudFormation provider root stack Region", 106 | "Value": { 107 | "Ref": "AWS::Region" 108 | }, 109 | "Export": { 110 | "Name": { 111 | "Fn::Sub": "${AWS::StackName}-Region" 112 | } 113 | } 114 | }, 115 | "StackName": { 116 | "Description": "CloudFormation provider root stack ID", 117 | "Value": { 118 | "Ref": "AWS::StackName" 119 | }, 120 | "Export": { 121 | "Name": { 122 | "Fn::Sub": "${AWS::StackName}-StackName" 123 | } 124 | } 125 | }, 126 | "StackId": { 127 | "Description": "CloudFormation provider root stack name", 128 | "Value": { 129 | "Ref": "AWS::StackId" 130 | }, 131 | "Export": { 132 | "Name": { 133 | "Fn::Sub": "${AWS::StackName}-StackId" 134 | } 135 | } 136 | }, 137 | "DeploymentBucketName": { 138 | "Description": "CloudFormation provider root stack deployment bucket name", 139 | "Value": { 140 | "Ref": "DeploymentBucketName" 141 | }, 142 | "Export": { 143 | "Name": { 144 | "Fn::Sub": "${AWS::StackName}-DeploymentBucketName" 145 | } 146 | } 147 | }, 148 | "AuthRoleArn": { 149 | "Value": { 150 | "Fn::GetAtt": [ 151 | "AuthRole", 152 | "Arn" 153 | ] 154 | } 155 | }, 156 | "UnauthRoleArn": { 157 | "Value": { 158 | "Fn::GetAtt": [ 159 | "UnauthRole", 160 | "Arn" 161 | ] 162 | } 163 | }, 164 | "AuthRoleName": { 165 | "Value": { 166 | "Ref": "AuthRole" 167 | } 168 | }, 169 | "UnauthRoleName": { 170 | "Value": { 171 | "Ref": "UnauthRole" 172 | } 173 | } 174 | } 175 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/schema.graphql: -------------------------------------------------------------------------------- 1 | type Todo { 2 | id: ID! 3 | name: String! 4 | description: String 5 | comments(filter: ModelCommentFilterInput, sortDirection: ModelSortDirection, limit: Int, nextToken: String): ModelCommentConnection 6 | location: Location 7 | createdAt: AWSDateTime 8 | } 9 | 10 | type Comment { 11 | id: ID! 12 | content: String 13 | todo: Todo 14 | createdAt: AWSDateTime 15 | } 16 | 17 | type Location { 18 | lat: Float 19 | lon: Float 20 | } 21 | 22 | input LocationInput { 23 | lat: Float 24 | lon: Float 25 | } 26 | 27 | type CommentConnection { 28 | items: [Comment] 29 | nextToken: String 30 | } 31 | 32 | type TodoConnection { 33 | items: [Todo] 34 | total: Int 35 | nextToken: String 36 | } 37 | 38 | type Query { 39 | commentsForTodo(todoId: ID!, limit: Int, nextToken: String): CommentConnection 40 | echo(msg: String): String 41 | nearbyTodos(location: LocationInput!, km: Int): TodoConnection 42 | getTodo(id: ID!): Todo 43 | listTodos(filter: ModelTodoFilterInput, limit: Int, nextToken: String): ModelTodoConnection 44 | getComment(id: ID!): Comment 45 | listComments(filter: ModelCommentFilterInput, limit: Int, nextToken: String): ModelCommentConnection 46 | searchTodos(filter: SearchableTodoFilterInput, sort: SearchableTodoSortInput, limit: Int, nextToken: Int): SearchableTodoConnection 47 | } 48 | 49 | enum ModelSortDirection { 50 | ASC 51 | DESC 52 | } 53 | 54 | type ModelTodoConnection { 55 | items: [Todo] 56 | nextToken: String 57 | } 58 | 59 | input ModelStringFilterInput { 60 | ne: String 61 | eq: String 62 | le: String 63 | lt: String 64 | ge: String 65 | gt: String 66 | contains: String 67 | notContains: String 68 | between: [String] 69 | beginsWith: String 70 | } 71 | 72 | input ModelIDFilterInput { 73 | ne: ID 74 | eq: ID 75 | le: ID 76 | lt: ID 77 | ge: ID 78 | gt: ID 79 | contains: ID 80 | notContains: ID 81 | between: [ID] 82 | beginsWith: ID 83 | } 84 | 85 | input ModelIntFilterInput { 86 | ne: Int 87 | eq: Int 88 | le: Int 89 | lt: Int 90 | ge: Int 91 | gt: Int 92 | contains: Int 93 | notContains: Int 94 | between: [Int] 95 | } 96 | 97 | input ModelFloatFilterInput { 98 | ne: Float 99 | eq: Float 100 | le: Float 101 | lt: Float 102 | ge: Float 103 | gt: Float 104 | contains: Float 105 | notContains: Float 106 | between: [Float] 107 | } 108 | 109 | input ModelBooleanFilterInput { 110 | ne: Boolean 111 | eq: Boolean 112 | } 113 | 114 | input ModelTodoFilterInput { 115 | id: ModelIDFilterInput 116 | name: ModelStringFilterInput 117 | description: ModelStringFilterInput 118 | createdAt: ModelStringFilterInput 119 | and: [ModelTodoFilterInput] 120 | or: [ModelTodoFilterInput] 121 | not: ModelTodoFilterInput 122 | } 123 | 124 | input CreateTodoInput { 125 | id: ID 126 | name: String! 127 | description: String 128 | location: LocationInput 129 | createdAt: AWSDateTime 130 | } 131 | 132 | input UpdateTodoInput { 133 | id: ID! 134 | name: String 135 | description: String 136 | location: LocationInput 137 | createdAt: AWSDateTime 138 | } 139 | 140 | input DeleteTodoInput { 141 | id: ID 142 | } 143 | 144 | type Mutation { 145 | createTodo(input: CreateTodoInput!): Todo 146 | updateTodo(input: UpdateTodoInput!): Todo 147 | deleteTodo(input: DeleteTodoInput!): Todo 148 | createComment(input: CreateCommentInput!): Comment 149 | updateComment(input: UpdateCommentInput!): Comment 150 | deleteComment(input: DeleteCommentInput!): Comment 151 | } 152 | 153 | type Subscription { 154 | onCreateTodo: Todo @aws_subscribe(mutations: ["createTodo"]) 155 | onUpdateTodo: Todo @aws_subscribe(mutations: ["updateTodo"]) 156 | onDeleteTodo: Todo @aws_subscribe(mutations: ["deleteTodo"]) 157 | onCreateComment: Comment @aws_subscribe(mutations: ["createComment"]) 158 | onUpdateComment: Comment @aws_subscribe(mutations: ["updateComment"]) 159 | onDeleteComment: Comment @aws_subscribe(mutations: ["deleteComment"]) 160 | } 161 | 162 | type ModelCommentConnection { 163 | items: [Comment] 164 | nextToken: String 165 | } 166 | 167 | input ModelCommentFilterInput { 168 | id: ModelIDFilterInput 169 | content: ModelStringFilterInput 170 | createdAt: ModelStringFilterInput 171 | and: [ModelCommentFilterInput] 172 | or: [ModelCommentFilterInput] 173 | not: ModelCommentFilterInput 174 | } 175 | 176 | input CreateCommentInput { 177 | id: ID 178 | content: String 179 | createdAt: AWSDateTime 180 | commentTodoId: ID 181 | } 182 | 183 | input UpdateCommentInput { 184 | id: ID! 185 | content: String 186 | createdAt: AWSDateTime 187 | commentTodoId: ID 188 | } 189 | 190 | input DeleteCommentInput { 191 | id: ID 192 | } 193 | 194 | input SearchableStringFilterInput { 195 | ne: String 196 | eq: String 197 | match: String 198 | matchPhrase: String 199 | matchPhrasePrefix: String 200 | multiMatch: String 201 | exists: Boolean 202 | wildcard: String 203 | regexp: String 204 | } 205 | 206 | input SearchableIDFilterInput { 207 | ne: ID 208 | eq: ID 209 | match: ID 210 | matchPhrase: ID 211 | matchPhrasePrefix: ID 212 | multiMatch: ID 213 | exists: Boolean 214 | wildcard: ID 215 | regexp: ID 216 | } 217 | 218 | input SearchableIntFilterInput { 219 | ne: Int 220 | gt: Int 221 | lt: Int 222 | gte: Int 223 | lte: Int 224 | eq: Int 225 | range: [Int] 226 | } 227 | 228 | input SearchableFloatFilterInput { 229 | ne: Float 230 | gt: Float 231 | lt: Float 232 | gte: Float 233 | lte: Float 234 | eq: Float 235 | range: [Float] 236 | } 237 | 238 | input SearchableBooleanFilterInput { 239 | eq: Boolean 240 | ne: Boolean 241 | } 242 | 243 | input SearchableTodoFilterInput { 244 | id: SearchableIDFilterInput 245 | name: SearchableStringFilterInput 246 | description: SearchableStringFilterInput 247 | createdAt: SearchableStringFilterInput 248 | and: [SearchableTodoFilterInput] 249 | or: [SearchableTodoFilterInput] 250 | not: SearchableTodoFilterInput 251 | } 252 | 253 | enum SearchableSortDirection { 254 | asc 255 | desc 256 | } 257 | 258 | enum SearchableTodoSortableFields { 259 | id 260 | name 261 | description 262 | createdAt 263 | } 264 | 265 | input SearchableTodoSortInput { 266 | field: SearchableTodoSortableFields 267 | direction: SearchableSortDirection 268 | } 269 | 270 | type SearchableTodoConnection { 271 | items: [Todo] 272 | nextToken: String 273 | } 274 | -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/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 | "QueryCommentsForTodoResolver": { 31 | "Type": "AWS::AppSync::Resolver", 32 | "Properties": { 33 | "ApiId": { 34 | "Ref": "AppSyncApiId" 35 | }, 36 | "DataSourceName": "CommentTable", 37 | "TypeName": "Query", 38 | "FieldName": "commentsForTodo", 39 | "RequestMappingTemplateS3Location": { 40 | "Fn::Sub": [ 41 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.commentsForTodo.req.vtl", 42 | { 43 | "S3DeploymentBucket": { 44 | "Ref": "S3DeploymentBucket" 45 | }, 46 | "S3DeploymentRootKey": { 47 | "Ref": "S3DeploymentRootKey" 48 | } 49 | } 50 | ] 51 | }, 52 | "ResponseMappingTemplateS3Location": { 53 | "Fn::Sub": [ 54 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.commentsForTodo.res.vtl", 55 | { 56 | "S3DeploymentBucket": { 57 | "Ref": "S3DeploymentBucket" 58 | }, 59 | "S3DeploymentRootKey": { 60 | "Ref": "S3DeploymentRootKey" 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | }, 67 | "QueryEchoResolver": { 68 | "Type": "AWS::AppSync::Resolver", 69 | "Properties": { 70 | "ApiId": { 71 | "Ref": "AppSyncApiId" 72 | }, 73 | "DataSourceName": { 74 | "Fn::GetAtt": [ 75 | "EchoLambdaDataSource", 76 | "Name" 77 | ] 78 | }, 79 | "TypeName": "Query", 80 | "FieldName": "echo", 81 | "RequestMappingTemplateS3Location": { 82 | "Fn::Sub": [ 83 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.echo.req.vtl", 84 | { 85 | "S3DeploymentBucket": { 86 | "Ref": "S3DeploymentBucket" 87 | }, 88 | "S3DeploymentRootKey": { 89 | "Ref": "S3DeploymentRootKey" 90 | } 91 | } 92 | ] 93 | }, 94 | "ResponseMappingTemplateS3Location": { 95 | "Fn::Sub": [ 96 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.echo.res.vtl", 97 | { 98 | "S3DeploymentBucket": { 99 | "Ref": "S3DeploymentBucket" 100 | }, 101 | "S3DeploymentRootKey": { 102 | "Ref": "S3DeploymentRootKey" 103 | } 104 | } 105 | ] 106 | } 107 | } 108 | }, 109 | "EchoLambdaDataSource": { 110 | "Type": "AWS::AppSync::DataSource", 111 | "Properties": { 112 | "Name": "EchoFunction", 113 | "Type": "AWS_LAMBDA", 114 | "ApiId": { "Ref": "AppSyncApiId" }, 115 | "ServiceRoleArn": { 116 | "Fn::GetAtt": [ 117 | "EchoLambdaDataSourceRole", 118 | "Arn" 119 | ] 120 | }, 121 | "LambdaConfig": { 122 | "LambdaFunctionArn": { 123 | "Fn::Sub": [ 124 | "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:echofunction-${env}", 125 | { 126 | "env": { 127 | "Ref": "env" 128 | } 129 | } 130 | ] 131 | } 132 | } 133 | } 134 | }, 135 | "EchoLambdaDataSourceRole": { 136 | "Type": "AWS::IAM::Role", 137 | "Properties": { 138 | "RoleName": "EchoLambdaDataSourceRole", 139 | "AssumeRolePolicyDocument": { 140 | "Version": "2012-10-17", 141 | "Statement": [ 142 | { 143 | "Effect": "Allow", 144 | "Principal": { 145 | "Service": "appsync.amazonaws.com" 146 | }, 147 | "Action": "sts:AssumeRole" 148 | } 149 | ] 150 | }, 151 | "Policies": [ 152 | { 153 | "PolicyName": "InvokeLambdaFunction", 154 | "PolicyDocument": { 155 | "Version": "2012-10-17", 156 | "Statement": [ 157 | { 158 | "Effect": "Allow", 159 | "Action": [ 160 | "lambda:invokeFunction" 161 | ], 162 | "Resource": [ 163 | { 164 | "Fn::Sub": [ 165 | "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:echofunction-${env}", 166 | { 167 | "env": { 168 | "Ref": "env" 169 | } 170 | } 171 | ] 172 | } 173 | ] 174 | } 175 | ] 176 | } 177 | } 178 | ] 179 | } 180 | }, 181 | "QueryNearbyTodos": { 182 | "Type": "AWS::AppSync::Resolver", 183 | "Properties": { 184 | "ApiId": { 185 | "Ref": "AppSyncApiId" 186 | }, 187 | "DataSourceName": "ElasticsearchDomain", 188 | "TypeName": "Query", 189 | "FieldName": "nearbyTodos", 190 | "RequestMappingTemplateS3Location": { 191 | "Fn::Sub": [ 192 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.nearbyTodos.req.vtl", 193 | { 194 | "S3DeploymentBucket": { 195 | "Ref": "S3DeploymentBucket" 196 | }, 197 | "S3DeploymentRootKey": { 198 | "Ref": "S3DeploymentRootKey" 199 | } 200 | } 201 | ] 202 | }, 203 | "ResponseMappingTemplateS3Location": { 204 | "Fn::Sub": [ 205 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.nearbyTodos.res.vtl", 206 | { 207 | "S3DeploymentBucket": { 208 | "Ref": "S3DeploymentBucket" 209 | }, 210 | "S3DeploymentRootKey": { 211 | "Ref": "S3DeploymentRootKey" 212 | } 213 | } 214 | ] 215 | } 216 | } 217 | } 218 | }, 219 | "Conditions": { 220 | "HasEnvironmentParameter": { 221 | "Fn::Not": [ 222 | { 223 | "Fn::Equals": [ 224 | { 225 | "Ref": "env" 226 | }, 227 | "NONE" 228 | ] 229 | } 230 | ] 231 | } 232 | }, 233 | "Outputs": { 234 | "EmptyOutput": { 235 | "Description": "An empty output. You may delete this if you have at least one resource above.", 236 | "Value": "" 237 | } 238 | } 239 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/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 | "QueryCommentsForTodoResolver": { 31 | "Type": "AWS::AppSync::Resolver", 32 | "Properties": { 33 | "ApiId": { 34 | "Ref": "AppSyncApiId" 35 | }, 36 | "DataSourceName": "CommentTable", 37 | "TypeName": "Query", 38 | "FieldName": "commentsForTodo", 39 | "RequestMappingTemplateS3Location": { 40 | "Fn::Sub": [ 41 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.commentsForTodo.req.vtl", 42 | { 43 | "S3DeploymentBucket": { 44 | "Ref": "S3DeploymentBucket" 45 | }, 46 | "S3DeploymentRootKey": { 47 | "Ref": "S3DeploymentRootKey" 48 | } 49 | } 50 | ] 51 | }, 52 | "ResponseMappingTemplateS3Location": { 53 | "Fn::Sub": [ 54 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.commentsForTodo.res.vtl", 55 | { 56 | "S3DeploymentBucket": { 57 | "Ref": "S3DeploymentBucket" 58 | }, 59 | "S3DeploymentRootKey": { 60 | "Ref": "S3DeploymentRootKey" 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | }, 67 | "QueryEchoResolver": { 68 | "Type": "AWS::AppSync::Resolver", 69 | "Properties": { 70 | "ApiId": { 71 | "Ref": "AppSyncApiId" 72 | }, 73 | "DataSourceName": { 74 | "Fn::GetAtt": [ 75 | "EchoLambdaDataSource", 76 | "Name" 77 | ] 78 | }, 79 | "TypeName": "Query", 80 | "FieldName": "echo", 81 | "RequestMappingTemplateS3Location": { 82 | "Fn::Sub": [ 83 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.echo.req.vtl", 84 | { 85 | "S3DeploymentBucket": { 86 | "Ref": "S3DeploymentBucket" 87 | }, 88 | "S3DeploymentRootKey": { 89 | "Ref": "S3DeploymentRootKey" 90 | } 91 | } 92 | ] 93 | }, 94 | "ResponseMappingTemplateS3Location": { 95 | "Fn::Sub": [ 96 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.echo.res.vtl", 97 | { 98 | "S3DeploymentBucket": { 99 | "Ref": "S3DeploymentBucket" 100 | }, 101 | "S3DeploymentRootKey": { 102 | "Ref": "S3DeploymentRootKey" 103 | } 104 | } 105 | ] 106 | } 107 | } 108 | }, 109 | "EchoLambdaDataSource": { 110 | "Type": "AWS::AppSync::DataSource", 111 | "Properties": { 112 | "Name": "EchoFunction", 113 | "Type": "AWS_LAMBDA", 114 | "ApiId": { 115 | "Ref": "AppSyncApiId" 116 | }, 117 | "ServiceRoleArn": { 118 | "Fn::GetAtt": [ 119 | "EchoLambdaDataSourceRole", 120 | "Arn" 121 | ] 122 | }, 123 | "LambdaConfig": { 124 | "LambdaFunctionArn": { 125 | "Fn::Sub": [ 126 | "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:echofunction-${env}", 127 | { 128 | "env": { 129 | "Ref": "env" 130 | } 131 | } 132 | ] 133 | } 134 | } 135 | } 136 | }, 137 | "EchoLambdaDataSourceRole": { 138 | "Type": "AWS::IAM::Role", 139 | "Properties": { 140 | "RoleName": "EchoLambdaDataSourceRole", 141 | "AssumeRolePolicyDocument": { 142 | "Version": "2012-10-17", 143 | "Statement": [ 144 | { 145 | "Effect": "Allow", 146 | "Principal": { 147 | "Service": "appsync.amazonaws.com" 148 | }, 149 | "Action": "sts:AssumeRole" 150 | } 151 | ] 152 | }, 153 | "Policies": [ 154 | { 155 | "PolicyName": "InvokeLambdaFunction", 156 | "PolicyDocument": { 157 | "Version": "2012-10-17", 158 | "Statement": [ 159 | { 160 | "Effect": "Allow", 161 | "Action": [ 162 | "lambda:invokeFunction" 163 | ], 164 | "Resource": [ 165 | { 166 | "Fn::Sub": [ 167 | "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:echofunction-${env}", 168 | { 169 | "env": { 170 | "Ref": "env" 171 | } 172 | } 173 | ] 174 | } 175 | ] 176 | } 177 | ] 178 | } 179 | } 180 | ] 181 | } 182 | }, 183 | "QueryNearbyTodos": { 184 | "Type": "AWS::AppSync::Resolver", 185 | "Properties": { 186 | "ApiId": { 187 | "Ref": "AppSyncApiId" 188 | }, 189 | "DataSourceName": "ElasticsearchDomain", 190 | "TypeName": "Query", 191 | "FieldName": "nearbyTodos", 192 | "RequestMappingTemplateS3Location": { 193 | "Fn::Sub": [ 194 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.nearbyTodos.req.vtl", 195 | { 196 | "S3DeploymentBucket": { 197 | "Ref": "S3DeploymentBucket" 198 | }, 199 | "S3DeploymentRootKey": { 200 | "Ref": "S3DeploymentRootKey" 201 | } 202 | } 203 | ] 204 | }, 205 | "ResponseMappingTemplateS3Location": { 206 | "Fn::Sub": [ 207 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.nearbyTodos.res.vtl", 208 | { 209 | "S3DeploymentBucket": { 210 | "Ref": "S3DeploymentBucket" 211 | }, 212 | "S3DeploymentRootKey": { 213 | "Ref": "S3DeploymentRootKey" 214 | } 215 | } 216 | ] 217 | } 218 | } 219 | } 220 | }, 221 | "Conditions": { 222 | "HasEnvironmentParameter": { 223 | "Fn::Not": [ 224 | { 225 | "Fn::Equals": [ 226 | { 227 | "Ref": "env" 228 | }, 229 | "NONE" 230 | ] 231 | } 232 | ] 233 | } 234 | }, 235 | "Outputs": { 236 | "EmptyOutput": { 237 | "Description": "An empty output. You may delete this if you have at least one resource above.", 238 | "Value": "" 239 | } 240 | } 241 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/stacks/ConnectionStack.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiName": { 7 | "Type": "String", 8 | "Description": "The name of the AppSync API", 9 | "Default": "AppSyncSimpleTransform" 10 | }, 11 | "ElasticsearchAccessIAMRoleName": { 12 | "Type": "String", 13 | "Description": "The name of the IAM role assumed by AppSync for Elasticsearch.", 14 | "Default": "AppSyncElasticsearchAccess" 15 | }, 16 | "ElasticsearchStreamingLambdaHandlerName": { 17 | "Type": "String", 18 | "Description": "The name of the lambda handler.", 19 | "Default": "python_streaming_function.lambda_handler" 20 | }, 21 | "ElasticsearchStreamingLambdaRuntime": { 22 | "Type": "String", 23 | "Description": "The lambda runtime (https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-Runtime)", 24 | "Default": "python3.6" 25 | }, 26 | "ElasticsearchStreamingFunctionName": { 27 | "Type": "String", 28 | "Description": "The name of the streaming lambda function.", 29 | "Default": "DdbToEsFn" 30 | }, 31 | "ElasticsearchStreamingIAMRoleName": { 32 | "Type": "String", 33 | "Description": "The name of the streaming lambda function IAM role.", 34 | "Default": "SearchableLambdaIAMRole" 35 | }, 36 | "ElasticsearchDebugStreamingLambda": { 37 | "Type": "Number", 38 | "Description": "Enable debug logs for the Dynamo -> ES streaming lambda.", 39 | "Default": 1, 40 | "AllowedValues": [ 41 | 0, 42 | 1 43 | ] 44 | }, 45 | "ElasticsearchInstanceCount": { 46 | "Type": "Number", 47 | "Description": "The number of instances to launch into the Elasticsearch domain.", 48 | "Default": 1 49 | }, 50 | "ElasticsearchDomainName": { 51 | "Type": "String", 52 | "Description": "The name of the Elasticsearch domain.", 53 | "Default": "appsync-elasticsearch-domain", 54 | "AllowedPattern": "^[a-z][a-z0-9-]*$", 55 | "MinLength": 1, 56 | "MaxLength": 28 57 | }, 58 | "ElasticsearchInstanceType": { 59 | "Type": "String", 60 | "Description": "The type of instance to launch into the Elasticsearch domain.", 61 | "Default": "t2.small.elasticsearch", 62 | "AllowedValues": [ 63 | "t2.small.elasticsearch", 64 | "t2.medium.elasticsearch", 65 | "c4.large.elasticsearch", 66 | "c4.xlarge.elasticsearch", 67 | "c4.2xlarge.elasticsearch", 68 | "c4.4xlarge.elasticsearch", 69 | "c4.8xlarge.elasticsearch", 70 | "m3.medium.elasticsearch", 71 | "m3.large.elasticsearch", 72 | "m3.xlarge.elasticsearch", 73 | "m3.2xlarge.elasticsearch", 74 | "m4.large.elasticsearch", 75 | "m4.xlarge.elasticsearch", 76 | "m4.2xlarge.elasticsearch", 77 | "m4.4xlarge.elasticsearch", 78 | "m4.10xlarge.elasticsearch", 79 | "r3.large.elasticsearch", 80 | "r3.xlarge.elasticsearch", 81 | "r3.2xlarge.elasticsearch", 82 | "r3.4xlarge.elasticsearch", 83 | "r3.8xlarge.elasticsearch", 84 | "r4.large.elasticsearch", 85 | "r4.xlarge.elasticsearch", 86 | "r4.2xlarge.elasticsearch", 87 | "r4.4xlarge.elasticsearch", 88 | "r4.8xlarge.elasticsearch", 89 | "r4.16xlarge.elasticsearch", 90 | "i2.xlarge.elasticsearch", 91 | "i2.2xlarge.elasticsearch", 92 | "i3.large.elasticsearch", 93 | "i3.xlarge.elasticsearch", 94 | "i3.2xlarge.elasticsearch", 95 | "i3.4xlarge.elasticsearch", 96 | "i3.8xlarge.elasticsearch", 97 | "i3.16xlarge.elasticsearch" 98 | ] 99 | }, 100 | "ElasticsearchEBSVolumeGB": { 101 | "Type": "Number", 102 | "Description": "The size in GB of the EBS volumes that contain our data.", 103 | "Default": 20 104 | }, 105 | "env": { 106 | "Type": "String", 107 | "Description": "The environment name. e.g. Dev, Test, or Production", 108 | "Default": "NONE" 109 | }, 110 | "S3DeploymentBucket": { 111 | "Type": "String", 112 | "Description": "The S3 bucket containing all deployment assets for the project." 113 | }, 114 | "S3DeploymentRootKey": { 115 | "Type": "String", 116 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root of the deployment directory." 117 | }, 118 | "AppSyncApiId": { 119 | "Type": "String", 120 | "Description": "The id of the AppSync API associated with this project." 121 | }, 122 | "GetAttGraphQLAPIApiId": { 123 | "Type": "String", 124 | "Description": "Auto-generated parameter that forwards Fn.GetAtt(GraphQLAPI, ApiId) through to nested stacks." 125 | } 126 | }, 127 | "Resources": { 128 | "TodocommentsResolver": { 129 | "Type": "AWS::AppSync::Resolver", 130 | "Properties": { 131 | "ApiId": { 132 | "Ref": "GetAttGraphQLAPIApiId" 133 | }, 134 | "DataSourceName": { 135 | "Fn::ImportValue": { 136 | "Fn::Join": [ 137 | ":", 138 | [ 139 | { 140 | "Ref": "AppSyncApiId" 141 | }, 142 | "GetAtt", 143 | "CommentDataSource", 144 | "Name" 145 | ] 146 | ] 147 | } 148 | }, 149 | "FieldName": "comments", 150 | "TypeName": "Todo", 151 | "RequestMappingTemplateS3Location": { 152 | "Fn::Sub": [ 153 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 154 | { 155 | "S3DeploymentBucket": { 156 | "Ref": "S3DeploymentBucket" 157 | }, 158 | "S3DeploymentRootKey": { 159 | "Ref": "S3DeploymentRootKey" 160 | }, 161 | "ResolverFileName": { 162 | "Fn::Join": [ 163 | ".", 164 | [ 165 | "Todo", 166 | "comments", 167 | "req", 168 | "vtl" 169 | ] 170 | ] 171 | } 172 | } 173 | ] 174 | }, 175 | "ResponseMappingTemplateS3Location": { 176 | "Fn::Sub": [ 177 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 178 | { 179 | "S3DeploymentBucket": { 180 | "Ref": "S3DeploymentBucket" 181 | }, 182 | "S3DeploymentRootKey": { 183 | "Ref": "S3DeploymentRootKey" 184 | }, 185 | "ResolverFileName": { 186 | "Fn::Join": [ 187 | ".", 188 | [ 189 | "Todo", 190 | "comments", 191 | "res", 192 | "vtl" 193 | ] 194 | ] 195 | } 196 | } 197 | ] 198 | } 199 | } 200 | }, 201 | "CommenttodoResolver": { 202 | "Type": "AWS::AppSync::Resolver", 203 | "Properties": { 204 | "ApiId": { 205 | "Ref": "GetAttGraphQLAPIApiId" 206 | }, 207 | "DataSourceName": { 208 | "Fn::ImportValue": { 209 | "Fn::Join": [ 210 | ":", 211 | [ 212 | { 213 | "Ref": "AppSyncApiId" 214 | }, 215 | "GetAtt", 216 | "TodoDataSource", 217 | "Name" 218 | ] 219 | ] 220 | } 221 | }, 222 | "FieldName": "todo", 223 | "TypeName": "Comment", 224 | "RequestMappingTemplateS3Location": { 225 | "Fn::Sub": [ 226 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 227 | { 228 | "S3DeploymentBucket": { 229 | "Ref": "S3DeploymentBucket" 230 | }, 231 | "S3DeploymentRootKey": { 232 | "Ref": "S3DeploymentRootKey" 233 | }, 234 | "ResolverFileName": { 235 | "Fn::Join": [ 236 | ".", 237 | [ 238 | "Comment", 239 | "todo", 240 | "req", 241 | "vtl" 242 | ] 243 | ] 244 | } 245 | } 246 | ] 247 | }, 248 | "ResponseMappingTemplateS3Location": { 249 | "Fn::Sub": [ 250 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 251 | { 252 | "S3DeploymentBucket": { 253 | "Ref": "S3DeploymentBucket" 254 | }, 255 | "S3DeploymentRootKey": { 256 | "Ref": "S3DeploymentRootKey" 257 | }, 258 | "ResolverFileName": { 259 | "Fn::Join": [ 260 | ".", 261 | [ 262 | "Comment", 263 | "todo", 264 | "res", 265 | "vtl" 266 | ] 267 | ] 268 | } 269 | } 270 | ] 271 | } 272 | } 273 | } 274 | }, 275 | "Outputs": {}, 276 | "Conditions": { 277 | "HasEnvironmentParameter": { 278 | "Fn::Not": [ 279 | { 280 | "Fn::Equals": [ 281 | { 282 | "Ref": "env" 283 | }, 284 | "NONE" 285 | ] 286 | } 287 | ] 288 | } 289 | } 290 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amplify CLI Nested Stack Sample 2 | 3 | This project can serve as a sample for getting started with the new nested stacks implementation of the Amplify CLI API category. 4 | 5 | # Setup 6 | 7 | This project only works when using the **nested-stacks** branch of this repository: 8 | 9 | https://github.com/mikeparisstuff/amplify-cli/tree/nested-stacks 10 | 11 | To get started, clone that repo and then run `npm run setup-dev` from the root 12 | of the project directory. When that command finishes, the `amplify` command on 13 | your machine will now map to the nested-stacks build. 14 | 15 | # Create custom resolvers and other resources 16 | 17 | The Amplify CLI exposes the GraphQL Transform libraries to help create APIs with common 18 | patterns and best practices baked in but it also provides number of escape hatches for 19 | those situations where you might need a bit more control. Here are a few common use cases 20 | you might find useful. 21 | 22 | **Overwrite a resolver generated by the GraphQL Transform** 23 | 24 | Let's say you have a simple *schema.graphql*... 25 | 26 | ```graphql 27 | type Todo @model { 28 | id: ID! 29 | name: String! 30 | description: String 31 | } 32 | ``` 33 | 34 | and you want to change the behavior of request mapping template of the the *Query.getTodo* resolver that will be generated when the project compiles. To do this you would create a file named `Query.getTodo.req.vtl` in the *resolvers* directory of your API project. The next time you run `amplify push` or `amplify api gql-compile`, your resolver template will be used instead of the auto-generated template. You may similarly create a `Query.getTodo.res.vtl` file to change the behavior of the resolver's response mapping template. 35 | 36 | **Add a custom resolver that targets a DynamoDB table from @model** 37 | 38 | This is useful if you want to write a more specific query against a DynamoDB table that was created by *@model*. For example, assume you had this schema with two *@model* types and a pair of *@connection* directives. 39 | 40 | ```graphql 41 | type Todo @model { 42 | id: ID! 43 | name: String! 44 | description: String 45 | comments: [Todo] @connection(name: "TodoComments") 46 | } 47 | type Comment @model { 48 | id: ID! 49 | content: String 50 | todo: Todo @connection(name: "TodoComments") 51 | } 52 | ``` 53 | 54 | This schema will generate resolvers for *Query.getTodo*, *Query.listTodos*, *Query.getComment*, and *Query.listComments* at the top level as well as for *Todo.comments*, and *Comment.todo* to implement the *@connection*. Under the hood, the transform will create a GlobalSecondaryIndex on the Comment table in DynamoDB but it will not generate a top level query field that queries the GSI because you can fetch the comments for a given todo object via the *Query.getTodo.comments* query path. If you want to fetch all comments for a todo object via a top level query field i.e. *Query.commentsForTodo* then do the following: 55 | 56 | 1. Add the desired field to your *schema.graphql*. 57 | 58 | ```graphql 59 | // ... Todo and Comment types from above 60 | 61 | type CommentConnection { 62 | items: [Comment] 63 | nextToken: String 64 | } 65 | type Query { 66 | commentsForTodo(todoId: ID!, limit: Int, nextToken: String): CommentConnection 67 | } 68 | ``` 69 | 70 | 2. Add a resolver resource to a stack in the *stacks/* directory. 71 | 72 | ```json 73 | { 74 | // ... The rest of the template 75 | "Resources": { 76 | "QueryCommentsForTodoResolver": { 77 | "Type": "AWS::AppSync::Resolver", 78 | "Properties": { 79 | "ApiId": { 80 | "Ref": "AppSyncApiId" 81 | }, 82 | "DataSourceName": "CommentTable", 83 | "TypeName": "Query", 84 | "FieldName": "commentsForTodo", 85 | "RequestMappingTemplateS3Location": { 86 | "Fn::Sub": [ 87 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.commentsForTodo.req.vtl", 88 | { 89 | "S3DeploymentBucket": { 90 | "Ref": "S3DeploymentBucket" 91 | }, 92 | "S3DeploymentRootKey": { 93 | "Ref": "S3DeploymentRootKey" 94 | } 95 | } 96 | ] 97 | }, 98 | "ResponseMappingTemplateS3Location": { 99 | "Fn::Sub": [ 100 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.commentsForTodo.res.vtl", 101 | { 102 | "S3DeploymentBucket": { 103 | "Ref": "S3DeploymentBucket" 104 | }, 105 | "S3DeploymentRootKey": { 106 | "Ref": "S3DeploymentRootKey" 107 | } 108 | } 109 | ] 110 | } 111 | } 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | 3. Write the resolver templates. 118 | 119 | ```vtl 120 | ## Query.commentsForTodo.req.vtl ** 121 | 122 | #set( $limit = $util.defaultIfNull($context.args.limit, 10) ) 123 | { 124 | "version": "2017-02-28", 125 | "operation": "Query", 126 | "query": { 127 | "expression": "#connectionAttribute = :connectionAttribute", 128 | "expressionNames": { 129 | "#connectionAttribute": "commentTodoId" 130 | }, 131 | "expressionValues": { 132 | ":connectionAttribute": { 133 | "S": "$context.args.todoId" 134 | } 135 | } 136 | }, 137 | "scanIndexForward": true, 138 | "limit": $limit, 139 | "nextToken": #if( $context.args.nextToken ) "$context.args.nextToken" #else null #end, 140 | "index": "gsi-TodoComments" 141 | } 142 | ``` 143 | 144 | ```vtl 145 | ## Query.commentsForTodo.res.vtl ** 146 | 147 | $util.toJson($ctx.result) 148 | ``` 149 | 150 | **Add a custom resolver that targets an AWS Lambda function** 151 | 152 | Velocity is useful as a fast, secure environment to run arbitrary code but when it comes to writing complex business logic you can just as easily call out to an AWS lambda function. Here is how: 153 | 154 | 1. First create a function by running `amplify add function`. The rest of the example assumes you created a function named "echofunction" via the `amplify add function` command. If you already have a function then you may skip this step. 155 | 156 | 2. Add a field to your schema.graphql that will invoke the AWS Lambda function. 157 | 158 | ``` 159 | type Query { 160 | echo(msg: String): String 161 | } 162 | ``` 163 | 164 | 3. Add the function as an AppSync data source in the stack's *Resources* block. 165 | 166 | ```json 167 | "EchoLambdaDataSource": { 168 | "Type": "AWS::AppSync::DataSource", 169 | "Properties": { 170 | "ApiId": { 171 | "Ref": "AppSyncApiId" 172 | }, 173 | "Type": "AWS_LAMBDA", 174 | "Name": "EchoFunction", 175 | "ServiceRoleArn": { 176 | "Fn::GetAtt": [ 177 | "EchoLambdaDataSourceRole", 178 | "Arn" 179 | ] 180 | }, 181 | "LambdaConfig": { 182 | "LambdaFunctionArn": { 183 | "Fn::Sub": [ 184 | "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:echofunction-${env}", 185 | { "env": { "Ref": "env" } } 186 | ] 187 | } 188 | } 189 | } 190 | } 191 | ``` 192 | 193 | 4. Create an AWS IAM role that allows AppSync to invoke the lambda function on your behalf to the stack's *Resources* block. 194 | 195 | ```json 196 | "EchoLambdaDataSourceRole": { 197 | "Type": "AWS::IAM::Role", 198 | "Properties": { 199 | "RoleName": "EchoLambdaDataSourceRole", 200 | "AssumeRolePolicyDocument": { 201 | "Version": "2012-10-17", 202 | "Statement": [ 203 | { 204 | "Effect": "Allow", 205 | "Principal": { 206 | "Service": "appsync.amazonaws.com" 207 | }, 208 | "Action": "sts:AssumeRole" 209 | } 210 | ] 211 | }, 212 | "Policies": [ 213 | { 214 | "PolicyName": "InvokeLambdaFunction", 215 | "PolicyDocument": { 216 | "Version": "2012-10-17", 217 | "Statement": [ 218 | { 219 | "Effect": "Allow", 220 | "Action": [ 221 | "lambda:invokeFunction" 222 | ], 223 | "Resource": [ 224 | { 225 | "Fn::Sub": [ 226 | "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:echofunction-${env}", 227 | { "env": { "Ref": "env" } } 228 | ] 229 | } 230 | ] 231 | } 232 | ] 233 | } 234 | } 235 | ] 236 | } 237 | } 238 | ``` 239 | 240 | 5. Create an AppSync resolver in the stack's *Resources* block. 241 | 242 | ```json 243 | "QueryEchoResolver": { 244 | "Type": "AWS::AppSync::Resolver", 245 | "Properties": { 246 | "ApiId": { 247 | "Ref": "AppSyncApiId" 248 | }, 249 | "DataSourceName": { 250 | "Fn::GetAtt": [ 251 | "EchoLambdaDataSource", 252 | "Name" 253 | ] 254 | }, 255 | "TypeName": "Query", 256 | "FieldName": "echo", 257 | "RequestMappingTemplateS3Location": { 258 | "Fn::Sub": [ 259 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.echo.req.vtl", 260 | { 261 | "S3DeploymentBucket": { 262 | "Ref": "S3DeploymentBucket" 263 | }, 264 | "S3DeploymentRootKey": { 265 | "Ref": "S3DeploymentRootKey" 266 | } 267 | } 268 | ] 269 | }, 270 | "ResponseMappingTemplateS3Location": { 271 | "Fn::Sub": [ 272 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.echo.res.vtl", 273 | { 274 | "S3DeploymentBucket": { 275 | "Ref": "S3DeploymentBucket" 276 | }, 277 | "S3DeploymentRootKey": { 278 | "Ref": "S3DeploymentRootKey" 279 | } 280 | } 281 | ] 282 | } 283 | } 284 | } 285 | ``` 286 | 287 | 6. Create the resolver templates in the project's *resolvers* directory. 288 | 289 | **resolvers/Query.echo.req.vtl** 290 | 291 | ``` 292 | { 293 | "version": "2017-02-28", 294 | "operation": "Invoke", 295 | "payload": { 296 | "type": "Query", 297 | "field": "echo", 298 | "arguments": $utils.toJson($context.arguments), 299 | "identity": $utils.toJson($context.identity), 300 | "source": $utils.toJson($context.source) 301 | } 302 | } 303 | ``` 304 | 305 | **resolvers/Query.echo.res.vtl** 306 | 307 | ``` 308 | $util.toJson($ctx.result) 309 | ``` 310 | 311 | After running `amplify push` open the AppSync console with `amplify api console` and test your API with this simple query: 312 | 313 | ``` 314 | query { 315 | echo(msg:"Hello, world!") 316 | } 317 | ``` 318 | 319 | **Add a custom geo search resolver that targets an Elasticsearch domain created by @searchable** 320 | 321 | To add a geo search capabilities to an API add the *@searchable* directive to an *@model* type. 322 | 323 | ``` 324 | type Todo @model @searchable { 325 | id: ID! 326 | name: String! 327 | description: String 328 | comments: [Todo] @connection(name: "TodoComments") 329 | } 330 | ``` 331 | 332 | The next time you run `amplify push`, an Amazon Elasticsearch domain will be created and configured such that data automatically streams from DynamoDB into Elasticsearch. The *@searchable* directive on the Todo type will generate a *Query.searchTodos* query field and resolver but it is not uncommon to want more specific search capabilities. You can write a custom search resolver by following these steps: 333 | 334 | 1. Add the relevant location and search fields to the schema. 335 | 336 | ``` 337 | type Location { 338 | lat: Float 339 | lon: Float 340 | } 341 | input LocationInput { 342 | lat: Float 343 | lon: Float 344 | } 345 | type Todo @model @searchable { 346 | id: ID! 347 | name: String! 348 | description: String 349 | comments: [Todo] @connection(name: "TodoComments") 350 | location: Location 351 | } 352 | type Query { 353 | nearbyTodos(location: LocationInput!, km: Int): TodoConnection 354 | } 355 | ``` 356 | 357 | 2. Create the resolver record in the stack's *Resources* block. 358 | 359 | ```json 360 | "QueryNearbyTodos": { 361 | "Type": "AWS::AppSync::Resolver", 362 | "Properties": { 363 | "ApiId": { 364 | "Ref": "AppSyncApiId" 365 | }, 366 | "DataSourceName": "ElasticsearchDomain", 367 | "TypeName": "Query", 368 | "FieldName": "nearbyTodos", 369 | "RequestMappingTemplateS3Location": { 370 | "Fn::Sub": [ 371 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.nearbyTodos.req.vtl", 372 | { 373 | "S3DeploymentBucket": { 374 | "Ref": "S3DeploymentBucket" 375 | }, 376 | "S3DeploymentRootKey": { 377 | "Ref": "S3DeploymentRootKey" 378 | } 379 | } 380 | ] 381 | }, 382 | "ResponseMappingTemplateS3Location": { 383 | "Fn::Sub": [ 384 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.nearbyTodos.res.vtl", 385 | { 386 | "S3DeploymentBucket": { 387 | "Ref": "S3DeploymentBucket" 388 | }, 389 | "S3DeploymentRootKey": { 390 | "Ref": "S3DeploymentRootKey" 391 | } 392 | } 393 | ] 394 | } 395 | } 396 | } 397 | ``` 398 | 399 | 3. Write the resolver templates. 400 | 401 | ``` 402 | ## Query.nearbyTodos.req.vtl 403 | ## Objects of type Todo will be stored in the /todo index 404 | 405 | #set( $indexPath = "/todo/doc/_search" ) 406 | #set( $distance = $util.defaultIfNull($ctx.args.km, 200) ) 407 | { 408 | "version": "2017-02-28", 409 | "operation": "GET", 410 | "path": "$indexPath.toLowerCase()", 411 | "params": { 412 | "body": { 413 | "query": { 414 | "bool" : { 415 | "must" : { 416 | "match_all" : {} 417 | }, 418 | "filter" : { 419 | "geo_distance" : { 420 | "distance" : "${distance}km", 421 | "location" : $util.toJson($ctx.args.location) 422 | } 423 | } 424 | } 425 | } 426 | } 427 | } 428 | } 429 | ``` 430 | 431 | ``` 432 | ## Query.nearbyTodos.res.vtl 433 | 434 | #set( $items = [] ) 435 | #foreach( $entry in $context.result.hits.hits ) 436 | #if( !$foreach.hasNext ) 437 | #set( $nextToken = "$entry.sort.get(0)" ) 438 | #end 439 | $util.qr($items.add($entry.get("_source"))) 440 | #end 441 | $util.toJson({ 442 | "items": $items, 443 | "total": $ctx.result.hits.total, 444 | "nextToken": $nextToken 445 | }) 446 | ``` 447 | 448 | 4. Run `ampify push` 449 | 450 | Amazon Elasticsearch domains can take a while to deploy. Take this time to read up on Elasticsearch to see what capabilities you are about to unlock. 451 | 452 | [Getting Started with Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html) 453 | 454 | 4. After the update is comples but before creating any objects, update your Elasticsearch index mapping. 455 | 456 | An index mapping tells elasticsearch how it should treat the data that you are trying to store. By default if we create an object with field `"location": { "lat": 40, "lon": -40 }`, Elasticsearch will treat that data as an *object* type when in reality we want it to be treated as a *geo_point*. You use the mapping APIs to tell Elasticsearch how to do this. 457 | 458 | Make sure you tell Elasticsearch that your location field is a *geo_point* before creating objects in the index because otherwise you will need delete the index and try again. Go to the [Amazon Elasticsearch Console](https://console.aws.amazon.com/es/home) and find the Elasticsearch domain that contains this environment's GraphQL API ID. Click on it and open the kibana link. To get kibana to show up you need to install a browser extension such as [AWS Agent](https://addons.mozilla.org/en-US/firefox/addon/aws-agent/) and configure it with your AWS profile's public key and secret so the browser can sign your requests to kibana for security reasons. Once you have kibana open, click the "Dev Tools" tab on the left and run the commands below using the in browser console. 459 | 460 | ```json 461 | # Create the /todo index if it does not exist 462 | PUT /todo 463 | 464 | # Tell Elasticsearch that the location field is a geo_point 465 | PUT /todo/_mapping/doc 466 | { 467 | "properties": { 468 | "location": { 469 | "type": "geo_point" 470 | } 471 | } 472 | } 473 | ``` 474 | 475 | 5. Use your API to create objects and immediately search them. 476 | 477 | After updating the Elasticsearch index mapping, open the AWS AppSync console with `amplify api console` and try out these queries. 478 | 479 | ``` 480 | mutation CreateTodo { 481 | createTodo(input:{ 482 | name: "Todo 1", 483 | description: "The first thing to do", 484 | location: { 485 | lat:43.476446, 486 | lon:-110.767786 487 | } 488 | }) { 489 | id 490 | name 491 | location { 492 | lat 493 | lon 494 | } 495 | description 496 | } 497 | } 498 | 499 | query NearbyTodos { 500 | nearbyTodos(location: { 501 | lat: 43.476546, 502 | lon: -110.768786 503 | }, km: 200) { 504 | items { 505 | id 506 | name 507 | location { 508 | lat 509 | lon 510 | } 511 | } 512 | } 513 | } 514 | ``` 515 | 516 | When you run *Mutation.createTodo*, the data will automatically be streamed via AWS Lambda into Elasticsearch such that it nearly immediately available via *Query.nearbyTodos*. 517 | -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/cloudformation-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiName": { 7 | "Type": "String", 8 | "Description": "The name of the AppSync API", 9 | "Default": "AppSyncSimpleTransform" 10 | }, 11 | "ElasticsearchAccessIAMRoleName": { 12 | "Type": "String", 13 | "Description": "The name of the IAM role assumed by AppSync for Elasticsearch.", 14 | "Default": "AppSyncElasticsearchAccess" 15 | }, 16 | "ElasticsearchStreamingLambdaHandlerName": { 17 | "Type": "String", 18 | "Description": "The name of the lambda handler.", 19 | "Default": "python_streaming_function.lambda_handler" 20 | }, 21 | "ElasticsearchStreamingLambdaRuntime": { 22 | "Type": "String", 23 | "Description": "The lambda runtime (https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-Runtime)", 24 | "Default": "python3.6" 25 | }, 26 | "ElasticsearchStreamingFunctionName": { 27 | "Type": "String", 28 | "Description": "The name of the streaming lambda function.", 29 | "Default": "DdbToEsFn" 30 | }, 31 | "ElasticsearchStreamingIAMRoleName": { 32 | "Type": "String", 33 | "Description": "The name of the streaming lambda function IAM role.", 34 | "Default": "SearchableLambdaIAMRole" 35 | }, 36 | "ElasticsearchDebugStreamingLambda": { 37 | "Type": "Number", 38 | "Description": "Enable debug logs for the Dynamo -> ES streaming lambda.", 39 | "Default": 1, 40 | "AllowedValues": [ 41 | 0, 42 | 1 43 | ] 44 | }, 45 | "ElasticsearchInstanceCount": { 46 | "Type": "Number", 47 | "Description": "The number of instances to launch into the Elasticsearch domain.", 48 | "Default": 1 49 | }, 50 | "ElasticsearchDomainName": { 51 | "Type": "String", 52 | "Description": "The name of the Elasticsearch domain.", 53 | "Default": "appsync-elasticsearch-domain", 54 | "AllowedPattern": "^[a-z][a-z0-9-]*$", 55 | "MinLength": 1, 56 | "MaxLength": 28 57 | }, 58 | "ElasticsearchInstanceType": { 59 | "Type": "String", 60 | "Description": "The type of instance to launch into the Elasticsearch domain.", 61 | "Default": "t2.small.elasticsearch", 62 | "AllowedValues": [ 63 | "t2.small.elasticsearch", 64 | "t2.medium.elasticsearch", 65 | "c4.large.elasticsearch", 66 | "c4.xlarge.elasticsearch", 67 | "c4.2xlarge.elasticsearch", 68 | "c4.4xlarge.elasticsearch", 69 | "c4.8xlarge.elasticsearch", 70 | "m3.medium.elasticsearch", 71 | "m3.large.elasticsearch", 72 | "m3.xlarge.elasticsearch", 73 | "m3.2xlarge.elasticsearch", 74 | "m4.large.elasticsearch", 75 | "m4.xlarge.elasticsearch", 76 | "m4.2xlarge.elasticsearch", 77 | "m4.4xlarge.elasticsearch", 78 | "m4.10xlarge.elasticsearch", 79 | "r3.large.elasticsearch", 80 | "r3.xlarge.elasticsearch", 81 | "r3.2xlarge.elasticsearch", 82 | "r3.4xlarge.elasticsearch", 83 | "r3.8xlarge.elasticsearch", 84 | "r4.large.elasticsearch", 85 | "r4.xlarge.elasticsearch", 86 | "r4.2xlarge.elasticsearch", 87 | "r4.4xlarge.elasticsearch", 88 | "r4.8xlarge.elasticsearch", 89 | "r4.16xlarge.elasticsearch", 90 | "i2.xlarge.elasticsearch", 91 | "i2.2xlarge.elasticsearch", 92 | "i3.large.elasticsearch", 93 | "i3.xlarge.elasticsearch", 94 | "i3.2xlarge.elasticsearch", 95 | "i3.4xlarge.elasticsearch", 96 | "i3.8xlarge.elasticsearch", 97 | "i3.16xlarge.elasticsearch" 98 | ] 99 | }, 100 | "ElasticsearchEBSVolumeGB": { 101 | "Type": "Number", 102 | "Description": "The size in GB of the EBS volumes that contain our data.", 103 | "Default": 20 104 | }, 105 | "env": { 106 | "Type": "String", 107 | "Description": "The environment name. e.g. Dev, Test, or Production", 108 | "Default": "NONE" 109 | }, 110 | "S3DeploymentBucket": { 111 | "Type": "String", 112 | "Description": "The S3 bucket containing all deployment assets for the project." 113 | }, 114 | "S3DeploymentRootKey": { 115 | "Type": "String", 116 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root of the deployment directory." 117 | } 118 | }, 119 | "Resources": { 120 | "GraphQLAPI": { 121 | "Type": "AWS::AppSync::GraphQLApi", 122 | "Properties": { 123 | "Name": { 124 | "Fn::If": [ 125 | "HasEnvironmentParameter", 126 | { 127 | "Fn::Join": [ 128 | "-", 129 | [ 130 | { 131 | "Ref": "AppSyncApiName" 132 | }, 133 | { 134 | "Ref": "env" 135 | } 136 | ] 137 | ] 138 | }, 139 | { 140 | "Ref": "AppSyncApiName" 141 | } 142 | ] 143 | }, 144 | "AuthenticationType": "API_KEY" 145 | } 146 | }, 147 | "GraphQLAPIKey": { 148 | "Type": "AWS::AppSync::ApiKey", 149 | "Properties": { 150 | "ApiId": { 151 | "Fn::GetAtt": [ 152 | "GraphQLAPI", 153 | "ApiId" 154 | ] 155 | } 156 | } 157 | }, 158 | "GraphQLSchema": { 159 | "Type": "AWS::AppSync::GraphQLSchema", 160 | "Properties": { 161 | "ApiId": { 162 | "Fn::GetAtt": [ 163 | "GraphQLAPI", 164 | "ApiId" 165 | ] 166 | }, 167 | "DefinitionS3Location": { 168 | "Fn::Sub": [ 169 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/schema.graphql", 170 | { 171 | "S3DeploymentBucket": { 172 | "Ref": "S3DeploymentBucket" 173 | }, 174 | "S3DeploymentRootKey": { 175 | "Ref": "S3DeploymentRootKey" 176 | } 177 | } 178 | ] 179 | } 180 | } 181 | }, 182 | "Todo": { 183 | "Type": "AWS::CloudFormation::Stack", 184 | "Properties": { 185 | "Parameters": { 186 | "AppSyncApiId": { 187 | "Fn::GetAtt": [ 188 | "GraphQLAPI", 189 | "ApiId" 190 | ] 191 | }, 192 | "AppSyncApiName": { 193 | "Ref": "AppSyncApiName" 194 | }, 195 | "ElasticsearchAccessIAMRoleName": { 196 | "Ref": "ElasticsearchAccessIAMRoleName" 197 | }, 198 | "ElasticsearchStreamingLambdaHandlerName": { 199 | "Ref": "ElasticsearchStreamingLambdaHandlerName" 200 | }, 201 | "ElasticsearchStreamingLambdaRuntime": { 202 | "Ref": "ElasticsearchStreamingLambdaRuntime" 203 | }, 204 | "ElasticsearchStreamingFunctionName": { 205 | "Ref": "ElasticsearchStreamingFunctionName" 206 | }, 207 | "ElasticsearchStreamingIAMRoleName": { 208 | "Ref": "ElasticsearchStreamingIAMRoleName" 209 | }, 210 | "ElasticsearchDebugStreamingLambda": { 211 | "Ref": "ElasticsearchDebugStreamingLambda" 212 | }, 213 | "ElasticsearchInstanceCount": { 214 | "Ref": "ElasticsearchInstanceCount" 215 | }, 216 | "ElasticsearchDomainName": { 217 | "Ref": "ElasticsearchDomainName" 218 | }, 219 | "ElasticsearchInstanceType": { 220 | "Ref": "ElasticsearchInstanceType" 221 | }, 222 | "ElasticsearchEBSVolumeGB": { 223 | "Ref": "ElasticsearchEBSVolumeGB" 224 | }, 225 | "env": { 226 | "Ref": "env" 227 | }, 228 | "S3DeploymentBucket": { 229 | "Ref": "S3DeploymentBucket" 230 | }, 231 | "S3DeploymentRootKey": { 232 | "Ref": "S3DeploymentRootKey" 233 | }, 234 | "GetAttGraphQLAPIApiId": { 235 | "Fn::GetAtt": [ 236 | "GraphQLAPI", 237 | "ApiId" 238 | ] 239 | } 240 | }, 241 | "TemplateURL": { 242 | "Fn::Join": [ 243 | "/", 244 | [ 245 | "https://s3.amazonaws.com", 246 | { 247 | "Ref": "S3DeploymentBucket" 248 | }, 249 | { 250 | "Ref": "S3DeploymentRootKey" 251 | }, 252 | "stacks", 253 | "Todo.json" 254 | ] 255 | ] 256 | } 257 | }, 258 | "DependsOn": [ 259 | "GraphQLSchema" 260 | ] 261 | }, 262 | "ConnectionStack": { 263 | "Type": "AWS::CloudFormation::Stack", 264 | "Properties": { 265 | "Parameters": { 266 | "AppSyncApiId": { 267 | "Fn::GetAtt": [ 268 | "GraphQLAPI", 269 | "ApiId" 270 | ] 271 | }, 272 | "AppSyncApiName": { 273 | "Ref": "AppSyncApiName" 274 | }, 275 | "ElasticsearchAccessIAMRoleName": { 276 | "Ref": "ElasticsearchAccessIAMRoleName" 277 | }, 278 | "ElasticsearchStreamingLambdaHandlerName": { 279 | "Ref": "ElasticsearchStreamingLambdaHandlerName" 280 | }, 281 | "ElasticsearchStreamingLambdaRuntime": { 282 | "Ref": "ElasticsearchStreamingLambdaRuntime" 283 | }, 284 | "ElasticsearchStreamingFunctionName": { 285 | "Ref": "ElasticsearchStreamingFunctionName" 286 | }, 287 | "ElasticsearchStreamingIAMRoleName": { 288 | "Ref": "ElasticsearchStreamingIAMRoleName" 289 | }, 290 | "ElasticsearchDebugStreamingLambda": { 291 | "Ref": "ElasticsearchDebugStreamingLambda" 292 | }, 293 | "ElasticsearchInstanceCount": { 294 | "Ref": "ElasticsearchInstanceCount" 295 | }, 296 | "ElasticsearchDomainName": { 297 | "Ref": "ElasticsearchDomainName" 298 | }, 299 | "ElasticsearchInstanceType": { 300 | "Ref": "ElasticsearchInstanceType" 301 | }, 302 | "ElasticsearchEBSVolumeGB": { 303 | "Ref": "ElasticsearchEBSVolumeGB" 304 | }, 305 | "env": { 306 | "Ref": "env" 307 | }, 308 | "S3DeploymentBucket": { 309 | "Ref": "S3DeploymentBucket" 310 | }, 311 | "S3DeploymentRootKey": { 312 | "Ref": "S3DeploymentRootKey" 313 | }, 314 | "GetAttGraphQLAPIApiId": { 315 | "Fn::GetAtt": [ 316 | "GraphQLAPI", 317 | "ApiId" 318 | ] 319 | } 320 | }, 321 | "TemplateURL": { 322 | "Fn::Join": [ 323 | "/", 324 | [ 325 | "https://s3.amazonaws.com", 326 | { 327 | "Ref": "S3DeploymentBucket" 328 | }, 329 | { 330 | "Ref": "S3DeploymentRootKey" 331 | }, 332 | "stacks", 333 | "ConnectionStack.json" 334 | ] 335 | ] 336 | } 337 | }, 338 | "DependsOn": [ 339 | "GraphQLSchema", 340 | "Comment", 341 | "Todo" 342 | ] 343 | }, 344 | "Comment": { 345 | "Type": "AWS::CloudFormation::Stack", 346 | "Properties": { 347 | "Parameters": { 348 | "AppSyncApiId": { 349 | "Fn::GetAtt": [ 350 | "GraphQLAPI", 351 | "ApiId" 352 | ] 353 | }, 354 | "AppSyncApiName": { 355 | "Ref": "AppSyncApiName" 356 | }, 357 | "ElasticsearchAccessIAMRoleName": { 358 | "Ref": "ElasticsearchAccessIAMRoleName" 359 | }, 360 | "ElasticsearchStreamingLambdaHandlerName": { 361 | "Ref": "ElasticsearchStreamingLambdaHandlerName" 362 | }, 363 | "ElasticsearchStreamingLambdaRuntime": { 364 | "Ref": "ElasticsearchStreamingLambdaRuntime" 365 | }, 366 | "ElasticsearchStreamingFunctionName": { 367 | "Ref": "ElasticsearchStreamingFunctionName" 368 | }, 369 | "ElasticsearchStreamingIAMRoleName": { 370 | "Ref": "ElasticsearchStreamingIAMRoleName" 371 | }, 372 | "ElasticsearchDebugStreamingLambda": { 373 | "Ref": "ElasticsearchDebugStreamingLambda" 374 | }, 375 | "ElasticsearchInstanceCount": { 376 | "Ref": "ElasticsearchInstanceCount" 377 | }, 378 | "ElasticsearchDomainName": { 379 | "Ref": "ElasticsearchDomainName" 380 | }, 381 | "ElasticsearchInstanceType": { 382 | "Ref": "ElasticsearchInstanceType" 383 | }, 384 | "ElasticsearchEBSVolumeGB": { 385 | "Ref": "ElasticsearchEBSVolumeGB" 386 | }, 387 | "env": { 388 | "Ref": "env" 389 | }, 390 | "S3DeploymentBucket": { 391 | "Ref": "S3DeploymentBucket" 392 | }, 393 | "S3DeploymentRootKey": { 394 | "Ref": "S3DeploymentRootKey" 395 | }, 396 | "GetAttGraphQLAPIApiId": { 397 | "Fn::GetAtt": [ 398 | "GraphQLAPI", 399 | "ApiId" 400 | ] 401 | } 402 | }, 403 | "TemplateURL": { 404 | "Fn::Join": [ 405 | "/", 406 | [ 407 | "https://s3.amazonaws.com", 408 | { 409 | "Ref": "S3DeploymentBucket" 410 | }, 411 | { 412 | "Ref": "S3DeploymentRootKey" 413 | }, 414 | "stacks", 415 | "Comment.json" 416 | ] 417 | ] 418 | } 419 | }, 420 | "DependsOn": [ 421 | "GraphQLSchema" 422 | ] 423 | }, 424 | "SearchableStack": { 425 | "Type": "AWS::CloudFormation::Stack", 426 | "Properties": { 427 | "Parameters": { 428 | "AppSyncApiId": { 429 | "Fn::GetAtt": [ 430 | "GraphQLAPI", 431 | "ApiId" 432 | ] 433 | }, 434 | "AppSyncApiName": { 435 | "Ref": "AppSyncApiName" 436 | }, 437 | "ElasticsearchAccessIAMRoleName": { 438 | "Ref": "ElasticsearchAccessIAMRoleName" 439 | }, 440 | "ElasticsearchStreamingLambdaHandlerName": { 441 | "Ref": "ElasticsearchStreamingLambdaHandlerName" 442 | }, 443 | "ElasticsearchStreamingLambdaRuntime": { 444 | "Ref": "ElasticsearchStreamingLambdaRuntime" 445 | }, 446 | "ElasticsearchStreamingFunctionName": { 447 | "Ref": "ElasticsearchStreamingFunctionName" 448 | }, 449 | "ElasticsearchStreamingIAMRoleName": { 450 | "Ref": "ElasticsearchStreamingIAMRoleName" 451 | }, 452 | "ElasticsearchDebugStreamingLambda": { 453 | "Ref": "ElasticsearchDebugStreamingLambda" 454 | }, 455 | "ElasticsearchInstanceCount": { 456 | "Ref": "ElasticsearchInstanceCount" 457 | }, 458 | "ElasticsearchDomainName": { 459 | "Ref": "ElasticsearchDomainName" 460 | }, 461 | "ElasticsearchInstanceType": { 462 | "Ref": "ElasticsearchInstanceType" 463 | }, 464 | "ElasticsearchEBSVolumeGB": { 465 | "Ref": "ElasticsearchEBSVolumeGB" 466 | }, 467 | "env": { 468 | "Ref": "env" 469 | }, 470 | "S3DeploymentBucket": { 471 | "Ref": "S3DeploymentBucket" 472 | }, 473 | "S3DeploymentRootKey": { 474 | "Ref": "S3DeploymentRootKey" 475 | }, 476 | "GetAttGraphQLAPIApiId": { 477 | "Fn::GetAtt": [ 478 | "GraphQLAPI", 479 | "ApiId" 480 | ] 481 | } 482 | }, 483 | "TemplateURL": { 484 | "Fn::Join": [ 485 | "/", 486 | [ 487 | "https://s3.amazonaws.com", 488 | { 489 | "Ref": "S3DeploymentBucket" 490 | }, 491 | { 492 | "Ref": "S3DeploymentRootKey" 493 | }, 494 | "stacks", 495 | "SearchableStack.json" 496 | ] 497 | ] 498 | } 499 | }, 500 | "DependsOn": [ 501 | "GraphQLSchema", 502 | "Todo" 503 | ] 504 | }, 505 | "CustomResourcesjson": { 506 | "Type": "AWS::CloudFormation::Stack", 507 | "Properties": { 508 | "Parameters": { 509 | "AppSyncApiId": { 510 | "Fn::GetAtt": [ 511 | "GraphQLAPI", 512 | "ApiId" 513 | ] 514 | }, 515 | "AppSyncApiName": { 516 | "Ref": "AppSyncApiName" 517 | }, 518 | "env": { 519 | "Ref": "env" 520 | }, 521 | "S3DeploymentBucket": { 522 | "Ref": "S3DeploymentBucket" 523 | }, 524 | "S3DeploymentRootKey": { 525 | "Ref": "S3DeploymentRootKey" 526 | } 527 | }, 528 | "TemplateURL": { 529 | "Fn::Join": [ 530 | "/", 531 | [ 532 | "https://s3.amazonaws.com", 533 | { 534 | "Ref": "S3DeploymentBucket" 535 | }, 536 | { 537 | "Ref": "S3DeploymentRootKey" 538 | }, 539 | "stacks", 540 | "CustomResources.json" 541 | ] 542 | ] 543 | } 544 | }, 545 | "DependsOn": [ 546 | "GraphQLAPI", 547 | "GraphQLSchema", 548 | "Todo", 549 | "ConnectionStack", 550 | "Comment", 551 | "SearchableStack" 552 | ] 553 | } 554 | }, 555 | "Outputs": { 556 | "GraphQLAPIIdOutput": { 557 | "Description": "Your GraphQL API ID.", 558 | "Value": { 559 | "Fn::GetAtt": [ 560 | "GraphQLAPI", 561 | "ApiId" 562 | ] 563 | }, 564 | "Export": { 565 | "Name": { 566 | "Fn::Join": [ 567 | ":", 568 | [ 569 | { 570 | "Ref": "AWS::StackName" 571 | }, 572 | "GraphQLApiId" 573 | ] 574 | ] 575 | } 576 | } 577 | }, 578 | "GraphQLAPIEndpointOutput": { 579 | "Description": "Your GraphQL API endpoint.", 580 | "Value": { 581 | "Fn::GetAtt": [ 582 | "GraphQLAPI", 583 | "GraphQLUrl" 584 | ] 585 | }, 586 | "Export": { 587 | "Name": { 588 | "Fn::Join": [ 589 | ":", 590 | [ 591 | { 592 | "Ref": "AWS::StackName" 593 | }, 594 | "GraphQLApiEndpoint" 595 | ] 596 | ] 597 | } 598 | } 599 | }, 600 | "GraphQLAPIKeyOutput": { 601 | "Description": "Your GraphQL API key. Provide via 'x-api-key' header.", 602 | "Value": { 603 | "Fn::GetAtt": [ 604 | "GraphQLAPIKey", 605 | "ApiKey" 606 | ] 607 | }, 608 | "Export": { 609 | "Name": { 610 | "Fn::Join": [ 611 | ":", 612 | [ 613 | { 614 | "Ref": "AWS::StackName" 615 | }, 616 | "GraphQLApiKey" 617 | ] 618 | ] 619 | } 620 | } 621 | } 622 | }, 623 | "Conditions": { 624 | "HasEnvironmentParameter": { 625 | "Fn::Not": [ 626 | { 627 | "Fn::Equals": [ 628 | { 629 | "Ref": "env" 630 | }, 631 | "NONE" 632 | ] 633 | } 634 | ] 635 | } 636 | } 637 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/stacks/SearchableStack.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiName": { 7 | "Type": "String", 8 | "Description": "The name of the AppSync API", 9 | "Default": "AppSyncSimpleTransform" 10 | }, 11 | "ElasticsearchAccessIAMRoleName": { 12 | "Type": "String", 13 | "Description": "The name of the IAM role assumed by AppSync for Elasticsearch.", 14 | "Default": "AppSyncElasticsearchAccess" 15 | }, 16 | "ElasticsearchStreamingLambdaHandlerName": { 17 | "Type": "String", 18 | "Description": "The name of the lambda handler.", 19 | "Default": "python_streaming_function.lambda_handler" 20 | }, 21 | "ElasticsearchStreamingLambdaRuntime": { 22 | "Type": "String", 23 | "Description": "The lambda runtime (https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-Runtime)", 24 | "Default": "python3.6" 25 | }, 26 | "ElasticsearchStreamingFunctionName": { 27 | "Type": "String", 28 | "Description": "The name of the streaming lambda function.", 29 | "Default": "DdbToEsFn" 30 | }, 31 | "ElasticsearchStreamingIAMRoleName": { 32 | "Type": "String", 33 | "Description": "The name of the streaming lambda function IAM role.", 34 | "Default": "SearchableLambdaIAMRole" 35 | }, 36 | "ElasticsearchDebugStreamingLambda": { 37 | "Type": "Number", 38 | "Description": "Enable debug logs for the Dynamo -> ES streaming lambda.", 39 | "Default": 1, 40 | "AllowedValues": [ 41 | 0, 42 | 1 43 | ] 44 | }, 45 | "ElasticsearchInstanceCount": { 46 | "Type": "Number", 47 | "Description": "The number of instances to launch into the Elasticsearch domain.", 48 | "Default": 1 49 | }, 50 | "ElasticsearchDomainName": { 51 | "Type": "String", 52 | "Description": "The name of the Elasticsearch domain.", 53 | "Default": "appsync-elasticsearch-domain", 54 | "AllowedPattern": "^[a-z][a-z0-9-]*$", 55 | "MinLength": 1, 56 | "MaxLength": 28 57 | }, 58 | "ElasticsearchInstanceType": { 59 | "Type": "String", 60 | "Description": "The type of instance to launch into the Elasticsearch domain.", 61 | "Default": "t2.small.elasticsearch", 62 | "AllowedValues": [ 63 | "t2.small.elasticsearch", 64 | "t2.medium.elasticsearch", 65 | "c4.large.elasticsearch", 66 | "c4.xlarge.elasticsearch", 67 | "c4.2xlarge.elasticsearch", 68 | "c4.4xlarge.elasticsearch", 69 | "c4.8xlarge.elasticsearch", 70 | "m3.medium.elasticsearch", 71 | "m3.large.elasticsearch", 72 | "m3.xlarge.elasticsearch", 73 | "m3.2xlarge.elasticsearch", 74 | "m4.large.elasticsearch", 75 | "m4.xlarge.elasticsearch", 76 | "m4.2xlarge.elasticsearch", 77 | "m4.4xlarge.elasticsearch", 78 | "m4.10xlarge.elasticsearch", 79 | "r3.large.elasticsearch", 80 | "r3.xlarge.elasticsearch", 81 | "r3.2xlarge.elasticsearch", 82 | "r3.4xlarge.elasticsearch", 83 | "r3.8xlarge.elasticsearch", 84 | "r4.large.elasticsearch", 85 | "r4.xlarge.elasticsearch", 86 | "r4.2xlarge.elasticsearch", 87 | "r4.4xlarge.elasticsearch", 88 | "r4.8xlarge.elasticsearch", 89 | "r4.16xlarge.elasticsearch", 90 | "i2.xlarge.elasticsearch", 91 | "i2.2xlarge.elasticsearch", 92 | "i3.large.elasticsearch", 93 | "i3.xlarge.elasticsearch", 94 | "i3.2xlarge.elasticsearch", 95 | "i3.4xlarge.elasticsearch", 96 | "i3.8xlarge.elasticsearch", 97 | "i3.16xlarge.elasticsearch" 98 | ] 99 | }, 100 | "ElasticsearchEBSVolumeGB": { 101 | "Type": "Number", 102 | "Description": "The size in GB of the EBS volumes that contain our data.", 103 | "Default": 20 104 | }, 105 | "env": { 106 | "Type": "String", 107 | "Description": "The environment name. e.g. Dev, Test, or Production", 108 | "Default": "NONE" 109 | }, 110 | "S3DeploymentBucket": { 111 | "Type": "String", 112 | "Description": "The S3 bucket containing all deployment assets for the project." 113 | }, 114 | "S3DeploymentRootKey": { 115 | "Type": "String", 116 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root of the deployment directory." 117 | }, 118 | "AppSyncApiId": { 119 | "Type": "String", 120 | "Description": "The id of the AppSync API associated with this project." 121 | }, 122 | "GetAttGraphQLAPIApiId": { 123 | "Type": "String", 124 | "Description": "Auto-generated parameter that forwards Fn.GetAtt(GraphQLAPI, ApiId) through to nested stacks." 125 | } 126 | }, 127 | "Resources": { 128 | "ElasticsearchAccessIAMRole": { 129 | "Type": "AWS::IAM::Role", 130 | "Properties": { 131 | "RoleName": { 132 | "Fn::If": [ 133 | "HasEnvironmentParameter", 134 | { 135 | "Fn::Join": [ 136 | "-", 137 | [ 138 | { 139 | "Ref": "ElasticsearchAccessIAMRoleName" 140 | }, 141 | { 142 | "Ref": "GetAttGraphQLAPIApiId" 143 | }, 144 | { 145 | "Ref": "env" 146 | } 147 | ] 148 | ] 149 | }, 150 | { 151 | "Fn::Join": [ 152 | "-", 153 | [ 154 | { 155 | "Ref": "ElasticsearchAccessIAMRoleName" 156 | }, 157 | { 158 | "Ref": "GetAttGraphQLAPIApiId" 159 | } 160 | ] 161 | ] 162 | } 163 | ] 164 | }, 165 | "AssumeRolePolicyDocument": { 166 | "Version": "2012-10-17", 167 | "Statement": [ 168 | { 169 | "Effect": "Allow", 170 | "Principal": { 171 | "Service": "appsync.amazonaws.com" 172 | }, 173 | "Action": "sts:AssumeRole" 174 | } 175 | ] 176 | }, 177 | "Policies": [ 178 | { 179 | "PolicyName": "ElasticsearchAccess", 180 | "PolicyDocument": { 181 | "Version": "2012-10-17", 182 | "Statement": [ 183 | { 184 | "Action": [ 185 | "es:ESHttpPost", 186 | "es:ESHttpDelete", 187 | "es:ESHttpHead", 188 | "es:ESHttpGet", 189 | "es:ESHttpPost", 190 | "es:ESHttpPut" 191 | ], 192 | "Effect": "Allow", 193 | "Resource": { 194 | "Fn::Join": [ 195 | "", 196 | [ 197 | { 198 | "Fn::GetAtt": [ 199 | "ElasticsearchDomain", 200 | "DomainArn" 201 | ] 202 | }, 203 | "/*" 204 | ] 205 | ] 206 | } 207 | } 208 | ] 209 | } 210 | } 211 | ] 212 | } 213 | }, 214 | "ElasticsearchDataSource": { 215 | "Type": "AWS::AppSync::DataSource", 216 | "Properties": { 217 | "ApiId": { 218 | "Ref": "GetAttGraphQLAPIApiId" 219 | }, 220 | "Name": "ElasticsearchDomain", 221 | "Type": "AMAZON_ELASTICSEARCH", 222 | "ServiceRoleArn": { 223 | "Fn::GetAtt": [ 224 | "ElasticsearchAccessIAMRole", 225 | "Arn" 226 | ] 227 | }, 228 | "ElasticsearchConfig": { 229 | "AwsRegion": { 230 | "Fn::Select": [ 231 | 3, 232 | { 233 | "Fn::Split": [ 234 | ":", 235 | { 236 | "Fn::GetAtt": [ 237 | "ElasticsearchDomain", 238 | "DomainArn" 239 | ] 240 | } 241 | ] 242 | } 243 | ] 244 | }, 245 | "Endpoint": { 246 | "Fn::Join": [ 247 | "", 248 | [ 249 | "https://", 250 | { 251 | "Fn::GetAtt": [ 252 | "ElasticsearchDomain", 253 | "DomainEndpoint" 254 | ] 255 | } 256 | ] 257 | ] 258 | } 259 | } 260 | }, 261 | "DependsOn": "ElasticsearchDomain" 262 | }, 263 | "ElasticsearchDomain": { 264 | "Type": "AWS::Elasticsearch::Domain", 265 | "Properties": { 266 | "DomainName": { 267 | "Fn::Join": [ 268 | "-", 269 | [ 270 | "d", 271 | { 272 | "Ref": "GetAttGraphQLAPIApiId" 273 | } 274 | ] 275 | ] 276 | }, 277 | "ElasticsearchVersion": "6.2", 278 | "ElasticsearchClusterConfig": { 279 | "ZoneAwarenessEnabled": false, 280 | "InstanceCount": { 281 | "Ref": "ElasticsearchInstanceCount" 282 | }, 283 | "InstanceType": { 284 | "Ref": "ElasticsearchInstanceType" 285 | } 286 | }, 287 | "EBSOptions": { 288 | "EBSEnabled": true, 289 | "VolumeType": "gp2", 290 | "VolumeSize": { 291 | "Ref": "ElasticsearchEBSVolumeGB" 292 | } 293 | } 294 | } 295 | }, 296 | "ElasticsearchStreamingLambdaIAMRole": { 297 | "Type": "AWS::IAM::Role", 298 | "Properties": { 299 | "RoleName": { 300 | "Fn::If": [ 301 | "HasEnvironmentParameter", 302 | { 303 | "Fn::Join": [ 304 | "-", 305 | [ 306 | { 307 | "Ref": "ElasticsearchStreamingIAMRoleName" 308 | }, 309 | { 310 | "Ref": "GetAttGraphQLAPIApiId" 311 | }, 312 | { 313 | "Ref": "env" 314 | } 315 | ] 316 | ] 317 | }, 318 | { 319 | "Fn::Join": [ 320 | "-", 321 | [ 322 | { 323 | "Ref": "ElasticsearchStreamingIAMRoleName" 324 | }, 325 | { 326 | "Ref": "GetAttGraphQLAPIApiId" 327 | } 328 | ] 329 | ] 330 | } 331 | ] 332 | }, 333 | "AssumeRolePolicyDocument": { 334 | "Version": "2012-10-17", 335 | "Statement": [ 336 | { 337 | "Effect": "Allow", 338 | "Principal": { 339 | "Service": "lambda.amazonaws.com" 340 | }, 341 | "Action": "sts:AssumeRole" 342 | } 343 | ] 344 | }, 345 | "Policies": [ 346 | { 347 | "PolicyName": "ElasticsearchAccess", 348 | "PolicyDocument": { 349 | "Version": "2012-10-17", 350 | "Statement": [ 351 | { 352 | "Action": [ 353 | "es:ESHttpPost", 354 | "es:ESHttpDelete", 355 | "es:ESHttpHead", 356 | "es:ESHttpGet", 357 | "es:ESHttpPost", 358 | "es:ESHttpPut" 359 | ], 360 | "Effect": "Allow", 361 | "Resource": { 362 | "Fn::Join": [ 363 | "", 364 | [ 365 | { 366 | "Fn::GetAtt": [ 367 | "ElasticsearchDomain", 368 | "DomainArn" 369 | ] 370 | }, 371 | "/*" 372 | ] 373 | ] 374 | } 375 | } 376 | ] 377 | } 378 | }, 379 | { 380 | "PolicyName": "DynamoDBStreamAccess", 381 | "PolicyDocument": { 382 | "Version": "2012-10-17", 383 | "Statement": [ 384 | { 385 | "Action": [ 386 | "dynamodb:DescribeStream", 387 | "dynamodb:GetRecords", 388 | "dynamodb:GetShardIterator", 389 | "dynamodb:ListStreams" 390 | ], 391 | "Effect": "Allow", 392 | "Resource": [ 393 | "*" 394 | ] 395 | } 396 | ] 397 | } 398 | }, 399 | { 400 | "PolicyName": "CloudWatchLogsAccess", 401 | "PolicyDocument": { 402 | "Version": "2012-10-17", 403 | "Statement": [ 404 | { 405 | "Effect": "Allow", 406 | "Action": [ 407 | "logs:CreateLogGroup", 408 | "logs:CreateLogStream", 409 | "logs:PutLogEvents" 410 | ], 411 | "Resource": "arn:aws:logs:*:*:*" 412 | } 413 | ] 414 | } 415 | } 416 | ] 417 | } 418 | }, 419 | "ElasticsearchStreamingLambdaFunction": { 420 | "Type": "AWS::Lambda::Function", 421 | "Properties": { 422 | "Code": { 423 | "S3Bucket": { 424 | "Ref": "S3DeploymentBucket" 425 | }, 426 | "S3Key": { 427 | "Fn::Join": [ 428 | "/", 429 | [ 430 | { 431 | "Ref": "S3DeploymentRootKey" 432 | }, 433 | "functions", 434 | { 435 | "Fn::Join": [ 436 | ".", 437 | [ 438 | "ElasticsearchStreamingLambdaFunction", 439 | "zip" 440 | ] 441 | ] 442 | } 443 | ] 444 | ] 445 | } 446 | }, 447 | "FunctionName": { 448 | "Fn::If": [ 449 | "HasEnvironmentParameter", 450 | { 451 | "Fn::Join": [ 452 | "-", 453 | [ 454 | { 455 | "Ref": "ElasticsearchStreamingFunctionName" 456 | }, 457 | { 458 | "Ref": "GetAttGraphQLAPIApiId" 459 | }, 460 | { 461 | "Ref": "env" 462 | } 463 | ] 464 | ] 465 | }, 466 | { 467 | "Fn::Join": [ 468 | "-", 469 | [ 470 | { 471 | "Ref": "ElasticsearchStreamingFunctionName" 472 | }, 473 | { 474 | "Ref": "GetAttGraphQLAPIApiId" 475 | } 476 | ] 477 | ] 478 | } 479 | ] 480 | }, 481 | "Handler": { 482 | "Ref": "ElasticsearchStreamingLambdaHandlerName" 483 | }, 484 | "Role": { 485 | "Fn::GetAtt": [ 486 | "ElasticsearchStreamingLambdaIAMRole", 487 | "Arn" 488 | ] 489 | }, 490 | "Runtime": { 491 | "Ref": "ElasticsearchStreamingLambdaRuntime" 492 | }, 493 | "Environment": { 494 | "Variables": { 495 | "ES_ENDPOINT": { 496 | "Fn::Join": [ 497 | "", 498 | [ 499 | "https://", 500 | { 501 | "Fn::GetAtt": [ 502 | "ElasticsearchDomain", 503 | "DomainEndpoint" 504 | ] 505 | } 506 | ] 507 | ] 508 | }, 509 | "ES_REGION": { 510 | "Fn::Select": [ 511 | 3, 512 | { 513 | "Fn::Split": [ 514 | ":", 515 | { 516 | "Fn::GetAtt": [ 517 | "ElasticsearchDomain", 518 | "DomainArn" 519 | ] 520 | } 521 | ] 522 | } 523 | ] 524 | }, 525 | "DEBUG": { 526 | "Ref": "ElasticsearchDebugStreamingLambda" 527 | } 528 | } 529 | } 530 | }, 531 | "DependsOn": [ 532 | "ElasticsearchStreamingLambdaIAMRole", 533 | "ElasticsearchDomain" 534 | ] 535 | }, 536 | "SearchableTodoLambdaMapping": { 537 | "Type": "AWS::Lambda::EventSourceMapping", 538 | "Properties": { 539 | "BatchSize": 1, 540 | "Enabled": true, 541 | "EventSourceArn": { 542 | "Fn::ImportValue": { 543 | "Fn::Join": [ 544 | ":", 545 | [ 546 | { 547 | "Ref": "AppSyncApiId" 548 | }, 549 | "GetAtt", 550 | "TodoTable", 551 | "StreamArn" 552 | ] 553 | ] 554 | } 555 | }, 556 | "FunctionName": { 557 | "Fn::GetAtt": [ 558 | "ElasticsearchStreamingLambdaFunction", 559 | "Arn" 560 | ] 561 | }, 562 | "StartingPosition": "LATEST" 563 | }, 564 | "DependsOn": [ 565 | "ElasticsearchStreamingLambdaFunction" 566 | ] 567 | }, 568 | "SearchTodoResolver": { 569 | "Type": "AWS::AppSync::Resolver", 570 | "Properties": { 571 | "ApiId": { 572 | "Ref": "GetAttGraphQLAPIApiId" 573 | }, 574 | "DataSourceName": { 575 | "Fn::GetAtt": [ 576 | "ElasticsearchDataSource", 577 | "Name" 578 | ] 579 | }, 580 | "FieldName": "searchTodos", 581 | "TypeName": "Query", 582 | "RequestMappingTemplateS3Location": { 583 | "Fn::Sub": [ 584 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 585 | { 586 | "S3DeploymentBucket": { 587 | "Ref": "S3DeploymentBucket" 588 | }, 589 | "S3DeploymentRootKey": { 590 | "Ref": "S3DeploymentRootKey" 591 | }, 592 | "ResolverFileName": { 593 | "Fn::Join": [ 594 | ".", 595 | [ 596 | "Query", 597 | "searchTodos", 598 | "req", 599 | "vtl" 600 | ] 601 | ] 602 | } 603 | } 604 | ] 605 | }, 606 | "ResponseMappingTemplateS3Location": { 607 | "Fn::Sub": [ 608 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 609 | { 610 | "S3DeploymentBucket": { 611 | "Ref": "S3DeploymentBucket" 612 | }, 613 | "S3DeploymentRootKey": { 614 | "Ref": "S3DeploymentRootKey" 615 | }, 616 | "ResolverFileName": { 617 | "Fn::Join": [ 618 | ".", 619 | [ 620 | "Query", 621 | "searchTodos", 622 | "res", 623 | "vtl" 624 | ] 625 | ] 626 | } 627 | } 628 | ] 629 | } 630 | } 631 | } 632 | }, 633 | "Outputs": {}, 634 | "Conditions": { 635 | "HasEnvironmentParameter": { 636 | "Fn::Not": [ 637 | { 638 | "Fn::Equals": [ 639 | { 640 | "Ref": "env" 641 | }, 642 | "NONE" 643 | ] 644 | } 645 | ] 646 | } 647 | } 648 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/stacks/Todo.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiName": { 7 | "Type": "String", 8 | "Description": "The name of the AppSync API", 9 | "Default": "AppSyncSimpleTransform" 10 | }, 11 | "ElasticsearchAccessIAMRoleName": { 12 | "Type": "String", 13 | "Description": "The name of the IAM role assumed by AppSync for Elasticsearch.", 14 | "Default": "AppSyncElasticsearchAccess" 15 | }, 16 | "ElasticsearchStreamingLambdaHandlerName": { 17 | "Type": "String", 18 | "Description": "The name of the lambda handler.", 19 | "Default": "python_streaming_function.lambda_handler" 20 | }, 21 | "ElasticsearchStreamingLambdaRuntime": { 22 | "Type": "String", 23 | "Description": "The lambda runtime (https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-Runtime)", 24 | "Default": "python3.6" 25 | }, 26 | "ElasticsearchStreamingFunctionName": { 27 | "Type": "String", 28 | "Description": "The name of the streaming lambda function.", 29 | "Default": "DdbToEsFn" 30 | }, 31 | "ElasticsearchStreamingIAMRoleName": { 32 | "Type": "String", 33 | "Description": "The name of the streaming lambda function IAM role.", 34 | "Default": "SearchableLambdaIAMRole" 35 | }, 36 | "ElasticsearchDebugStreamingLambda": { 37 | "Type": "Number", 38 | "Description": "Enable debug logs for the Dynamo -> ES streaming lambda.", 39 | "Default": 1, 40 | "AllowedValues": [ 41 | 0, 42 | 1 43 | ] 44 | }, 45 | "ElasticsearchInstanceCount": { 46 | "Type": "Number", 47 | "Description": "The number of instances to launch into the Elasticsearch domain.", 48 | "Default": 1 49 | }, 50 | "ElasticsearchDomainName": { 51 | "Type": "String", 52 | "Description": "The name of the Elasticsearch domain.", 53 | "Default": "appsync-elasticsearch-domain", 54 | "AllowedPattern": "^[a-z][a-z0-9-]*$", 55 | "MinLength": 1, 56 | "MaxLength": 28 57 | }, 58 | "ElasticsearchInstanceType": { 59 | "Type": "String", 60 | "Description": "The type of instance to launch into the Elasticsearch domain.", 61 | "Default": "t2.small.elasticsearch", 62 | "AllowedValues": [ 63 | "t2.small.elasticsearch", 64 | "t2.medium.elasticsearch", 65 | "c4.large.elasticsearch", 66 | "c4.xlarge.elasticsearch", 67 | "c4.2xlarge.elasticsearch", 68 | "c4.4xlarge.elasticsearch", 69 | "c4.8xlarge.elasticsearch", 70 | "m3.medium.elasticsearch", 71 | "m3.large.elasticsearch", 72 | "m3.xlarge.elasticsearch", 73 | "m3.2xlarge.elasticsearch", 74 | "m4.large.elasticsearch", 75 | "m4.xlarge.elasticsearch", 76 | "m4.2xlarge.elasticsearch", 77 | "m4.4xlarge.elasticsearch", 78 | "m4.10xlarge.elasticsearch", 79 | "r3.large.elasticsearch", 80 | "r3.xlarge.elasticsearch", 81 | "r3.2xlarge.elasticsearch", 82 | "r3.4xlarge.elasticsearch", 83 | "r3.8xlarge.elasticsearch", 84 | "r4.large.elasticsearch", 85 | "r4.xlarge.elasticsearch", 86 | "r4.2xlarge.elasticsearch", 87 | "r4.4xlarge.elasticsearch", 88 | "r4.8xlarge.elasticsearch", 89 | "r4.16xlarge.elasticsearch", 90 | "i2.xlarge.elasticsearch", 91 | "i2.2xlarge.elasticsearch", 92 | "i3.large.elasticsearch", 93 | "i3.xlarge.elasticsearch", 94 | "i3.2xlarge.elasticsearch", 95 | "i3.4xlarge.elasticsearch", 96 | "i3.8xlarge.elasticsearch", 97 | "i3.16xlarge.elasticsearch" 98 | ] 99 | }, 100 | "ElasticsearchEBSVolumeGB": { 101 | "Type": "Number", 102 | "Description": "The size in GB of the EBS volumes that contain our data.", 103 | "Default": 20 104 | }, 105 | "env": { 106 | "Type": "String", 107 | "Description": "The environment name. e.g. Dev, Test, or Production", 108 | "Default": "NONE" 109 | }, 110 | "S3DeploymentBucket": { 111 | "Type": "String", 112 | "Description": "The S3 bucket containing all deployment assets for the project." 113 | }, 114 | "S3DeploymentRootKey": { 115 | "Type": "String", 116 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root of the deployment directory." 117 | }, 118 | "AppSyncApiId": { 119 | "Type": "String", 120 | "Description": "The id of the AppSync API associated with this project." 121 | }, 122 | "GetAttGraphQLAPIApiId": { 123 | "Type": "String", 124 | "Description": "Auto-generated parameter that forwards Fn.GetAtt(GraphQLAPI, ApiId) through to nested stacks." 125 | } 126 | }, 127 | "Resources": { 128 | "TodoTable": { 129 | "Type": "AWS::DynamoDB::Table", 130 | "Properties": { 131 | "TableName": { 132 | "Fn::If": [ 133 | "HasEnvironmentParameter", 134 | { 135 | "Fn::Join": [ 136 | "-", 137 | [ 138 | "Todo", 139 | { 140 | "Ref": "GetAttGraphQLAPIApiId" 141 | }, 142 | { 143 | "Ref": "env" 144 | } 145 | ] 146 | ] 147 | }, 148 | { 149 | "Fn::Join": [ 150 | "-", 151 | [ 152 | "Todo", 153 | { 154 | "Ref": "GetAttGraphQLAPIApiId" 155 | } 156 | ] 157 | ] 158 | } 159 | ] 160 | }, 161 | "KeySchema": [ 162 | { 163 | "AttributeName": "id", 164 | "KeyType": "HASH" 165 | } 166 | ], 167 | "AttributeDefinitions": [ 168 | { 169 | "AttributeName": "id", 170 | "AttributeType": "S" 171 | } 172 | ], 173 | "StreamSpecification": { 174 | "StreamViewType": "NEW_AND_OLD_IMAGES" 175 | }, 176 | "BillingMode": "PAY_PER_REQUEST", 177 | "SSESpecification": { 178 | "SSEEnabled": true 179 | } 180 | } 181 | }, 182 | "TodoIAMRole": { 183 | "Type": "AWS::IAM::Role", 184 | "Properties": { 185 | "RoleName": { 186 | "Fn::If": [ 187 | "HasEnvironmentParameter", 188 | { 189 | "Fn::Join": [ 190 | "-", 191 | [ 192 | "TodoTable", 193 | "role", 194 | { 195 | "Ref": "GetAttGraphQLAPIApiId" 196 | }, 197 | { 198 | "Ref": "env" 199 | } 200 | ] 201 | ] 202 | }, 203 | { 204 | "Fn::Join": [ 205 | "-", 206 | [ 207 | "TodoTable", 208 | "role", 209 | { 210 | "Ref": "GetAttGraphQLAPIApiId" 211 | } 212 | ] 213 | ] 214 | } 215 | ] 216 | }, 217 | "AssumeRolePolicyDocument": { 218 | "Version": "2012-10-17", 219 | "Statement": [ 220 | { 221 | "Effect": "Allow", 222 | "Principal": { 223 | "Service": "appsync.amazonaws.com" 224 | }, 225 | "Action": "sts:AssumeRole" 226 | } 227 | ] 228 | }, 229 | "Policies": [ 230 | { 231 | "PolicyName": "DynamoDBAccess", 232 | "PolicyDocument": { 233 | "Version": "2012-10-17", 234 | "Statement": [ 235 | { 236 | "Effect": "Allow", 237 | "Action": [ 238 | "dynamodb:BatchGetItem", 239 | "dynamodb:BatchWriteItem", 240 | "dynamodb:PutItem", 241 | "dynamodb:DeleteItem", 242 | "dynamodb:GetItem", 243 | "dynamodb:Scan", 244 | "dynamodb:Query", 245 | "dynamodb:UpdateItem" 246 | ], 247 | "Resource": [ 248 | { 249 | "Fn::GetAtt": [ 250 | "TodoTable", 251 | "Arn" 252 | ] 253 | }, 254 | { 255 | "Fn::Join": [ 256 | "/", 257 | [ 258 | { 259 | "Fn::GetAtt": [ 260 | "TodoTable", 261 | "Arn" 262 | ] 263 | }, 264 | "*" 265 | ] 266 | ] 267 | } 268 | ] 269 | } 270 | ] 271 | } 272 | } 273 | ] 274 | } 275 | }, 276 | "TodoDataSource": { 277 | "Type": "AWS::AppSync::DataSource", 278 | "Properties": { 279 | "ApiId": { 280 | "Ref": "GetAttGraphQLAPIApiId" 281 | }, 282 | "Name": "TodoTable", 283 | "Type": "AMAZON_DYNAMODB", 284 | "ServiceRoleArn": { 285 | "Fn::GetAtt": [ 286 | "TodoIAMRole", 287 | "Arn" 288 | ] 289 | }, 290 | "DynamoDBConfig": { 291 | "AwsRegion": { 292 | "Fn::Select": [ 293 | 3, 294 | { 295 | "Fn::Split": [ 296 | ":", 297 | { 298 | "Fn::GetAtt": [ 299 | "TodoTable", 300 | "Arn" 301 | ] 302 | } 303 | ] 304 | } 305 | ] 306 | }, 307 | "TableName": { 308 | "Ref": "TodoTable" 309 | } 310 | } 311 | }, 312 | "DependsOn": [ 313 | "TodoTable", 314 | "TodoIAMRole" 315 | ] 316 | }, 317 | "GetTodoResolver": { 318 | "Type": "AWS::AppSync::Resolver", 319 | "Properties": { 320 | "ApiId": { 321 | "Ref": "GetAttGraphQLAPIApiId" 322 | }, 323 | "DataSourceName": { 324 | "Fn::GetAtt": [ 325 | "TodoDataSource", 326 | "Name" 327 | ] 328 | }, 329 | "FieldName": "getTodo", 330 | "TypeName": "Query", 331 | "RequestMappingTemplateS3Location": { 332 | "Fn::Sub": [ 333 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 334 | { 335 | "S3DeploymentBucket": { 336 | "Ref": "S3DeploymentBucket" 337 | }, 338 | "S3DeploymentRootKey": { 339 | "Ref": "S3DeploymentRootKey" 340 | }, 341 | "ResolverFileName": { 342 | "Fn::Join": [ 343 | ".", 344 | [ 345 | "Query", 346 | "getTodo", 347 | "req", 348 | "vtl" 349 | ] 350 | ] 351 | } 352 | } 353 | ] 354 | }, 355 | "ResponseMappingTemplateS3Location": { 356 | "Fn::Sub": [ 357 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 358 | { 359 | "S3DeploymentBucket": { 360 | "Ref": "S3DeploymentBucket" 361 | }, 362 | "S3DeploymentRootKey": { 363 | "Ref": "S3DeploymentRootKey" 364 | }, 365 | "ResolverFileName": { 366 | "Fn::Join": [ 367 | ".", 368 | [ 369 | "Query", 370 | "getTodo", 371 | "res", 372 | "vtl" 373 | ] 374 | ] 375 | } 376 | } 377 | ] 378 | } 379 | } 380 | }, 381 | "ListTodoResolver": { 382 | "Type": "AWS::AppSync::Resolver", 383 | "Properties": { 384 | "ApiId": { 385 | "Ref": "GetAttGraphQLAPIApiId" 386 | }, 387 | "DataSourceName": { 388 | "Fn::GetAtt": [ 389 | "TodoDataSource", 390 | "Name" 391 | ] 392 | }, 393 | "FieldName": "listTodos", 394 | "TypeName": "Query", 395 | "RequestMappingTemplateS3Location": { 396 | "Fn::Sub": [ 397 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 398 | { 399 | "S3DeploymentBucket": { 400 | "Ref": "S3DeploymentBucket" 401 | }, 402 | "S3DeploymentRootKey": { 403 | "Ref": "S3DeploymentRootKey" 404 | }, 405 | "ResolverFileName": { 406 | "Fn::Join": [ 407 | ".", 408 | [ 409 | "Query", 410 | "listTodos", 411 | "req", 412 | "vtl" 413 | ] 414 | ] 415 | } 416 | } 417 | ] 418 | }, 419 | "ResponseMappingTemplateS3Location": { 420 | "Fn::Sub": [ 421 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 422 | { 423 | "S3DeploymentBucket": { 424 | "Ref": "S3DeploymentBucket" 425 | }, 426 | "S3DeploymentRootKey": { 427 | "Ref": "S3DeploymentRootKey" 428 | }, 429 | "ResolverFileName": { 430 | "Fn::Join": [ 431 | ".", 432 | [ 433 | "Query", 434 | "listTodos", 435 | "res", 436 | "vtl" 437 | ] 438 | ] 439 | } 440 | } 441 | ] 442 | } 443 | } 444 | }, 445 | "CreateTodoResolver": { 446 | "Type": "AWS::AppSync::Resolver", 447 | "Properties": { 448 | "ApiId": { 449 | "Ref": "GetAttGraphQLAPIApiId" 450 | }, 451 | "DataSourceName": { 452 | "Fn::GetAtt": [ 453 | "TodoDataSource", 454 | "Name" 455 | ] 456 | }, 457 | "FieldName": "createTodo", 458 | "TypeName": "Mutation", 459 | "RequestMappingTemplateS3Location": { 460 | "Fn::Sub": [ 461 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 462 | { 463 | "S3DeploymentBucket": { 464 | "Ref": "S3DeploymentBucket" 465 | }, 466 | "S3DeploymentRootKey": { 467 | "Ref": "S3DeploymentRootKey" 468 | }, 469 | "ResolverFileName": { 470 | "Fn::Join": [ 471 | ".", 472 | [ 473 | "Mutation", 474 | "createTodo", 475 | "req", 476 | "vtl" 477 | ] 478 | ] 479 | } 480 | } 481 | ] 482 | }, 483 | "ResponseMappingTemplateS3Location": { 484 | "Fn::Sub": [ 485 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 486 | { 487 | "S3DeploymentBucket": { 488 | "Ref": "S3DeploymentBucket" 489 | }, 490 | "S3DeploymentRootKey": { 491 | "Ref": "S3DeploymentRootKey" 492 | }, 493 | "ResolverFileName": { 494 | "Fn::Join": [ 495 | ".", 496 | [ 497 | "Mutation", 498 | "createTodo", 499 | "res", 500 | "vtl" 501 | ] 502 | ] 503 | } 504 | } 505 | ] 506 | } 507 | } 508 | }, 509 | "UpdateTodoResolver": { 510 | "Type": "AWS::AppSync::Resolver", 511 | "Properties": { 512 | "ApiId": { 513 | "Ref": "GetAttGraphQLAPIApiId" 514 | }, 515 | "DataSourceName": { 516 | "Fn::GetAtt": [ 517 | "TodoDataSource", 518 | "Name" 519 | ] 520 | }, 521 | "FieldName": "updateTodo", 522 | "TypeName": "Mutation", 523 | "RequestMappingTemplateS3Location": { 524 | "Fn::Sub": [ 525 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 526 | { 527 | "S3DeploymentBucket": { 528 | "Ref": "S3DeploymentBucket" 529 | }, 530 | "S3DeploymentRootKey": { 531 | "Ref": "S3DeploymentRootKey" 532 | }, 533 | "ResolverFileName": { 534 | "Fn::Join": [ 535 | ".", 536 | [ 537 | "Mutation", 538 | "updateTodo", 539 | "req", 540 | "vtl" 541 | ] 542 | ] 543 | } 544 | } 545 | ] 546 | }, 547 | "ResponseMappingTemplateS3Location": { 548 | "Fn::Sub": [ 549 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 550 | { 551 | "S3DeploymentBucket": { 552 | "Ref": "S3DeploymentBucket" 553 | }, 554 | "S3DeploymentRootKey": { 555 | "Ref": "S3DeploymentRootKey" 556 | }, 557 | "ResolverFileName": { 558 | "Fn::Join": [ 559 | ".", 560 | [ 561 | "Mutation", 562 | "updateTodo", 563 | "res", 564 | "vtl" 565 | ] 566 | ] 567 | } 568 | } 569 | ] 570 | } 571 | } 572 | }, 573 | "DeleteTodoResolver": { 574 | "Type": "AWS::AppSync::Resolver", 575 | "Properties": { 576 | "ApiId": { 577 | "Ref": "GetAttGraphQLAPIApiId" 578 | }, 579 | "DataSourceName": { 580 | "Fn::GetAtt": [ 581 | "TodoDataSource", 582 | "Name" 583 | ] 584 | }, 585 | "FieldName": "deleteTodo", 586 | "TypeName": "Mutation", 587 | "RequestMappingTemplateS3Location": { 588 | "Fn::Sub": [ 589 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 590 | { 591 | "S3DeploymentBucket": { 592 | "Ref": "S3DeploymentBucket" 593 | }, 594 | "S3DeploymentRootKey": { 595 | "Ref": "S3DeploymentRootKey" 596 | }, 597 | "ResolverFileName": { 598 | "Fn::Join": [ 599 | ".", 600 | [ 601 | "Mutation", 602 | "deleteTodo", 603 | "req", 604 | "vtl" 605 | ] 606 | ] 607 | } 608 | } 609 | ] 610 | }, 611 | "ResponseMappingTemplateS3Location": { 612 | "Fn::Sub": [ 613 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 614 | { 615 | "S3DeploymentBucket": { 616 | "Ref": "S3DeploymentBucket" 617 | }, 618 | "S3DeploymentRootKey": { 619 | "Ref": "S3DeploymentRootKey" 620 | }, 621 | "ResolverFileName": { 622 | "Fn::Join": [ 623 | ".", 624 | [ 625 | "Mutation", 626 | "deleteTodo", 627 | "res", 628 | "vtl" 629 | ] 630 | ] 631 | } 632 | } 633 | ] 634 | } 635 | } 636 | } 637 | }, 638 | "Outputs": { 639 | "GetAttTodoDataSourceName": { 640 | "Value": { 641 | "Fn::GetAtt": [ 642 | "TodoDataSource", 643 | "Name" 644 | ] 645 | }, 646 | "Export": { 647 | "Name": { 648 | "Fn::Join": [ 649 | ":", 650 | [ 651 | { 652 | "Ref": "AppSyncApiId" 653 | }, 654 | "GetAtt", 655 | "TodoDataSource", 656 | "Name" 657 | ] 658 | ] 659 | } 660 | } 661 | }, 662 | "GetAttTodoTableStreamArn": { 663 | "Value": { 664 | "Fn::GetAtt": [ 665 | "TodoTable", 666 | "StreamArn" 667 | ] 668 | }, 669 | "Export": { 670 | "Name": { 671 | "Fn::Join": [ 672 | ":", 673 | [ 674 | { 675 | "Ref": "AppSyncApiId" 676 | }, 677 | "GetAtt", 678 | "TodoTable", 679 | "StreamArn" 680 | ] 681 | ] 682 | } 683 | } 684 | } 685 | }, 686 | "Conditions": { 687 | "HasEnvironmentParameter": { 688 | "Fn::Not": [ 689 | { 690 | "Fn::Equals": [ 691 | { 692 | "Ref": "env" 693 | }, 694 | "NONE" 695 | ] 696 | } 697 | ] 698 | } 699 | } 700 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifynestedsample/build/stacks/Comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiName": { 7 | "Type": "String", 8 | "Description": "The name of the AppSync API", 9 | "Default": "AppSyncSimpleTransform" 10 | }, 11 | "ElasticsearchAccessIAMRoleName": { 12 | "Type": "String", 13 | "Description": "The name of the IAM role assumed by AppSync for Elasticsearch.", 14 | "Default": "AppSyncElasticsearchAccess" 15 | }, 16 | "ElasticsearchStreamingLambdaHandlerName": { 17 | "Type": "String", 18 | "Description": "The name of the lambda handler.", 19 | "Default": "python_streaming_function.lambda_handler" 20 | }, 21 | "ElasticsearchStreamingLambdaRuntime": { 22 | "Type": "String", 23 | "Description": "The lambda runtime (https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-Runtime)", 24 | "Default": "python3.6" 25 | }, 26 | "ElasticsearchStreamingFunctionName": { 27 | "Type": "String", 28 | "Description": "The name of the streaming lambda function.", 29 | "Default": "DdbToEsFn" 30 | }, 31 | "ElasticsearchStreamingIAMRoleName": { 32 | "Type": "String", 33 | "Description": "The name of the streaming lambda function IAM role.", 34 | "Default": "SearchableLambdaIAMRole" 35 | }, 36 | "ElasticsearchDebugStreamingLambda": { 37 | "Type": "Number", 38 | "Description": "Enable debug logs for the Dynamo -> ES streaming lambda.", 39 | "Default": 1, 40 | "AllowedValues": [ 41 | 0, 42 | 1 43 | ] 44 | }, 45 | "ElasticsearchInstanceCount": { 46 | "Type": "Number", 47 | "Description": "The number of instances to launch into the Elasticsearch domain.", 48 | "Default": 1 49 | }, 50 | "ElasticsearchDomainName": { 51 | "Type": "String", 52 | "Description": "The name of the Elasticsearch domain.", 53 | "Default": "appsync-elasticsearch-domain", 54 | "AllowedPattern": "^[a-z][a-z0-9-]*$", 55 | "MinLength": 1, 56 | "MaxLength": 28 57 | }, 58 | "ElasticsearchInstanceType": { 59 | "Type": "String", 60 | "Description": "The type of instance to launch into the Elasticsearch domain.", 61 | "Default": "t2.small.elasticsearch", 62 | "AllowedValues": [ 63 | "t2.small.elasticsearch", 64 | "t2.medium.elasticsearch", 65 | "c4.large.elasticsearch", 66 | "c4.xlarge.elasticsearch", 67 | "c4.2xlarge.elasticsearch", 68 | "c4.4xlarge.elasticsearch", 69 | "c4.8xlarge.elasticsearch", 70 | "m3.medium.elasticsearch", 71 | "m3.large.elasticsearch", 72 | "m3.xlarge.elasticsearch", 73 | "m3.2xlarge.elasticsearch", 74 | "m4.large.elasticsearch", 75 | "m4.xlarge.elasticsearch", 76 | "m4.2xlarge.elasticsearch", 77 | "m4.4xlarge.elasticsearch", 78 | "m4.10xlarge.elasticsearch", 79 | "r3.large.elasticsearch", 80 | "r3.xlarge.elasticsearch", 81 | "r3.2xlarge.elasticsearch", 82 | "r3.4xlarge.elasticsearch", 83 | "r3.8xlarge.elasticsearch", 84 | "r4.large.elasticsearch", 85 | "r4.xlarge.elasticsearch", 86 | "r4.2xlarge.elasticsearch", 87 | "r4.4xlarge.elasticsearch", 88 | "r4.8xlarge.elasticsearch", 89 | "r4.16xlarge.elasticsearch", 90 | "i2.xlarge.elasticsearch", 91 | "i2.2xlarge.elasticsearch", 92 | "i3.large.elasticsearch", 93 | "i3.xlarge.elasticsearch", 94 | "i3.2xlarge.elasticsearch", 95 | "i3.4xlarge.elasticsearch", 96 | "i3.8xlarge.elasticsearch", 97 | "i3.16xlarge.elasticsearch" 98 | ] 99 | }, 100 | "ElasticsearchEBSVolumeGB": { 101 | "Type": "Number", 102 | "Description": "The size in GB of the EBS volumes that contain our data.", 103 | "Default": 20 104 | }, 105 | "env": { 106 | "Type": "String", 107 | "Description": "The environment name. e.g. Dev, Test, or Production", 108 | "Default": "NONE" 109 | }, 110 | "S3DeploymentBucket": { 111 | "Type": "String", 112 | "Description": "The S3 bucket containing all deployment assets for the project." 113 | }, 114 | "S3DeploymentRootKey": { 115 | "Type": "String", 116 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root of the deployment directory." 117 | }, 118 | "AppSyncApiId": { 119 | "Type": "String", 120 | "Description": "The id of the AppSync API associated with this project." 121 | }, 122 | "GetAttGraphQLAPIApiId": { 123 | "Type": "String", 124 | "Description": "Auto-generated parameter that forwards Fn.GetAtt(GraphQLAPI, ApiId) through to nested stacks." 125 | } 126 | }, 127 | "Resources": { 128 | "CommentTable": { 129 | "Type": "AWS::DynamoDB::Table", 130 | "Properties": { 131 | "TableName": { 132 | "Fn::If": [ 133 | "HasEnvironmentParameter", 134 | { 135 | "Fn::Join": [ 136 | "-", 137 | [ 138 | "Comment", 139 | { 140 | "Ref": "GetAttGraphQLAPIApiId" 141 | }, 142 | { 143 | "Ref": "env" 144 | } 145 | ] 146 | ] 147 | }, 148 | { 149 | "Fn::Join": [ 150 | "-", 151 | [ 152 | "Comment", 153 | { 154 | "Ref": "GetAttGraphQLAPIApiId" 155 | } 156 | ] 157 | ] 158 | } 159 | ] 160 | }, 161 | "KeySchema": [ 162 | { 163 | "AttributeName": "id", 164 | "KeyType": "HASH" 165 | } 166 | ], 167 | "AttributeDefinitions": [ 168 | { 169 | "AttributeName": "id", 170 | "AttributeType": "S" 171 | }, 172 | { 173 | "AttributeName": "commentTodoId", 174 | "AttributeType": "S" 175 | }, 176 | { 177 | "AttributeName": "createdAt", 178 | "AttributeType": "S" 179 | } 180 | ], 181 | "StreamSpecification": { 182 | "StreamViewType": "NEW_AND_OLD_IMAGES" 183 | }, 184 | "BillingMode": "PAY_PER_REQUEST", 185 | "SSESpecification": { 186 | "SSEEnabled": true 187 | }, 188 | "GlobalSecondaryIndexes": [ 189 | { 190 | "IndexName": "gsi-TodoComments", 191 | "KeySchema": [ 192 | { 193 | "AttributeName": "commentTodoId", 194 | "KeyType": "HASH" 195 | }, 196 | { 197 | "AttributeName": "createdAt", 198 | "KeyType": "RANGE" 199 | } 200 | ], 201 | "Projection": { 202 | "ProjectionType": "ALL" 203 | } 204 | } 205 | ] 206 | } 207 | }, 208 | "CommentIAMRole": { 209 | "Type": "AWS::IAM::Role", 210 | "Properties": { 211 | "RoleName": { 212 | "Fn::If": [ 213 | "HasEnvironmentParameter", 214 | { 215 | "Fn::Join": [ 216 | "-", 217 | [ 218 | "CommentTable", 219 | "role", 220 | { 221 | "Ref": "GetAttGraphQLAPIApiId" 222 | }, 223 | { 224 | "Ref": "env" 225 | } 226 | ] 227 | ] 228 | }, 229 | { 230 | "Fn::Join": [ 231 | "-", 232 | [ 233 | "CommentTable", 234 | "role", 235 | { 236 | "Ref": "GetAttGraphQLAPIApiId" 237 | } 238 | ] 239 | ] 240 | } 241 | ] 242 | }, 243 | "AssumeRolePolicyDocument": { 244 | "Version": "2012-10-17", 245 | "Statement": [ 246 | { 247 | "Effect": "Allow", 248 | "Principal": { 249 | "Service": "appsync.amazonaws.com" 250 | }, 251 | "Action": "sts:AssumeRole" 252 | } 253 | ] 254 | }, 255 | "Policies": [ 256 | { 257 | "PolicyName": "DynamoDBAccess", 258 | "PolicyDocument": { 259 | "Version": "2012-10-17", 260 | "Statement": [ 261 | { 262 | "Effect": "Allow", 263 | "Action": [ 264 | "dynamodb:BatchGetItem", 265 | "dynamodb:BatchWriteItem", 266 | "dynamodb:PutItem", 267 | "dynamodb:DeleteItem", 268 | "dynamodb:GetItem", 269 | "dynamodb:Scan", 270 | "dynamodb:Query", 271 | "dynamodb:UpdateItem" 272 | ], 273 | "Resource": [ 274 | { 275 | "Fn::GetAtt": [ 276 | "CommentTable", 277 | "Arn" 278 | ] 279 | }, 280 | { 281 | "Fn::Join": [ 282 | "/", 283 | [ 284 | { 285 | "Fn::GetAtt": [ 286 | "CommentTable", 287 | "Arn" 288 | ] 289 | }, 290 | "*" 291 | ] 292 | ] 293 | } 294 | ] 295 | } 296 | ] 297 | } 298 | } 299 | ] 300 | } 301 | }, 302 | "CommentDataSource": { 303 | "Type": "AWS::AppSync::DataSource", 304 | "Properties": { 305 | "ApiId": { 306 | "Ref": "GetAttGraphQLAPIApiId" 307 | }, 308 | "Name": "CommentTable", 309 | "Type": "AMAZON_DYNAMODB", 310 | "ServiceRoleArn": { 311 | "Fn::GetAtt": [ 312 | "CommentIAMRole", 313 | "Arn" 314 | ] 315 | }, 316 | "DynamoDBConfig": { 317 | "AwsRegion": { 318 | "Fn::Select": [ 319 | 3, 320 | { 321 | "Fn::Split": [ 322 | ":", 323 | { 324 | "Fn::GetAtt": [ 325 | "CommentTable", 326 | "Arn" 327 | ] 328 | } 329 | ] 330 | } 331 | ] 332 | }, 333 | "TableName": { 334 | "Ref": "CommentTable" 335 | } 336 | } 337 | }, 338 | "DependsOn": [ 339 | "CommentTable", 340 | "CommentIAMRole" 341 | ] 342 | }, 343 | "GetCommentResolver": { 344 | "Type": "AWS::AppSync::Resolver", 345 | "Properties": { 346 | "ApiId": { 347 | "Ref": "GetAttGraphQLAPIApiId" 348 | }, 349 | "DataSourceName": { 350 | "Fn::GetAtt": [ 351 | "CommentDataSource", 352 | "Name" 353 | ] 354 | }, 355 | "FieldName": "getComment", 356 | "TypeName": "Query", 357 | "RequestMappingTemplateS3Location": { 358 | "Fn::Sub": [ 359 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 360 | { 361 | "S3DeploymentBucket": { 362 | "Ref": "S3DeploymentBucket" 363 | }, 364 | "S3DeploymentRootKey": { 365 | "Ref": "S3DeploymentRootKey" 366 | }, 367 | "ResolverFileName": { 368 | "Fn::Join": [ 369 | ".", 370 | [ 371 | "Query", 372 | "getComment", 373 | "req", 374 | "vtl" 375 | ] 376 | ] 377 | } 378 | } 379 | ] 380 | }, 381 | "ResponseMappingTemplateS3Location": { 382 | "Fn::Sub": [ 383 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 384 | { 385 | "S3DeploymentBucket": { 386 | "Ref": "S3DeploymentBucket" 387 | }, 388 | "S3DeploymentRootKey": { 389 | "Ref": "S3DeploymentRootKey" 390 | }, 391 | "ResolverFileName": { 392 | "Fn::Join": [ 393 | ".", 394 | [ 395 | "Query", 396 | "getComment", 397 | "res", 398 | "vtl" 399 | ] 400 | ] 401 | } 402 | } 403 | ] 404 | } 405 | } 406 | }, 407 | "ListCommentResolver": { 408 | "Type": "AWS::AppSync::Resolver", 409 | "Properties": { 410 | "ApiId": { 411 | "Ref": "GetAttGraphQLAPIApiId" 412 | }, 413 | "DataSourceName": { 414 | "Fn::GetAtt": [ 415 | "CommentDataSource", 416 | "Name" 417 | ] 418 | }, 419 | "FieldName": "listComments", 420 | "TypeName": "Query", 421 | "RequestMappingTemplateS3Location": { 422 | "Fn::Sub": [ 423 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 424 | { 425 | "S3DeploymentBucket": { 426 | "Ref": "S3DeploymentBucket" 427 | }, 428 | "S3DeploymentRootKey": { 429 | "Ref": "S3DeploymentRootKey" 430 | }, 431 | "ResolverFileName": { 432 | "Fn::Join": [ 433 | ".", 434 | [ 435 | "Query", 436 | "listComments", 437 | "req", 438 | "vtl" 439 | ] 440 | ] 441 | } 442 | } 443 | ] 444 | }, 445 | "ResponseMappingTemplateS3Location": { 446 | "Fn::Sub": [ 447 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 448 | { 449 | "S3DeploymentBucket": { 450 | "Ref": "S3DeploymentBucket" 451 | }, 452 | "S3DeploymentRootKey": { 453 | "Ref": "S3DeploymentRootKey" 454 | }, 455 | "ResolverFileName": { 456 | "Fn::Join": [ 457 | ".", 458 | [ 459 | "Query", 460 | "listComments", 461 | "res", 462 | "vtl" 463 | ] 464 | ] 465 | } 466 | } 467 | ] 468 | } 469 | } 470 | }, 471 | "CreateCommentResolver": { 472 | "Type": "AWS::AppSync::Resolver", 473 | "Properties": { 474 | "ApiId": { 475 | "Ref": "GetAttGraphQLAPIApiId" 476 | }, 477 | "DataSourceName": { 478 | "Fn::GetAtt": [ 479 | "CommentDataSource", 480 | "Name" 481 | ] 482 | }, 483 | "FieldName": "createComment", 484 | "TypeName": "Mutation", 485 | "RequestMappingTemplateS3Location": { 486 | "Fn::Sub": [ 487 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 488 | { 489 | "S3DeploymentBucket": { 490 | "Ref": "S3DeploymentBucket" 491 | }, 492 | "S3DeploymentRootKey": { 493 | "Ref": "S3DeploymentRootKey" 494 | }, 495 | "ResolverFileName": { 496 | "Fn::Join": [ 497 | ".", 498 | [ 499 | "Mutation", 500 | "createComment", 501 | "req", 502 | "vtl" 503 | ] 504 | ] 505 | } 506 | } 507 | ] 508 | }, 509 | "ResponseMappingTemplateS3Location": { 510 | "Fn::Sub": [ 511 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 512 | { 513 | "S3DeploymentBucket": { 514 | "Ref": "S3DeploymentBucket" 515 | }, 516 | "S3DeploymentRootKey": { 517 | "Ref": "S3DeploymentRootKey" 518 | }, 519 | "ResolverFileName": { 520 | "Fn::Join": [ 521 | ".", 522 | [ 523 | "Mutation", 524 | "createComment", 525 | "res", 526 | "vtl" 527 | ] 528 | ] 529 | } 530 | } 531 | ] 532 | } 533 | } 534 | }, 535 | "UpdateCommentResolver": { 536 | "Type": "AWS::AppSync::Resolver", 537 | "Properties": { 538 | "ApiId": { 539 | "Ref": "GetAttGraphQLAPIApiId" 540 | }, 541 | "DataSourceName": { 542 | "Fn::GetAtt": [ 543 | "CommentDataSource", 544 | "Name" 545 | ] 546 | }, 547 | "FieldName": "updateComment", 548 | "TypeName": "Mutation", 549 | "RequestMappingTemplateS3Location": { 550 | "Fn::Sub": [ 551 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 552 | { 553 | "S3DeploymentBucket": { 554 | "Ref": "S3DeploymentBucket" 555 | }, 556 | "S3DeploymentRootKey": { 557 | "Ref": "S3DeploymentRootKey" 558 | }, 559 | "ResolverFileName": { 560 | "Fn::Join": [ 561 | ".", 562 | [ 563 | "Mutation", 564 | "updateComment", 565 | "req", 566 | "vtl" 567 | ] 568 | ] 569 | } 570 | } 571 | ] 572 | }, 573 | "ResponseMappingTemplateS3Location": { 574 | "Fn::Sub": [ 575 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 576 | { 577 | "S3DeploymentBucket": { 578 | "Ref": "S3DeploymentBucket" 579 | }, 580 | "S3DeploymentRootKey": { 581 | "Ref": "S3DeploymentRootKey" 582 | }, 583 | "ResolverFileName": { 584 | "Fn::Join": [ 585 | ".", 586 | [ 587 | "Mutation", 588 | "updateComment", 589 | "res", 590 | "vtl" 591 | ] 592 | ] 593 | } 594 | } 595 | ] 596 | } 597 | } 598 | }, 599 | "DeleteCommentResolver": { 600 | "Type": "AWS::AppSync::Resolver", 601 | "Properties": { 602 | "ApiId": { 603 | "Ref": "GetAttGraphQLAPIApiId" 604 | }, 605 | "DataSourceName": { 606 | "Fn::GetAtt": [ 607 | "CommentDataSource", 608 | "Name" 609 | ] 610 | }, 611 | "FieldName": "deleteComment", 612 | "TypeName": "Mutation", 613 | "RequestMappingTemplateS3Location": { 614 | "Fn::Sub": [ 615 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 616 | { 617 | "S3DeploymentBucket": { 618 | "Ref": "S3DeploymentBucket" 619 | }, 620 | "S3DeploymentRootKey": { 621 | "Ref": "S3DeploymentRootKey" 622 | }, 623 | "ResolverFileName": { 624 | "Fn::Join": [ 625 | ".", 626 | [ 627 | "Mutation", 628 | "deleteComment", 629 | "req", 630 | "vtl" 631 | ] 632 | ] 633 | } 634 | } 635 | ] 636 | }, 637 | "ResponseMappingTemplateS3Location": { 638 | "Fn::Sub": [ 639 | "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", 640 | { 641 | "S3DeploymentBucket": { 642 | "Ref": "S3DeploymentBucket" 643 | }, 644 | "S3DeploymentRootKey": { 645 | "Ref": "S3DeploymentRootKey" 646 | }, 647 | "ResolverFileName": { 648 | "Fn::Join": [ 649 | ".", 650 | [ 651 | "Mutation", 652 | "deleteComment", 653 | "res", 654 | "vtl" 655 | ] 656 | ] 657 | } 658 | } 659 | ] 660 | } 661 | } 662 | } 663 | }, 664 | "Outputs": { 665 | "GetAttCommentDataSourceName": { 666 | "Value": { 667 | "Fn::GetAtt": [ 668 | "CommentDataSource", 669 | "Name" 670 | ] 671 | }, 672 | "Export": { 673 | "Name": { 674 | "Fn::Join": [ 675 | ":", 676 | [ 677 | { 678 | "Ref": "AppSyncApiId" 679 | }, 680 | "GetAtt", 681 | "CommentDataSource", 682 | "Name" 683 | ] 684 | ] 685 | } 686 | } 687 | } 688 | }, 689 | "Conditions": { 690 | "HasEnvironmentParameter": { 691 | "Fn::Not": [ 692 | { 693 | "Fn::Equals": [ 694 | { 695 | "Ref": "env" 696 | }, 697 | "NONE" 698 | ] 699 | } 700 | ] 701 | } 702 | } 703 | } --------------------------------------------------------------------------------