Resources:
  NotesTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: NoteId
        Type: String

  DynamoDBRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - sts:AssumeRole
          Principal:
            Service:
            - appsync.amazonaws.com

  AppSyncApi:
    Type: AWS::AppSync::GraphQLApi
    Properties:
      AuthenticationType: API_KEY
      Name: AppSyncApi

  ApiKey:
    Type: AWS::AppSync::ApiKey
    Properties:
      ApiId: !GetAtt AppSyncApi.ApiId

  ApiSchema:
    Type: AWS::AppSync::GraphQLSchema
    Properties:
      ApiId: !GetAtt AppSyncApi.ApiId
      Definition: |
        type Note {
          NoteId: ID!
          title: String
          content: String
        }
        type Query {
          getNote(NoteId: ID!): Note
        }
        type Mutation {
          saveNote(NoteId: ID!, title: String!, content: String!): Note!
        }
        schema {
          query: Query
          mutation: Mutation
        }

  NotesTableDataSource:
    Type: AWS::AppSync::DataSource
    Properties:
      ApiId: !GetAtt AppSyncApi.ApiId
      Name: NotesTableDataSource
      Type: AMAZON_DYNAMODB
      ServiceRoleArn: !GetAtt DynamoDBRole.Arn
      DynamoDBConfig:
        TableName: !Ref NotesTable
        AwsRegion: !Sub ${AWS::Region}

  GetNoteResolver:
    Type: AWS::AppSync::Resolver
    DependsOn: ApiSchema
    Properties:
      ApiId: !GetAtt AppSyncApi.ApiId
      DataSourceName: !GetAtt NotesTableDataSource.Name
      TypeName: Query
      FieldName: getNote
      RequestMappingTemplate: |
        {
          "version": "2017-02-28",
          "operation": "GetItem",
          "key": {
            "NoteId": $util.dynamodb.toDynamoDBJson($context.arguments.NoteId)
          }
        }
      ResponseMappingTemplate: $util.toJson($context.result)

  SaveNoteResolver:
    Type: AWS::AppSync::Resolver
    DependsOn: ApiSchema
    Properties:
      ApiId: !GetAtt AppSyncApi.ApiId
      TypeName: Mutation
      FieldName: saveNote
      DataSourceName: !GetAtt NotesTableDataSource.Name
      RequestMappingTemplate: |
        {
          "version": "2017-02-28",
          "operation": "PutItem",
          "key": {
            "NoteId": $util.dynamodb.toDynamoDBJson($context.arguments.NoteId)
          },
          "attributeValues": {
            "title": $util.dynamodb.toDynamoDBJson($context.arguments.title),
            "content": $util.dynamodb.toDynamoDBJson($context.arguments.content)
          }
        }
      ResponseMappingTemplate: $util.toJson($context.result)

  DataSourceToTableConnector:
    Type: AWS::Serverless::Connector
    Properties:
      Source:
        Id: NotesTableDataSource
      Destination:
        Id: NotesTable
      Permissions:
      - Read
      - Write

  TriggerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Environment:
        Variables:
          API_KEY: !GetAtt ApiKey.ApiKey
          GRAPHQL_URL: !GetAtt AppSyncApi.GraphQLUrl
      Runtime: nodejs18.x
      Handler: index.handler
      InlineCode: |
        const https = require("https");

        exports.handler = async (_) => {
          const queries = {
            getNote: /* GraphQL */ `
              query {
                getNote(NoteId: "1") {
                  title
                  content
                }
              }
            `,
            saveNote: /* GraphQL */ `
              mutation {
                saveNote(content: "some note", NoteId: "1", title: "1st note") {
                  title
                  content
                }
              }
            `,
          };

          const fetch = async (url, options) =>
            new Promise((resolve, reject) => {
              const req = https.request(url, options, (res) => {
                const body = [];
                res.on("data", (chunk) => body.push(chunk));
                res.on("end", () => {
                  const resString = Buffer.concat(body).toString();
                  resolve(resString);
                });
              });

              req.on("error", (err) => {
                reject(err);
              });

              req.on("timeout", () => {
                req.destroy();
                reject(new Error("Request time out"));
              });

              req.write(options.body);
              req.end();
            });


          const makeRequest = async (queryName) => {
            const options = {
              method: "POST",
              headers: {
                "x-api-key": process.env.API_KEY,
              },
              body: JSON.stringify({ query: queries[queryName] }),
              timeout: 10000, // ms
            };

            const response = await fetch(process.env.GRAPHQL_URL, options);
            let body = JSON.parse(response);
            const data = body.data?.[queryName];
            
            if (body.errors !== undefined) {
              throw JSON.stringify(body.errors);
            }
            
            if (data?.title !== "1st note" || data?.content !== "some note") {
              throw new Error(
                `${queryName} error: '${data?.title}' must be '1st note', '${data?.content}' must be 'some note'`);
            }
            
            return body.data;
          };


          const saveResponse = await makeRequest("saveNote");
          const getResponse = await makeRequest("getNote");
          return { ...saveResponse, ...getResponse };
        };

Metadata:
  SamTransformTest: true
