
service: graphql # Important: this must be unique in AWS Cloudformation!
frameworkVersion: ">=3.24.1 <4.0.0"

custom:
  stageDefault: dev
  # Could later change to a reference to a CloudFormation output.
  # AWS doesn't yet provide an easy way to get a DynamoDB table ARN:
  # https://forums.aws.amazon.com/thread.jspa?threadID=100847
  # https://serverless.com/framework/docs/providers/aws/guide/variables#reference-cloudformation-outputs
  dbTableArnBase: arn:aws:dynamodb:${env:AWS_REGION}:${env:AWS_ACCOUNT_ID}:table/
  # Ideally would reference the Cloudformation output directly, but this is
  # buggy for the authorizer: https://github.com/serverless/serverless/issues/3129
  customAuthorizerArn: arn:aws:lambda:${env:AWS_REGION}:${env:AWS_ACCOUNT_ID}:function:lambda-${self:provider.stage}-firebaseAuthorizer
  # https://github.com/claygregory/serverless-prune-plugin
  prune:
    automatic: true
    number: 5

provider:
  name: aws
  runtime: nodejs16.x
  region: us-west-2
  stage: ${env:SLS_STAGE, self:custom.stageDefault}
  # Let's be conservative and prevent any unexpected changes
  # to the stack. See:
  # https://github.com/gladly-team/tab/issues/582
  # Example to update policy:
  #   aws cloudformation set-stack-policy --stack-name graphql-test --region us-west-2 --stack-policy-body \
  #   '{"Statement":[
  #     {"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"LogicalResourceId/ApiGatewayDeployment*"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/ApiGatewayMethodGraphqlOptions"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/ApiGatewayMethodGraphqlPost"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/ApiGatewayResourceGraphql"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/ApiGatewayRestApi"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/FirebaseAuthorizerApiGatewayAuthorizer"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"LogicalResourceId/FirebaseAuthorizerLambdaPermissionApiGateway"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/GraphqlLambdaFunction"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"LogicalResourceId/GraphqlLambdaPermissionApiGateway"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"LogicalResourceId/GraphqlLambdaVersion*"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/GraphqlLogGroup"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"LogicalResourceId/IamRoleLambdaExecution"}
  #   ]}'
  stackPolicy:
    # If not specified here, Cloudformation denies updates by default.
    # In alphabetical order by resource.
    - Effect: Allow
      Principal: "*"
      Action: "Update:*"
      Resource: "LogicalResourceId/ApiGatewayDeployment*"
    - Effect: Allow
      Principal: "*"
      Action: "Update:Modify"
      Resource: "LogicalResourceId/ApiGatewayMethodGraphqlOptions" 
    - Effect: Allow
      Principal: "*"
      Action: "Update:Modify"
      Resource: "LogicalResourceId/ApiGatewayMethodGraphqlPost" 
    - Effect: Allow
      Principal: "*"
      Action: "Update:Modify"
      Resource: "LogicalResourceId/ApiGatewayResourceGraphql" 
    - Effect: Allow
      Principal: "*"
      Action: "Update:Modify"
      Resource: "LogicalResourceId/ApiGatewayRestApi"
    - Effect: Allow
      Principal: "*"
      Action: "Update:Modify"
      Resource: "LogicalResourceId/FirebaseAuthorizerApiGatewayAuthorizer"
    - Effect: Allow
      Principal: "*"
      Action: "Update:*"
      Resource: "LogicalResourceId/FirebaseAuthorizerLambdaPermissionApiGateway"
    - Effect: Allow
      Principal: "*"
      Action: "Update:Modify"
      Resource: "LogicalResourceId/GraphqlLambdaFunction"
    - Effect: Allow
      Principal: "*"
      Action: "Update:*"
      Resource: "LogicalResourceId/GraphqlLambdaPermissionApiGateway"
    - Effect: Allow
      Principal: "*"
      Action: "Update:*"
      Resource: "LogicalResourceId/GraphqlLambdaVersion*"
    - Effect: Allow
      Principal: "*"
      Action: "Update:Modify"
      Resource: "LogicalResourceId/GraphqlLogGroup"
    - Effect: Allow
      Principal: "*"
      Action: "Update:Modify"
      Resource: "LogicalResourceId/IamRoleLambdaExecution"
  # Rules for fine-grained DynamoDB access:
  #   https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/specifying-conditions.html
  #   https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/using-identity-based-policies.html
  iamRoleStatements:
    # Charities table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:Query"
        - "dynamodb:Scan"
      Resource: "${self:custom.dbTableArnBase}Charities-${self:provider.stage}"
    # Widgets table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:Query"
        - "dynamodb:Scan"
      Resource: "${self:custom.dbTableArnBase}Widgets-${self:provider.stage}"
    # Users table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:PutItem"
        - "dynamodb:Query"
        - "dynamodb:UpdateItem"
      Resource:
        - "${self:custom.dbTableArnBase}Users-${self:provider.stage}"
        - "${self:custom.dbTableArnBase}Users-${self:provider.stage}/index/*"
    # UserLevels table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:Query"
        - "dynamodb:Scan"
      Resource: "${self:custom.dbTableArnBase}UserLevels-${self:provider.stage}"
    # VcDonationLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
      Resource: "${self:custom.dbTableArnBase}VcDonationLog-${self:provider.stage}"
    # VcDonationByCharity table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
        - "dynamodb:Query"
        - "dynamodb:UpdateItem"
      Resource: "${self:custom.dbTableArnBase}VcDonationByCharity-${self:provider.stage}"
    # BackgroundImages table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:Query"
        - "dynamodb:Scan"
      Resource:
        - "${self:custom.dbTableArnBase}BackgroundImages-${self:provider.stage}"
        - "${self:custom.dbTableArnBase}BackgroundImages-${self:provider.stage}/index/*"
    # UserImpact table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:PutItem"
        - "dynamodb:Query"
        - "dynamodb:UpdateItem"
      Resource:
        - "${self:custom.dbTableArnBase}UserImpact-${self:provider.stage}"
        - "${self:custom.dbTableArnBase}UserImpact-${self:provider.stage}/index/*"
    # Missions table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:PutItem"
        - "dynamodb:Query"
        - "dynamodb:UpdateItem"
      Resource:
        - "${self:custom.dbTableArnBase}Missions-${self:provider.stage}"
        - "${self:custom.dbTableArnBase}Missions-${self:provider.stage}/index/*"
    # UserMissions table 
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:PutItem"
        - "dynamodb:Query"
        - "dynamodb:UpdateItem"
      Resource:
        - "${self:custom.dbTableArnBase}UserMissions-${self:provider.stage}"
        - "${self:custom.dbTableArnBase}UserMissions-${self:provider.stage}/index/*"
    # InvitedUsers table
    - Effect: 'Allow'
      Action:
        - 'dynamodb:BatchGetItem'
        - 'dynamodb:GetItem'
        - 'dynamodb:PutItem'
        - 'dynamodb:Query'
        - 'dynamodb:UpdateItem'
      Resource:
        - '${self:custom.dbTableArnBase}InvitedUsers-${self:provider.stage}'
        - '${self:custom.dbTableArnBase}InvitedUsers-${self:provider.stage}/index/*'
    # UserWidgets table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:BatchWriteItem"
        - "dynamodb:GetItem"
        - "dynamodb:PutItem"
        - "dynamodb:Query"
        - "dynamodb:UpdateItem"
      Resource:
        - "${self:custom.dbTableArnBase}UserWidgets-${self:provider.stage}"
        - "${self:custom.dbTableArnBase}UserWidgets-${self:provider.stage}/index/*"
    # UserTabsLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
      Resource: "${self:custom.dbTableArnBase}UserTabsLog-${self:provider.stage}"
    # UserSearchLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:GetItem"
        - "dynamodb:PutItem"
        - "dynamodb:Query"
      Resource: "${self:custom.dbTableArnBase}UserSearchLog-${self:provider.stage}"
    # UserRevenueLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
      Resource: "${self:custom.dbTableArnBase}UserRevenueLog-${self:provider.stage}"
    # VideoAdLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:PutItem"
        - "dynamodb:Query"
        - "dynamodb:UpdateItem"
      Resource: 
        - "${self:custom.dbTableArnBase}VideoAdLog-${self:provider.stage}"
        - "${self:custom.dbTableArnBase}VideoAdLog-${self:provider.stage}/index/*"
    # UserDataConsentLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
      Resource: "${self:custom.dbTableArnBase}UserDataConsentLog-${self:provider.stage}"
    # ReferralDataLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
        - "dynamodb:BatchGetItem"
        - "dynamodb:GetItem"
        - "dynamodb:Query"
        - "dynamodb:Scan"
      Resource:
        - "${self:custom.dbTableArnBase}ReferralDataLog-${self:provider.stage}"
        - "${self:custom.dbTableArnBase}ReferralDataLog-${self:provider.stage}/index/*"
    # ReferralLinkClickLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
      Resource: "${self:custom.dbTableArnBase}ReferralLinkClickLog-${self:provider.stage}"
    # Causes table
    - Effect: "Allow"
      Action:
        - "dynamodb:GetItem"
        - "dynamodb:Query"
      Resource:
        - "${self:custom.dbTableArnBase}Causes-${self:provider.stage}"
    # UserSwitchSearchPromptLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
      Resource:
        - "${self:custom.dbTableArnBase}UserSwitchSearchPromptLog-${self:provider.stage}"
    # UserSwitchSearchPromptLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
      Resource:
        - "${self:custom.dbTableArnBase}UserSearchSettingsLog-${self:provider.stage}"
    # UserExperiment table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
        - "dynamodb:GetItem"
      Resource:
        - "${self:custom.dbTableArnBase}UserExperiment-${self:provider.stage}"
    # UserEventLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
      Resource:
        - "${self:custom.dbTableArnBase}UserEventLog-${self:provider.stage}"
    # UserGroupImpactMetricLog table
    - Effect: "Allow"
      Action:
        - "dynamodb:PutItem"
        - "dynamodb:GetItem"
      Resource:
        - "${self:custom.dbTableArnBase}UserGroupImpactMetricLog-${self:provider.stage}"
    # @service/redis - Uncomment this if we re-launch the Redis service.
    # Grant access to our Redis service.
    # https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html
    - Effect: "Allow"
      Action:
        - "execute-api:Invoke"
      # We may be dealing with this issue:
      # https://github.com/serverless/serverless/issues/7310
      # Once it's fixed, try using the imported resource name instead of
      # the generalized execute-api resource.
      # Resource:
      #   Fn::Join:
      #     - ""
      #     - - Fn::ImportValue: "tab-redis-${self:provider.stage}-APIGatewayBaseResourceID"
      #       - /POST/redis
      Resource: "arn:aws:execute-api:${self:provider.region}:*:*/${self:provider.stage}/POST/redis"

