service: apigw-rest-api-dynamodb-sls
frameworkVersion: '^3' # require serverless v3 or later

params:
  default:
    AppName: api-music

provider:
  name: aws

  # override the default stage (dev)
  stage: ${opt:stage, "v1"}

  # use --region option value or the default - us-east-1
  region: ${opt:region, "us-east-1"}

resources:
  # Override the default description
  Description: An Amazon API Gateway REST API that integrates with an Amazon DynamoDB table (Serverless Framework).

  Resources:
    DynamoDBTable:
      Type: 'AWS::DynamoDB::Table'
      Properties:
        TableName: Music
        # A list of attributes that describe the key schema for the DynamoDB table and indexes.
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
          - AttributeName: artist
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 5
          WriteCapacityUnits: 5
        GlobalSecondaryIndexes:
          - IndexName: Artist-Index
            KeySchema:
              - AttributeName: artist
                KeyType: HASH
            Projection:
              ProjectionType: INCLUDE
              NonKeyAttributes:
                - album
            ProvisionedThroughput:
              ReadCapacityUnits: 5
              WriteCapacityUnits: 5
    APIGatewayRole:
      Type: 'AWS::IAM::Role'
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Action:
                - 'sts:AssumeRole'
              Effect: Allow
              Principal:
                Service:
                  - apigateway.amazonaws.com
        Policies:
          - PolicyName: APIGatewayDynamoDBPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - 'dynamodb:PutItem'
                    - 'dynamodb:Query'
                  # Including * in the resource Arn allows access to the DynamoDB table and indexes
                  Resource: !Sub
                    - '${varTableArn}*'
                    - varTableArn: !GetAtt DynamoDBTable.Arn
    Api:
      Type: 'AWS::ApiGateway::RestApi'
      Properties:
        Name: ${param:AppName}
        ApiKeySourceType: HEADER
    MusicResource:
      Type: 'AWS::ApiGateway::Resource'
      Properties:
        RestApiId: !Ref Api
        ParentId: !GetAtt Api.RootResourceId
        PathPart: 'music'
    MusicMethodPost:
      Type: 'AWS::ApiGateway::Method'
      Properties:
        RestApiId: !Ref Api
        ResourceId: !Ref MusicResource
        HttpMethod: POST
        ApiKeyRequired: true
        AuthorizationType: NONE
        Integration:
          Type: AWS
          Credentials: !GetAtt APIGatewayRole.Arn
          # Should always be POST when integrating with AWS services
          IntegrationHttpMethod: POST
          # More info: https://docs.aws.amazon.com/apigateway/api-reference/resource/integration/
          Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:dynamodb:action/PutItem'
          PassthroughBehavior: WHEN_NO_TEMPLATES
          RequestTemplates:
            # Build the structure of the request that is sent when calling DynamoDB PutItem
            # Using single-line, stringified JSON as the mapping template
            # Example body when making API request: {"artist": "The Beatles", "album": "Abbey Road"}
            # Use the unique id of the API context variable (eg: $context.requestId) as the DynamoDB item id
            application/json: "{\"TableName\":\"Music\",\"Item\":{\"id\":{\"S\":\"$context.requestId\"},\"artist\":{\"S\":\"$input.path('$.artist')\"},\"album\":{\"S\":\"$input.path('$.album')\"}}}"
          IntegrationResponses:
            - StatusCode: '200'
              ResponseTemplates:
                application/json: "{}"
        MethodResponses:
          - StatusCode: '200'
    MusicArtistResource:
      Type: 'AWS::ApiGateway::Resource'
      Properties:
        RestApiId: !Ref Api
        ParentId: !Ref MusicResource
        PathPart: '{artist}'
    MusicArtistMethodGet:
      Type: 'AWS::ApiGateway::Method'
      Properties:
        RestApiId: !Ref Api
        ResourceId: !Ref MusicArtistResource
        HttpMethod: GET
        ApiKeyRequired: true
        AuthorizationType: NONE
        RequestParameters:
          # Determines whether the path parameter (eg: artist) is required
          method.request.path.artist: true
        Integration:
          Type: AWS
          Credentials: !GetAtt APIGatewayRole.Arn
          # Should always be POST when integrating with AWS services
          IntegrationHttpMethod: POST
          # More info: https://docs.aws.amazon.com/apigateway/api-reference/resource/integration/
          Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:dynamodb:action/Query'
          PassthroughBehavior: WHEN_NO_TEMPLATES
          RequestParameters:
            integration.request.path.artist: method.request.path.artist
          RequestTemplates:
            # Build the structure of the request that is sent when calling DynamoDB Query
            # Using single-line, stringified JSON as the mapping template
            # Use $util.urlDecode($input.params('pathname')) to decode if the url path value contains spaces
            # A + or %20 may be used as a space in the url
            application/json: "{\"TableName\":\"Music\",\"IndexName\":\"Artist-Index\",\"KeyConditionExpression\":\"artist=:v1\",\"ExpressionAttributeValues\":{\":v1\":{\"S\":\"$util.urlDecode($input.params('artist'))\"}}}"
          IntegrationResponses:
            - StatusCode: '200'
              ResponseTemplates:
                # Modify the response of the DynamoDB Query before sending back to the caller
                # Using single-line Velocity Template Language (VTL) code as the mapping template
                # \n represents a new line, \t represents a tab character, \" represents a single quote character
                # Example response: {"music":[{"id":"38bfb57e-a5a8-4fed-9a4f-391d66d5e987","artist":"The Beatles","album":"Abbey Road"}]}
                application/json: "#set($inputRoot = $input.path('$'))\n{\n\t\"music\": [\n\t\t#foreach($field in $inputRoot.Items) {\n\t\t\t\"id\": \"$field.id.S\",\n\t\t\t\"artist\": \"$field.artist.S\",\n\t\t\t\"album\": \"$field.album.S\"\n\t\t}#if($foreach.hasNext),#end\n\t\t#end\n\t]\n}"
        MethodResponses:
          - StatusCode: '200'
    ApiDeployment:
      Type: 'AWS::ApiGateway::Deployment'
      DependsOn:
        - MusicArtistMethodGet
      Properties:
        RestApiId: !Ref Api
        StageName: ${self:provider.stage}
    ApiKey:
      Type: 'AWS::ApiGateway::ApiKey'
      DependsOn:
        - ApiDeployment
      Properties:
        Enabled: true
        Name: ${param:AppName}-apikey
        StageKeys:
          - RestApiId: !Ref Api
            StageName: ${self:provider.stage}
    ApiUsagePlan:
      Type: 'AWS::ApiGateway::UsagePlan'
      DependsOn:
        - ApiDeployment
      Properties:
        ApiStages:
          - ApiId: !Ref Api
            Stage: ${self:provider.stage}
        Throttle:
          RateLimit: 500
          BurstLimit: 1000
        UsagePlanName: ${param:AppName}-usage-plan
        Quota:
          Limit: 10000
          Period: MONTH
    ApiUsagePlanKey:
      Type: 'AWS::ApiGateway::UsagePlanKey'
      Properties:
        KeyType: API_KEY
        KeyId: !Ref ApiKey
        UsagePlanId: !Ref ApiUsagePlan
  Outputs:
    ApiRootUrl:
      Description: Root Url of the API
      Value: !Sub 
        - 'https://${ApiId}.execute-api.${AWS::Region}.amazonaws.com/${self:provider.stage}'
        - ApiId: !Ref Api