plugins:
  # Include only the required node modules.
  # https://github.com/dougmoscrop/serverless-plugin-include-dependencies
  - serverless-plugin-include-dependencies
  # https://github.com/claygregory/serverless-prune-plugin
  - serverless-prune-plugin

# Only include the build and its node modules.
package:
  patterns:
    - build/**

functions:
  graphql:
    handler: build/handler.serverlessHandler
    # In seconds. Should remain ~2-4 seconds for graphql, whose requests
    # should typically complete in ~300ms.
    # We manually set the API Gateway "TimeoutInMillis" in the AWS console:
    #   API -> Route -> Method -> Integration request ->
    #   Uncheck Default Timeout
    # It's configurable in CloudFormation but doesn't appear to be supported
    # by Serverless for the v1 "REST" API, only the v2 "HTTP" API.
    # CloudFormation:
    #   https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html#cfn-apigateway-method-integration-timeoutinmillis
    # Relevant StackOverflow question:
    #   https://stackoverflow.com/a/60609096
    # Support of "TimeoutInMillis" in Serverless for v2 API Gateway:
    #   https://github.com/serverless/serverless/issues/7052
    timeout: 4
    events:
      - http:
          path: graphql/
          method: post
          cors: true
          # Require user to be authenticated.
          # https://serverless.com/framework/docs/providers/aws/events/apigateway#http-endpoints-with-custom-authorizers
          authorizer:
            # Reference the authorizer ARN exported from the "lambda" stack.
            #   https://github.com/serverless/serverless/pull/3575
            # Bug with this behavior:
            #   https://github.com/serverless/serverless/issues/3129
            arn: ${self:custom.customAuthorizerArn}
            type: request
            resultTtlInSeconds: 300
            # https://www.serverless.com/framework/docs/providers/aws/events/apigateway#http-endpoints-with-custom-authorizers
            identitySource: method.request.header.Authorization
    environment:
      IS_GLOBAL_CAMPAIGN_LIVE: false
      EST_MONEY_RAISED_PER_TAB: ${env:EST_MONEY_RAISED_PER_TAB}
      EST_MONEY_RAISED_PER_SEARCH: ${env:EST_MONEY_RAISED_PER_SEARCH}
      # @service/redis - Set the Redis endpoint if we re-launch the Redis service.
      REDIS_SERVICE_ENDPOINT: ""
      # REDIS_SERVICE_ENDPOINT:
      #   "Fn::ImportValue": tab-redis-${self:provider.stage}-ServiceEndpoint
