AWSTemplateFormatVersion: 2010-09-09 Parameters: ProjectName: Description: Used as a prefix for project resources. Can be up to 12 characters, lowercase letters (a-z) only. Type: String Default: mybookstore AllowedPattern: "^[a-z]{1,12}" ConstraintDescription: The ProjectName can be up to 12 characters, lowercase letters (a-z) only. Conditions: IADRegion: !Equals [!Ref "AWS::Region", "us-east-1"] Mappings: S3Buckets: us-east-1: Bucket: aws-bookstore-demo-app-us-east-1 NeptuneDataBucket: bookstore-neptune us-west-2: Bucket: aws-bookstore-demo-app-us-west-2 NeptuneDataBucket: bookstore-neptune-us-west-2 eu-central-1: Bucket: aws-bookstore-demo-app-eu-central-1 NeptuneDataBucket: bookstore-neptune-eu-central-1 eu-west-1: Bucket: aws-bookstore-demo-app-eu-west-1 NeptuneDataBucket: bookstore-neptune-eu-west-1 Constants: AppKeys: SeedRepository: https://s3.amazonaws.com/aws-bookstore-demo/bookstore-webapp.zip S3Keys: ListOrdersCode: functions/ListOrders.zip GetBookCode: functions/GetBook.zip ListBooksCode: functions/ListBooks.zip UpdateCartCode: functions/UpdateCart.zip GetCartItemCode: functions/GetCartItem.zip ListItemsInCartCode: functions/ListItemsInCart.zip AddToCartCode: functions/AddToCart.zip RemoveFromCartCode: functions/RemoveFromCart.zip GetBestSellersCode: functions/GetBestSellers.zip CheckoutCode: functions/Checkout.zip UploadBooksCode: functions/UploadBooks.zip GetRecommendationsCode: functions/GetRecommendations.zip GetRecommendationsByBookCode: functions/GetRecommendationsByBook.zip SearchCode: functions/Search.zip UpdateSearchCode: functions/UpdateSearchCluster.zip UpdateBestSellersCode: functions/UpdateBestSellers.zip NeptuneLoaderCode: functions/NeptuneLoader.zip NeptuneIAMCode: functions/NeptuneIAM.zip bookstoreNeptuneS3DataPath: /data/ booksData: data/books.json CreateOSRoleCode: functions/CreateOSRole.zip UpdateConfigCode: functions/UpdateConfig.zip PythonLambdaLayer: functions/PythonLambdaLayer.zip DeleteBucketsCode: functions/DeleteBuckets.zip SeederFunctionCode: functions/aws-serverless-codecommit-seeder.zip Resources: # ---------- VPC - SUBNET - SECURITY GROUPS --------- bookstoreVPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: '172.31.0.0/16' bookstoreSubnet1: Type: "AWS::EC2::Subnet" Properties: CidrBlock: Fn::Select: - 0 - Fn::Cidr: - Fn::GetAtt: [bookstoreVPC, CidrBlock] - 3 - 8 VpcId: Ref: bookstoreVPC AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: Ref: "AWS::Region" bookstoreSubnet2: Type: "AWS::EC2::Subnet" Properties: CidrBlock: Fn::Select: - 1 - Fn::Cidr: - Fn::GetAtt: [bookstoreVPC, CidrBlock] - 3 - 8 VpcId: Ref: bookstoreVPC AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: Ref: "AWS::Region" bookstoreVPCRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref bookstoreVPC bookstoreVPCRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref bookstoreVPCRouteTable SubnetId: !Ref bookstoreSubnet1 bookstoreVPCRouteTableAssociationTwo: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref bookstoreVPCRouteTable SubnetId: !Ref bookstoreSubnet2 bookstoreNeptuneSecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: GroupDescription: "Security group for Neptune DB within book store app." SecurityGroupIngress: - CidrIp: "0.0.0.0/0" FromPort: 8182 ToPort: 8182 IpProtocol: tcp VpcId: Ref: bookstoreVPC bookstoreNeptuneSubnetGroup: Type: "AWS::Neptune::DBSubnetGroup" Properties: DBSubnetGroupDescription: "Subnet group for Neptune DB within book store app." SubnetIds: - !Ref bookstoreSubnet1 - !Ref bookstoreSubnet2 redisLambdaSecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: VpcId: Ref: bookstoreVPC GroupDescription: "A component security group allowing access only to redis" bookstoreCacheSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Elasticache security group SecurityGroupIngress: - IpProtocol: "tcp" FromPort: "6379" ToPort: "6379" SourceSecurityGroupId: Ref: redisLambdaSecurityGroup VpcId: !Ref bookstoreVPC bookstoreCacheSubnets: Type: 'AWS::ElastiCache::SubnetGroup' Properties: Description: Subnets for ElastiCache SubnetIds: - Ref: bookstoreSubnet1 # ---------- NEPTUNE CLUSTER DEFINITION --------- bookstoreNeptuneCluster: Type: "AWS::Neptune::DBCluster" Properties: IamAuthEnabled : false DBSubnetGroupName: Ref: bookstoreNeptuneSubnetGroup VpcSecurityGroupIds: [ !GetAtt bookstoreNeptuneSecurityGroup.GroupId ] bookstoreNeptuneDB: Type: "AWS::Neptune::DBInstance" Properties: DBClusterIdentifier: !Ref bookstoreNeptuneCluster DBInstanceClass: "db.r6g.large" # ---------- IAM ROLE ATTACH FOR S3 BULK LOAD ------------ bookstoreNeptuneLoaderS3ReadPolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - s3:Get* - s3:List* Effect: Allow Resource: "*" Version: '2012-10-17' PolicyName: bookstoreNeptuneLoaderS3ReadPolicy Roles: - Ref: bookstoreNeptuneLoaderS3ReadRole bookstoreNeptuneLoaderS3ReadRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: - rds.amazonaws.com Version: '2012-10-17' Path: / # ---------- CUSTOM CLOUDFORMATION RESOURCE FOR ATTACHING S3 READ ROLE TO NEPTUNE CLUSTER --------- bookstoreNeptuneIAMAttach: DependsOn: - bookstoreNeptuneIAMAttachLambdaRoleCloudWatchStream - bookstoreNeptuneIAMAttachLambdaRoleCloudWatchGroup - bookstoreNeptuneIAMAttachLambdaRoleEC2 - bookstoreNeptuneIAMAttachLambdaRoleRDS - bookstoreNeptuneIAMAttachLambdaRole - bookstoreNeptuneIAMAttachLambda - bookstoreNeptuneLoaderS3ReadPolicy - bookstoreNeptuneLoaderS3ReadRole Type: Custom::NeptuneIAMAttach Properties: ServiceToken: Fn::GetAtt: [ bookstoreNeptuneIAMAttachLambda, Arn ] NeptuneDB: !Ref bookstoreNeptuneCluster IAMRole: Fn::GetAtt: [ bookstoreNeptuneLoaderS3ReadRole, Arn ] Region: !Ref 'AWS::Region' bookstoreNeptuneIAMAttachLambdaRoleCloudWatchStream: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - logs:CreateLogStream - logs:PutLogEvents Effect: Allow Resource: !Join [ "", [ "arn:aws:logs:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":log-group:/aws/lambda/", !Ref bookstoreNeptuneIAMAttachLambda, ":*" ]] Version: '2012-10-17' PolicyName: bookstoreNeptuneIAMAttachLambdaRoleCloudWatchStream Roles: - Ref: bookstoreNeptuneIAMAttachLambdaRole bookstoreNeptuneIAMAttachLambdaRoleCloudWatchGroup: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - logs:CreateLogGroup Effect: Allow Resource: !Join [ "", [ "arn:aws:logs:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":*" ]] Version: '2012-10-17' PolicyName: bookstoreNeptuneIAMAttachLambdaRoleCloudWatchGroup Roles: - Ref: bookstoreNeptuneIAMAttachLambdaRole bookstoreNeptuneIAMAttachLambdaRoleEC2: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface - ec2:DetachNetworkInterface Effect: Allow Resource: "*" Version: '2012-10-17' PolicyName: bookstoreNeptuneIAMAttachLambdaRoleEC2 Roles: - Ref: bookstoreNeptuneIAMAttachLambdaRole bookstoreNeptuneIAMAttachLambdaRoleRDS: DependsOn: bookstoreNeptuneDB Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - rds:AddRoleToDBCluster - rds:DescribeDBClusters Effect: Allow Resource: - "*" - Action: - iam:PassRole Effect: Allow Resource: - Fn::GetAtt: [ bookstoreNeptuneLoaderS3ReadRole, Arn ] Version: '2012-10-17' PolicyName: bookstoreNeptuneIAMAttachLambdaRoleRDS Roles: - Ref: bookstoreNeptuneIAMAttachLambdaRole bookstoreNeptuneIAMAttachLambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: - lambda.amazonaws.com Version: '2012-10-17' Path: / bookstoreNeptuneIAMAttachLambda: Type: AWS::Lambda::Function DependsOn: bookstoreNeptuneDB Properties: Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - NeptuneIAMCode Description: 'Lambda function to add an IAM policy to a Neptune cluster to allow for bulk load.' Handler: lambda_function.lambda_handler Layers: - !Ref PythonLambdaLayer MemorySize: 128 Role: Fn::GetAtt: [ bookstoreNeptuneIAMAttachLambdaRole, Arn ] Runtime: python3.9 Timeout: 30 # ---------- VPC ENDPOINT FOR NEPTUNE BULK LOADER ACCESS TO S3 --------- S3Endpoint: Type: AWS::EC2::VPCEndpoint Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: '*' Action: - 's3:Get*' - 's3:PutObject' - 's3:List*' Resource: - '*' RouteTableIds: - !Ref bookstoreVPCRouteTable ServiceName: !Join [ "" , [ "com.amazonaws.", !Ref "AWS::Region", ".s3"]] VpcId: !Ref bookstoreVPC # ---------- CUSTOM RESOURCE TO INITIATE NEPTUNE BULK LOAD PROCESS --------- bookstoreNeptuneLoader: DependsOn: - bookstoreNeptuneLoaderLambdaRoleCloudWatchStream - bookstoreNeptuneLoaderLambdaRoleCloudWatchGroup - bookstoreNeptuneLoaderLambdaRoleEC2 - bookstoreNeptuneLoaderLambdaRole - bookstoreNeptuneLoaderLambda - bookstoreNeptuneLoaderS3ReadPolicy - bookstoreNeptuneLoaderS3ReadRole - bookstoreVPCRouteTableAssociation - bookstoreVPCRouteTableAssociationTwo - S3Endpoint Type: "Custom::NeptuneLoader" Properties: ServiceToken: Fn::GetAtt: [ bookstoreNeptuneLoaderLambda, Arn] bookstoreNeptuneLoaderLambdaRoleCloudWatchStream: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - logs:CreateLogStream - logs:PutLogEvents Effect: Allow Resource: !Join [ "", [ "arn:aws:logs:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":log-group:/aws/lambda/", !Ref bookstoreNeptuneLoaderLambda, ":*" ]] Version: '2012-10-17' PolicyName: bookstoreNeptuneLoaderLambdaRoleCloudWatchStream Roles: - Ref: bookstoreNeptuneLoaderLambdaRole bookstoreNeptuneLoaderLambdaRoleCloudWatchGroup: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - logs:CreateLogGroup Effect: Allow Resource: !Join [ "", [ "arn:aws:logs:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":*" ]] Version: '2012-10-17' PolicyName: bookstoreNeptuneLoaderLambdaRoleCloudWatchGroup Roles: - Ref: bookstoreNeptuneLoaderLambdaRole bookstoreNeptuneLoaderLambdaRoleEC2: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface - ec2:DetachNetworkInterface Effect: Allow Resource: "*" Version: '2012-10-17' PolicyName: bookstoreNeptuneLoaderLambdaRoleEC2 Roles: - Ref: bookstoreNeptuneLoaderLambdaRole bookstoreNeptuneLoaderLambda: Type: AWS::Lambda::Function DependsOn: - bookstoreNeptuneDB - S3Endpoint Properties: Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - NeptuneLoaderCode Description: 'Lambda function to load data into Neptune instance.' Environment: Variables: neptunedb: Fn::GetAtt: [ bookstoreNeptuneCluster, Endpoint ] neptuneloads3path: !Join - '' - - 's3://' - !FindInMap - S3Buckets - !Ref 'AWS::Region' - NeptuneDataBucket - !FindInMap - Constants - S3Keys - bookstoreNeptuneS3DataPath region: Ref: "AWS::Region" s3loadiamrole: Fn::GetAtt: [ bookstoreNeptuneLoaderS3ReadRole, Arn] Handler: lambda_function.lambda_handler MemorySize: 128 Layers: - !Ref PythonLambdaLayer Role: Fn::GetAtt: [ bookstoreNeptuneLoaderLambdaRole, Arn ] Runtime: python3.9 Timeout: 180 VpcConfig: SecurityGroupIds: - Ref: bookstoreNeptuneSecurityGroup SubnetIds: - Ref: bookstoreSubnet1 - Ref: bookstoreSubnet2 bookstoreNeptuneLoaderLambdaRole: Type: AWS::IAM::Role Properties: ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' - 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: - lambda.amazonaws.com Version: '2012-10-17' Path: / # ---------- ROLES FOR DYNAMODB --------- DynamoDbRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${ProjectName}-DynamoDbLambda' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' - 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' Policies: - PolicyName: PutRidePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'dynamodb:PutItem' - 'dynamodb:Query' - 'dynamodb:UpdateTable' - 'dynamodb:UpdateItem' - 'dynamodb:BatchWriteItem' - 'dynamodb:GetItem' - 'dynamodb:Scan' - 'dynamodb:DeleteItem' Resource: - !GetAtt - TBooks - Arn - !GetAtt - TOrders - Arn - !GetAtt - TCart - Arn - !Join - '' - - !GetAtt - TBooks - Arn - /* Metadata: 'AWS::CloudFormation::Designer': id: 07b29683-0ea5-44bf-a439-fcb89713fd09 # ---------- ROLES FOR NEPTUNE --------- RecommendationsLambdaRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${ProjectName}-RecommendationsLambdaRole' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: RecommendationsLambdaRoleEC2 PolicyDocument: Statement: - Action: - 'ec2:CreateNetworkInterface' - 'ec2:DescribeNetworkInterfaces' - 'ec2:DeleteNetworkInterface' - 'ec2:DetachNetworkInterface' Effect: Allow Resource: '*' Version: 2012-10-17 # ---------- DYNAMODB TABLES DEFINITIONS --------- TBooks: Type: 'AWS::DynamoDB::Table' Properties: TableName: !Sub '${ProjectName}-Books' AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: category AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 GlobalSecondaryIndexes: - IndexName: category-index KeySchema: - AttributeName: category KeyType: HASH Projection: ProjectionType: ALL ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES Metadata: 'AWS::CloudFormation::Designer': id: 9de25578-c943-42f5-9ec7-7a5258c7771a TOrders: Type: 'AWS::DynamoDB::Table' Properties: TableName: !Sub '${ProjectName}-Orders' AttributeDefinitions: - AttributeName: customerId AttributeType: S - AttributeName: orderId AttributeType: S KeySchema: - AttributeName: customerId KeyType: HASH - AttributeName: orderId KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES Metadata: 'AWS::CloudFormation::Designer': id: 57fb3162-6a2d-4651-a9c8-7404e82181e8 TCart: Type: 'AWS::DynamoDB::Table' Properties: TableName: !Sub '${ProjectName}-Cart' AttributeDefinitions: - AttributeName: customerId AttributeType: S - AttributeName: bookId AttributeType: S KeySchema: - AttributeName: customerId KeyType: HASH - AttributeName: bookId KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 Metadata: 'AWS::CloudFormation::Designer': id: e1a45038-ec76-464d-91b5-6b31bc325fa7 # ---------- OPENSEARCH ROLES DEFINITIONS --------- OSSearchRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${ProjectName}-OSSearchRole' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' - 'arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole' Policies: - PolicyName: !Sub '${ProjectName}-lambda-policy' PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'es:ESHttpPost' - 'es:ESHttpGet' Resource: !Join - '' - - 'arn:aws:es:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - domain/ - !Ref OpenSearchDomain - /* - Effect: Allow Action: - 's3:ListBucket' - 's3:GetObject' Resource: !Join - '' - - 'arn:aws:s3:::' - !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket - /* - Effect: Allow Action: - 'dynamodb:DescribeStream' - 'dynamodb:GetRecords' - 'dynamodb:GetShardIterator' - 'dynamodb:ListStreams' Resource: - !GetAtt - TBooks - Arn - !Join - '' - - !GetAtt - TBooks - Arn - /stream/* OpenSearchDomain: Type: 'AWS::OpenSearchService::Domain' DependsOn: - ESRoleCreator Properties: DomainName: !Sub '${ProjectName}-domain' EngineVersion: OpenSearch_1.3 ClusterConfig: DedicatedMasterEnabled: 'false' InstanceCount: 1 ZoneAwarenessEnabled: 'false' InstanceType: t3.small.search VPCOptions: SubnetIds: - Ref: bookstoreSubnet1 EBSOptions: EBSEnabled: true Iops: 0 VolumeSize: 100 VolumeType: gp2 AccessPolicies: Version: 2012-10-17 Statement: - Effect: Allow Principal: AWS: '*' Action: - 'es:*' Resource: !Join - '' - - 'arn:aws:es:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - domain/ - !Sub '${ProjectName}-domain' - /* AdvancedOptions: rest.action.multi.allow_explicit_index: true # ---------- ROLES FOR ELASTICACHE --------- RedisRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${ProjectName}-RedisRole' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonElastiCacheFullAccess' - 'arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole' - 'arn:aws:iam::aws:policy/AWSLambdaInvocation-DynamoDB' # ---------- ELASTICACHE DEFINITIONS --------- ElastiCacheCluster: DependsOn: - bookstoreCacheSubnets - bookstoreCacheSecurityGroup Type: 'AWS::ElastiCache::CacheCluster' Properties: ClusterName: !Sub '${ProjectName}-cluster' CacheNodeType: cache.t2.micro NumCacheNodes: '1' Engine: redis CacheSubnetGroupName: !Ref bookstoreCacheSubnets VpcSecurityGroupIds: - Fn::GetAtt: - bookstoreCacheSecurityGroup - 'GroupId' # ---------- LAMBDA STREAMING FUNCTIONS --------- PythonLambdaLayer: Type: "AWS::Lambda::LayerVersion" Properties: CompatibleRuntimes: - python3.9 - python3.7 - python3.6 Content: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - PythonLambdaLayer UpdateSearchCluster: Type: 'AWS::Lambda::Function' DependsOn: - OSSearchRole - OpenSearchDomain - TBooks Properties: FunctionName: !Sub '${ProjectName}-UpdateSearchCluster' Description: 'Update OpenSearch cluster as books are added' Handler: index.handler Role: !GetAtt - OSSearchRole - Arn Runtime: python3.9 Timeout: '60' VpcConfig: SecurityGroupIds: - Fn::GetAtt: [bookstoreVPC, DefaultSecurityGroup] SubnetIds: - Ref: bookstoreSubnet1 Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - UpdateSearchCode Environment: Variables: ESENDPOINT: !GetAtt - OpenSearchDomain - DomainEndpoint REGION: !Ref 'AWS::Region' FunctionUpdateBestSellers: Type: 'AWS::Lambda::Function' DependsOn: - RedisRole - ElastiCacheCluster - TOrders Properties: FunctionName: !Sub '${ProjectName}-UpdateBestSellers' Description: 'Updates BestSellers as orders are placed' Handler: index.handler Role: !GetAtt - RedisRole - Arn Runtime: nodejs16.x Timeout: '60' VpcConfig: SecurityGroupIds: - Ref: redisLambdaSecurityGroup SubnetIds: - Ref: bookstoreSubnet1 Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - UpdateBestSellersCode Environment: Variables: URL: !GetAtt - ElastiCacheCluster - RedisEndpoint.Address # ---------- STREAMING TRIGGERS --------- DataTableStream: DependsOn: TBooks Type: 'AWS::Lambda::EventSourceMapping' Properties: BatchSize: 1 Enabled: true EventSourceArn: !GetAtt - TBooks - StreamArn FunctionName: !GetAtt - UpdateSearchCluster - Arn StartingPosition: TRIM_HORIZON OrderTableStream: DependsOn: TOrders Type: 'AWS::Lambda::EventSourceMapping' Properties: BatchSize: 1 Enabled: true EventSourceArn: !GetAtt - TOrders - StreamArn FunctionName: !GetAtt - FunctionUpdateBestSellers - Arn StartingPosition: TRIM_HORIZON # ---------- LAMBDA for DYNAMODB FUNCTIONS --------- FunctionListOrders: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-ListOrders' Description: Get list of books ordered by customerId Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Orders' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - ListOrdersCode Metadata: 'AWS::CloudFormation::Designer': id: f873825f-3037-47b0-90b4-fca6924fb4e9 FunctionGetBook: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-GetBook' Description: Get book by id Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Books' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - GetBookCode Metadata: 'AWS::CloudFormation::Designer': id: 1ae22cac-5803-4077-a6fe-1a3ca1aedfc2 FunctionListBooks: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-ListBooks' Description: Get list of books by category Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Books' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - ListBooksCode Metadata: 'AWS::CloudFormation::Designer': id: 8e306d11-6d48-49e0-8f69-760139c83447 FunctionUpdateCart: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-UpdateCart' Description: Update Customer's cart Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Cart' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - UpdateCartCode Metadata: 'AWS::CloudFormation::Designer': id: 92605ac3-2103-491b-8593-c1ed98181425 FunctionGetCartItem: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-GetCartItem' Description: Get item in cart by customer and book id Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Cart' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - GetCartItemCode Metadata: 'AWS::CloudFormation::Designer': id: e64608f0-5134-4c91-8f3c-4d03408c8236 FunctionListItemsInCart: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-ListOrdersInCart' Description: Get list of items in cart Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Cart' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - ListItemsInCartCode Metadata: 'AWS::CloudFormation::Designer': id: e64608f0-5134-4c91-8f3c-4d03408c8236 FunctionAddToCart: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-AddToCart' Description: Add a book to cart Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Cart' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - AddToCartCode Metadata: 'AWS::CloudFormation::Designer': id: e64608f0-5134-4c91-8f3c-4d03408c8236 FunctionRemoveFromCart: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-RemoveFromCart' Description: Remove a book from cart Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Cart' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - RemoveFromCartCode Metadata: 'AWS::CloudFormation::Designer': id: e64608f0-5134-4c91-8f3c-4d03408c8236 FunctionCheckout: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-Checkout' Description: Get list of books ordered by customerId Handler: index.handler MemorySize: 256 Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: ORDERS_TABLE: !Sub '${ProjectName}-Orders' CART_TABLE: !Sub '${ProjectName}-Cart' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - CheckoutCode Metadata: 'AWS::CloudFormation::Designer': id: 9c3f4191-a370-4344-a5b1-626228919c45 FunctionUploadBooks: DependsOn: DataTableStream Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-UploadBooks' Description: Upload sample data for books Handler: index.handler Runtime: nodejs16.x Role: !GetAtt - DynamoDbRole - Arn Timeout: 120 Environment: Variables: TABLE_NAME: !Sub '${ProjectName}-Books' S3_BUCKET: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket FILE_NAME: !FindInMap - Constants - S3Keys - booksData Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - UploadBooksCode Metadata: 'AWS::CloudFormation::Designer': id: 9c3f4191-a370-4344-a5b1-626228919c47 # ---------- LAMBDA FUNCTIONS for NEPTUNE --------- FunctionGetRecommendations: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-GetRecommendations' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - GetRecommendationsCode Description: Get the top 5 product recommendations from Neptune Environment: Variables: neptunedb: !GetAtt bookstoreNeptuneCluster.Endpoint Handler: index.handler MemorySize: 256 Role: 'Fn::GetAtt': - RecommendationsLambdaRole - Arn Runtime: python3.9 Timeout: 30 VpcConfig: SecurityGroupIds: - Ref: bookstoreNeptuneSecurityGroup SubnetIds: - Ref: bookstoreSubnet1 - Ref: bookstoreSubnet2 FunctionGetRecommendationsByBook: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-GetRecommendationsByBook' Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - GetRecommendationsByBookCode Description: Get friends who purchased this book Environment: Variables: neptunedb: !GetAtt bookstoreNeptuneCluster.Endpoint Handler: index.handler MemorySize: 256 Role: 'Fn::GetAtt': - RecommendationsLambdaRole - Arn Runtime: python3.9 Timeout: 30 VpcConfig: SecurityGroupIds: - Ref: bookstoreNeptuneSecurityGroup SubnetIds: - Ref: bookstoreSubnet1 - Ref: bookstoreSubnet2 # ---------- LAMBDA FUNCTIONS for OPENSEARCH and ELASTICACHE --------- FunctionSearch: Type: 'AWS::Lambda::Function' DependsOn: - OSSearchRole - OpenSearchDomain - TBooks Properties: FunctionName: !Sub '${ProjectName}-Search' Description: Search for books across book names, authors, and categories Handler: index.handler MemorySize: 256 Role: !GetAtt - OSSearchRole - Arn Runtime: python3.8 Layers: - !Ref PythonLambdaLayer Timeout: '60' VpcConfig: SecurityGroupIds: - Fn::GetAtt: [bookstoreVPC, DefaultSecurityGroup] SubnetIds: - Ref: bookstoreSubnet1 Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - SearchCode Environment: Variables: ESENDPOINT: !GetAtt - OpenSearchDomain - DomainEndpoint REGION: !Ref 'AWS::Region' FunctionGetBestSellers: Type: 'AWS::Lambda::Function' DependsOn: - RedisRole - ElastiCacheCluster - TOrders Properties: FunctionName: !Sub '${ProjectName}-GetBestSellers' Description: Get the top 20 best selling books from ElastiCache Handler: index.handler MemorySize: 256 Role: !GetAtt - RedisRole - Arn Runtime: nodejs16.x Timeout: '60' VpcConfig: SecurityGroupIds: - Ref: redisLambdaSecurityGroup SubnetIds: - Ref: bookstoreSubnet1 Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - GetBestSellersCode Environment: Variables: URL: !GetAtt - ElastiCacheCluster - RedisEndpoint.Address # ---------- LAMBDA PERMISSIONS --------- FunctionListOrdersPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionListOrders Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionGetRecommendationsPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionGetRecommendations Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* FunctionGetRecommendationsByBookPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionGetRecommendationsByBook Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* FunctionGetBookPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionGetBook Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionListBooksPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionListBooks Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionUpdateCartPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionUpdateCart Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionGetCartItemPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionGetCartItem Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionListItemsInCartPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionListItemsInCart Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionAddToCartPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionAddToCart Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionRemoveFromCartPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionRemoveFromCart Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionCheckoutPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionCheckout Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionSearchPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionSearch Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c FunctionGetBestSellersPermissions: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref FunctionGetBestSellers Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* Metadata: 'AWS::CloudFormation::Designer': id: 06a821b7-638e-471c-ab50-51710b73767c # ---------- API GATEWAY --------- AppApi: Type: 'AWS::ApiGateway::RestApi' Properties: Name: !Sub '${ProjectName}-Bookstore' Description: API used for Bookstore requests FailOnWarnings: true Metadata: 'AWS::CloudFormation::Designer': id: 3e7a32d4-9e4d-4ecc-89b2-77203c5d926b RecomendationsApiRequestGET: DependsOn: - FunctionGetRecommendations Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionGetRecommendations - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResourceId: !Ref RecommendationsApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty RecomendationsApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref RecommendationsApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' RecomendationsByBookApiRequestGET: DependsOn: - FunctionGetRecommendationsByBook Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionGetRecommendationsByBook - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResourceId: !Ref RecommendationsByBookApiResource RequestParameters: method.request.path.bookId: false RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty RecomendationsByBookApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref RecommendationsByBookApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' BooksApiRequestGET: DependsOn: - FunctionListOrdersPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionListBooks - Arn - /invocations IntegrationResponses: - StatusCode: 200 RequestParameters: method.request.querystring.category: false ResourceId: !Ref BooksApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: 5a740957-6b64-4ddd-bd1d-3027df7fc6d5 BooksApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref BooksApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' Metadata: 'AWS::CloudFormation::Designer': id: f0f16a94-104a-431f-8c87-930f56274955 BookItemApiRequestGET: DependsOn: FunctionGetBookPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionGetBook - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResponseTemplates: application/json: $input.json('$.body') RequestParameters: method.request.path.id: true ResourceId: !Ref BookItemApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: a311ee38-d902-4856-9f09-1d00d1bc8600 BookItemApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref BookItemApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' Metadata: 'AWS::CloudFormation::Designer': id: c31e25ac-e338-4d21-9e4b-881a16e0a494 OrdersApiRequestGET: DependsOn: FunctionListOrdersPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionListOrders - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResourceId: !Ref OrdersApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: 9007f88f-407a-4ab7-9ebb-ce6fa13b223e OrdersApiRequestPOST: DependsOn: FunctionCheckoutPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: POST Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionCheckout - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResourceId: !Ref OrdersApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: 454289ef-fb5c-4a1f-941d-4016ebc9cf24 OrdersApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref OrdersApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' Metadata: 'AWS::CloudFormation::Designer': id: e97bf15c-b88b-48b7-b6d8-bffcaa87b24f CartApiRequestGET: DependsOn: FunctionListItemsInCartPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionListItemsInCart - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResourceId: !Ref CartApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: 3023c794-550d-4066-8a59-c139f4f54616 CartApiRequestPOST: DependsOn: FunctionAddToCartPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: POST Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionAddToCart - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResourceId: !Ref CartApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: 49458e1d-a6a5-4c44-96b5-f1e3f6c694a8 CartApiRequestPUT: DependsOn: FunctionUpdateCartPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: PUT Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionUpdateCart - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResourceId: !Ref CartApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: 6a4462a7-45d0-4bd9-a08a-03cd5621e0ce CartApiRequestDELETE: DependsOn: FunctionRemoveFromCartPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: DELETE Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionRemoveFromCart - Arn - /invocations IntegrationResponses: - StatusCode: 200 RequestParameters: method.request.path.id: true ResourceId: !Ref CartApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: a6392891-b014-4ea9-93a2-831f602268aa CartApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref CartApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' Metadata: 'AWS::CloudFormation::Designer': id: 97efc723-a637-41c7-b712-3af9363a1702 CartItemApiRequestGET: DependsOn: FunctionGetCartItemPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionGetCartItem - Arn - /invocations IntegrationResponses: - StatusCode: 200 RequestParameters: method.request.path.bookId: true ResourceId: !Ref CartItemApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty Metadata: 'AWS::CloudFormation::Designer': id: acebdfda-9b64-45f8-a888-3d1251bba41a CartItemApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref CartItemApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' Metadata: 'AWS::CloudFormation::Designer': id: 0e6ce8f6-0271-439e-afcf-4e4d23c1edc3 SearchApiRequestGET: DependsOn: FunctionSearchPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionSearch - Arn - /invocations IntegrationResponses: - StatusCode: 200 RequestParameters: method.request.querystring.q: false ResourceId: !Ref SearchApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty SearchApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref SearchApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' BestsellersApiRequestGET: DependsOn: FunctionGetBestSellersPermissions Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: AWS_IAM HttpMethod: GET Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - FunctionGetBestSellers - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResourceId: !Ref BestsellersApiResource RestApiId: !Ref AppApi MethodResponses: - StatusCode: 200 ResponseModels: application/json: Empty BestsellersApiRequestOPTIONS: Type: 'AWS::ApiGateway::Method' Properties: ResourceId: !Ref BestsellersApiResource RestApiId: !Ref AppApi AuthorizationType: None HttpMethod: OPTIONS Integration: Type: MOCK IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: >- 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH''' method.response.header.Access-Control-Allow-Origin: '''*''' ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' RecommendationsApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !GetAtt - AppApi - RootResourceId PathPart: recommendations RecommendationsByBookApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !Ref RecommendationsApiResource PathPart: '{bookId}' BooksApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !GetAtt - AppApi - RootResourceId PathPart: books Metadata: 'AWS::CloudFormation::Designer': id: e0ffbcc7-5f62-43ac-b377-47ec80a5a71e BookItemApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !Ref BooksApiResource PathPart: '{id}' Metadata: 'AWS::CloudFormation::Designer': id: 25b4ecc5-9d47-49cb-b5c4-d03574eee62e OrdersApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !GetAtt - AppApi - RootResourceId PathPart: orders Metadata: 'AWS::CloudFormation::Designer': id: cecab4d7-120d-48b2-99d9-1152b429cebc CartApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !GetAtt - AppApi - RootResourceId PathPart: cart Metadata: 'AWS::CloudFormation::Designer': id: 9747c416-4f2e-45b1-bc9d-16335256f1db CartItemApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !Ref CartApiResource PathPart: '{bookId}' Metadata: 'AWS::CloudFormation::Designer': id: a79e1eec-10b0-4593-9ea4-9c6b82c0ec8b SearchApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !GetAtt - AppApi - RootResourceId PathPart: search BestsellersApiResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref AppApi ParentId: !GetAtt - AppApi - RootResourceId PathPart: bestsellers ApiAuthorizer: Type: 'AWS::ApiGateway::Authorizer' Properties: AuthorizerResultTtlInSeconds: 300 IdentitySource: method.request.header.Authorization Name: CognitoDefaultUserPoolAuthorizer ProviderARNs: - !GetAtt - UserPool - Arn RestApiId: !Ref AppApi Type: COGNITO_USER_POOLS Metadata: 'AWS::CloudFormation::Designer': id: 03db03b0-3367-4eb8-bfa1-82df7efc4df4 APIDeployment: DependsOn: - BooksApiRequestGET - BooksApiRequestOPTIONS - BookItemApiRequestGET - BookItemApiRequestOPTIONS - OrdersApiRequestGET - OrdersApiRequestPOST - OrdersApiRequestOPTIONS - CartApiRequestGET - CartApiRequestPUT - CartApiRequestPOST - CartApiRequestDELETE - CartApiRequestOPTIONS - CartItemApiRequestGET - CartItemApiRequestOPTIONS - RecomendationsApiRequestGET - RecomendationsApiRequestOPTIONS - RecomendationsByBookApiRequestGET - RecomendationsByBookApiRequestOPTIONS - SearchApiRequestGET - SearchApiRequestOPTIONS - BestsellersApiRequestGET - BestsellersApiRequestOPTIONS Type: 'AWS::ApiGateway::Deployment' Properties: Description: Prod deployment for API RestApiId: !Ref AppApi StageName: prod Metadata: 'AWS::CloudFormation::Designer': id: 278dda65-02c9-49fa-a19c-3dd7afd25683 # ---------- COGNITO DEFINITIONS --------- SNSRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - cognito-idp.amazonaws.com Action: - 'sts:AssumeRole' Policies: - PolicyName: CognitoSNSPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: 'sns:publish' Resource: '*' UserPool: Type: 'AWS::Cognito::UserPool' Properties: UserPoolName: !Sub '${ProjectName}-user-pool' UsernameAttributes: - email AdminCreateUserConfig: AllowAdminCreateUserOnly: false InviteMessageTemplate: EmailMessage: 'Your username is {username} and temporary password is {####}. ' EmailSubject: Your temporary password SMSMessage: 'Your username is {username} and temporary password is {####}.' UnusedAccountValidityDays: 7 Policies: PasswordPolicy: MinimumLength: 8 RequireLowercase: false RequireNumbers: false RequireSymbols: false RequireUppercase: false AutoVerifiedAttributes: - email EmailVerificationMessage: 'Here is your verification code: {####}' EmailVerificationSubject: Your verification code Schema: - Name: email AttributeDataType: String Mutable: false Required: true UserPoolClient: Type: 'AWS::Cognito::UserPoolClient' Properties: ClientName: !Sub '${ProjectName}-client' GenerateSecret: false UserPoolId: !Ref UserPool IdentityPool: Type: 'AWS::Cognito::IdentityPool' Properties: IdentityPoolName: !Sub '${ProjectName}Identity' AllowUnauthenticatedIdentities: true CognitoIdentityProviders: - ClientId: !Ref UserPoolClient ProviderName: !GetAtt - UserPool - ProviderName CognitoUnAuthorizedRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: - 'sts:AssumeRoleWithWebIdentity' Condition: StringEquals: 'cognito-identity.amazonaws.com:aud': !Ref IdentityPool 'ForAnyValue:StringLike': 'cognito-identity.amazonaws.com:amr': unauthenticated Policies: - PolicyName: CognitoUnauthorizedPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'mobileanalytics:PutEvents' - 'cognito-sync:*' Resource: '*' CognitoAuthorizedRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: - 'sts:AssumeRoleWithWebIdentity' Condition: StringEquals: 'cognito-identity.amazonaws.com:aud': !Ref IdentityPool 'ForAnyValue:StringLike': 'cognito-identity.amazonaws.com:amr': authenticated Policies: - PolicyName: CognitoAuthorizedPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'mobileanalytics:PutEvents' - 'cognito-sync:*' - 'cognito-identity:*' Resource: '*' - Effect: Allow Action: - 'execute-api:Invoke' Resource: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref AppApi - /* IdentityPoolRoleMapping: Type: 'AWS::Cognito::IdentityPoolRoleAttachment' Properties: IdentityPoolId: !Ref IdentityPool Roles: authenticated: !GetAtt - CognitoAuthorizedRole - Arn unauthenticated: !GetAtt - CognitoUnAuthorizedRole - Arn BooksUploader: Type: 'Custom::CustomResource' Properties: ServiceToken: !GetAtt FunctionUploadBooks.Arn ParameterOne: Parameter to pass into Custom Lambda Function DependsOn: - FunctionUploadBooks - UpdateSearchCluster - OpenSearchDomain # ------------------------ FRONTEND ------------------------ AssetsCodeRepository: Type: 'AWS::CodeCommit::Repository' Properties: RepositoryDescription: Code repository for web application RepositoryName: !Sub '${ProjectName}-WebAssets' Metadata: 'AWS::CloudFormation::Designer': id: a2e798f0-374b-4297-b68f-ce7766170a6d AssetsBucket: Type: 'AWS::S3::Bucket' Properties: AccessControl: Private MetricsConfigurations: - Id: EntireBucket WebsiteConfiguration: IndexDocument: index.html Metadata: 'AWS::CloudFormation::Designer': id: a141b7c9-9999-40e3-8702-f1cf283573ef AssetsBucketPolicy: Type: 'AWS::S3::BucketPolicy' Properties: Bucket: !Ref AssetsBucket PolicyDocument: Statement: - Action: 's3:GetObject' Effect: Allow Resource: !Sub 'arn:aws:s3:::${AssetsBucket}/*' Principal: AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${AssetsBucketOriginAccessIdentity} DependsOn: - AssetsBucketOriginAccessIdentity Metadata: 'AWS::CloudFormation::Designer': id: c177c7a0-df73-4587-90aa-de6b7c3a53b2 AssetsBucketOriginAccessIdentity: Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity' Properties: CloudFrontOriginAccessIdentityConfig: Comment: !Sub 'OriginAccessIdentity for ${AssetsBucket}' Metadata: 'AWS::CloudFormation::Designer': id: f391801b-d255-40b5-be40-725ad2e93a22 AssetsCDN: Type: 'AWS::CloudFront::Distribution' DependsOn: - AssetsBucketOriginAccessIdentity Properties: DistributionConfig: Enabled: true Comment: !Sub 'CDN for ${AssetsBucket}' DefaultRootObject: index.html Origins: - DomainName: !Join - '' - - !Sub '${AssetsBucket}.s3' - !If [IADRegion, '', !Sub '-${AWS::Region}'] - '.amazonaws.com' Id: S3 S3OriginConfig: OriginAccessIdentity: !Sub >- origin-access-identity/cloudfront/${AssetsBucketOriginAccessIdentity} DefaultCacheBehavior: TargetOriginId: S3 ViewerProtocolPolicy: https-only ForwardedValues: QueryString: 'false' Metadata: 'AWS::CloudFormation::Designer': id: 16b95097-f526-4bf7-b997-ce2f6187ef4d CodeBuildRole: Description: Creating service role in IAM for AWS CodeBuild Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${ProjectName}-codebuild-role' AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - codebuild.amazonaws.com Action: 'sts:AssumeRole' Policies: - PolicyName: codebuild-policy PolicyDocument: Statement: - Action: - 's3:PutObject' - 's3:GetObject' - 's3:GetObjectVersion' - 's3:GetBucketVersioning' Resource: - !Join - '' - - !GetAtt AssetsBucket.Arn - /* - !Join - '' - - !GetAtt PipelineArtifactsBucket.Arn - /* Effect: Allow - PolicyName: codebuild-logs PolicyDocument: Statement: - Action: - 'logs:CreateLogStream' - 'logs:PutLogEvents' - 'logs:CreateLogGroup' - 'cloudfront:CreateInvalidation' Resource: '*' Effect: Allow Path: / Metadata: 'AWS::CloudFormation::Designer': id: 3aa76bbe-43cb-455a-9e85-48663b8ba66e CodePipelineRole: Description: Creating service role in IAM for AWS CodePipeline Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${ProjectName}-CodePipeline-Role' AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - codepipeline.amazonaws.com Action: 'sts:AssumeRole' Policies: - PolicyName: codecommit-for-codepipeline PolicyDocument: Statement: - Action: - 'codecommit:GetBranch' - 'codecommit:GetCommit' - 'codecommit:UploadArchive' - 'codecommit:GetUploadArchiveStatus' - 'codecommit:CancelUploadArchive' Resource: !GetAtt AssetsCodeRepository.Arn Effect: Allow - PolicyName: artifacts-for-pipeline PolicyDocument: Statement: - Action: - 's3:PutObject' - 's3:GetObject' Resource: 'Fn::Join': - '' - - 'Fn::GetAtt': - PipelineArtifactsBucket - Arn - /* Effect: Allow - PolicyName: codebuild-for-pipeline PolicyDocument: Statement: - Action: - 'codebuild:BatchGetBuilds' - 'codebuild:StartBuild' Resource: !GetAtt - CodeBuildProject - Arn Effect: Allow Path: / Metadata: 'AWS::CloudFormation::Designer': id: c7b2d059-8720-4407-a75f-9b5f50c503d1 PipelineArtifactsBucket: Type: 'AWS::S3::Bucket' Properties: AccessControl: Private Metadata: 'AWS::CloudFormation::Designer': id: bbbc886f-d307-45dc-a6e3-63353f40a4f4 CodeBuildProject: DependsOn: - PipelineArtifactsBucket Description: Creating AWS CodeBuild project Type: 'AWS::CodeBuild::Project' Properties: Artifacts: Type: CODEPIPELINE Description: !Sub 'Building stage for ${ProjectName}.' Environment: ComputeType: BUILD_GENERAL1_SMALL EnvironmentVariables: - Name: S3_BUCKET Value: !Ref PipelineArtifactsBucket Image: 'aws/codebuild/standard:2.0' Type: LINUX_CONTAINER Name: !Sub '${ProjectName}-build' ServiceRole: !Ref CodeBuildRole Source: Type: CODEPIPELINE BuildSpec: !Sub | version: 0.2 phases: install: runtime-versions: nodejs: 10 pre_build: commands: - echo Installing NPM dependencies... - npm install build: commands: - npm run build post_build: commands: - echo Uploading to AssetsBucket - aws s3 cp --recursive ./build s3://${AssetsBucket}/ - aws s3 cp --cache-control="max-age=0, no-cache, no-store, must-revalidate" ./build/service-worker.js s3://${AssetsBucket}/ - aws s3 cp --cache-control="max-age=0, no-cache, no-store, must-revalidate" ./build/index.html s3://${AssetsBucket}/ - aws cloudfront create-invalidation --distribution-id ${AssetsCDN} --paths /index.html /service-worker.js artifacts: files: - '**/*' base-directory: build Tags: - Key: app-name Value: !Ref ProjectName TimeoutInMinutes: 5 Metadata: 'AWS::CloudFormation::Designer': id: a83e893f-d993-4a92-9c51-b00425960a96 AssetsCodePipeline: Type: 'AWS::CodePipeline::Pipeline' Properties: Name: !Sub '${ProjectName}-Assets-Pipeline' RoleArn: !GetAtt - CodePipelineRole - Arn ArtifactStore: Location: !Ref PipelineArtifactsBucket Type: S3 Stages: - Name: Source Actions: - Name: Source InputArtifacts: [] ActionTypeId: Version: '1' Category: Source Owner: AWS Provider: CodeCommit Configuration: BranchName: master RepositoryName: !Sub '${ProjectName}-WebAssets' OutputArtifacts: - Name: !Sub '${ProjectName}-SourceArtifact' - Name: Build Actions: - Name: build-and-deploy InputArtifacts: - Name: !Sub '${ProjectName}-SourceArtifact' ActionTypeId: Category: Build Owner: AWS Version: '1' Provider: CodeBuild OutputArtifacts: - Name: !Sub '${ProjectName}-BuildArtifact' Configuration: ProjectName: !Sub '${ProjectName}-build' RunOrder: 1 Metadata: 'AWS::CloudFormation::Designer': id: bd7060e4-3e26-442c-9c4e-141ca61a9590 DependsOn: - PipelineArtifactsBucket SeederFunction: Properties: Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - SeederFunctionCode Description: CodeCommit repository seeder Handler: seeder.SeedRepositoryHandler MemorySize: 3008 Role: 'Fn::GetAtt': - SeederRole - Arn Runtime: java8 Timeout: 900 Type: 'AWS::Lambda::Function' Metadata: 'AWS::CloudFormation::Designer': id: 6b5081a3-db00-41db-a607-c9d5f98cf394 DependsOn: - AssetsCodeRepository CreateOSRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${ProjectName}-CreateOSRole' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: CreateRolePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'iam:CreateServiceLinkedRole' Resource: 'arn:aws:iam::*:role/aws-service-role/opensearchservice.amazonaws.com/AWSServiceRoleForAmazonOpenSearchService' CreateOSRoleFunction: Properties: Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - CreateOSRoleCode Description: Create OpenSearch role Handler: index.handler Role: 'Fn::GetAtt': - CreateOSRole - Arn Runtime: nodejs16.x Timeout: 300 Type: 'AWS::Lambda::Function' ESRoleCreator: Type: 'Custom::CustomResource' Properties: ServiceToken: !GetAtt CreateOSRoleFunction.Arn ParameterOne: Parameter to pass into Custom Lambda Function DependsOn: CreateOSRoleFunction Metadata: 'AWS::CloudFormation::Designer': id: 63b08124-fccb-4874-ab13-ce6cfe6ce885 UpdateConfigFunction: Properties: Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - UpdateConfigCode Description: Update config for CodeCommit repository Handler: index.handler Role: 'Fn::GetAtt': - SeederRole - Arn Runtime: nodejs16.x Timeout: 300 Environment: Variables: API_URL: !Sub 'https://${AppApi}.execute-api.${AWS::Region}.amazonaws.com/prod' BRANCH_NAME: master REGION: !Ref 'AWS::Region' REPOSITORY_NAME: !Sub '${ProjectName}-WebAssets' USER_POOL_ID: !Ref UserPool APP_CLIENT_ID: !Ref UserPoolClient IDENTITY_POOL_ID: !Ref IdentityPool Type: 'AWS::Lambda::Function' Metadata: 'AWS::CloudFormation::Designer': id: 00cdcc4b-3885-4a51-bdad-776ac697419c DependsOn: - AssetsCodeRepository - SeederFunction - RepositorySeeder - AppApi - UserPool - UserPoolClient - IdentityPool SeederRole: Properties: AssumeRolePolicyDocument: Statement: - Action: - 'sts:AssumeRole' Effect: Allow Principal: Service: - lambda.amazonaws.com Version: 2012-10-17 ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyDocument: Statement: - Action: - 'codecommit:GetRepository' - 'codecommit:GitPush' - 'codecommit:GetBranch' - 'codecommit:PutFile' Effect: Allow Resource: !GetAtt AssetsCodeRepository.Arn Version: 2012-10-17 PolicyName: SeederRolePolicy - PolicyDocument: Statement: - Action: - 'logs:*' Effect: Allow Resource: 'arn:aws:logs:*:*:*' Version: 2012-10-17 PolicyName: LogsPolicy Type: 'AWS::IAM::Role' Metadata: 'AWS::CloudFormation::Designer': id: a10d77af-fa97-40ee-a6f0-5d9988792aeb RepositorySeeder: Properties: ServiceToken: 'Fn::GetAtt': - SeederFunction - Arn sourceUrl: !FindInMap [Constants, AppKeys, SeedRepository] targetRepositoryName: !Sub '${ProjectName}-WebAssets' targetRepositoryRegion: '${AWS::Region}' Type: 'Custom::RepositorySeeder' Metadata: 'AWS::CloudFormation::Designer': id: 688ea29a-9fa9-40c9-b5a8-85e863cc8a4c RepositoryUpdater: Type: 'Custom::CustomResource' Properties: ServiceToken: !GetAtt UpdateConfigFunction.Arn ParameterOne: Parameter to pass into Custom Lambda Function DependsOn: UpdateConfigFunction Metadata: 'AWS::CloudFormation::Designer': id: 63b08124-fccb-4874-ab13-ce6cfe6ce885 # ---------- CLEANUP STACK --------- BucketCleanupRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${ProjectName}-BucketCleanupRole' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: BucketCleanupPolicy PolicyDocument: Statement: - Action: - 's3:List*' - 's3:DeleteObject' Effect: Allow Resource: - !GetAtt AssetsBucket.Arn - !GetAtt PipelineArtifactsBucket.Arn - !Join [ "", [ !GetAtt AssetsBucket.Arn , "/*" ]] - !Join [ "", [ !GetAtt PipelineArtifactsBucket.Arn , "/*" ]] Version: 2012-10-17 BucketCleanupFunction: Type: 'AWS::Lambda::Function' Properties: FunctionName: !Sub '${ProjectName}-BucketCleanup' Description: Cleanup S3 buckets when deleting stack Handler: index.handler MemorySize: 256 Role: !GetAtt BucketCleanupRole.Arn Runtime: python3.9 Timeout: 30 Layers: - !Ref PythonLambdaLayer Code: S3Bucket: !FindInMap - S3Buckets - !Ref 'AWS::Region' - Bucket S3Key: !FindInMap - Constants - S3Keys - DeleteBucketsCode DeleteBucketsObjects: Type: 'Custom::CustomResource' Properties: ServiceToken: !GetAtt BucketCleanupFunction.Arn BucketNames: - !Ref AssetsBucket - !Ref PipelineArtifactsBucket DependsOn: - BucketCleanupFunction - BucketCleanupRole BookstoreCognitoDefaultUser: DependsOn: - UserPool Type: 'AWS::CloudFormation::Stack' Properties: TemplateURL: !Join [ "", [ "https://s3." , !Ref "AWS::Region" , ".amazonaws.com/" , !FindInMap [ S3Buckets, !Ref 'AWS::Region', Bucket ] , "/templates/bookstore-cognito-user.yaml" ] ] Parameters: CognitoUserPool: !Ref UserPool # ----- WARMER EVENTS ------ ScheduledRuleSearchRecs: Type: AWS::Events::Rule Properties: ScheduleExpression: "rate(10 minutes)" State: "ENABLED" Targets: - Arn: Fn::GetAtt: - "FunctionSearch" - "Arn" Id: "SearchTarget" Input: "{\"queryStringParameters\":{\"q\":\"Umami\"}}" - Arn: Fn::GetAtt: - "FunctionGetRecommendations" - "Arn" Id: "GetRecommendationTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionGetBestSellers" - "Arn" Id: "GetBestsellersTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionGetRecommendationsByBook" - "Arn" Id: "GetRecommendationByBookTarget" Input: "{\"queryStringParameters\":{\"bookId\":\"zceo3fdn-d93b-11e8-9f8b-f2801f1b9fd1\"}}" - Arn: Fn::GetAtt: - "FunctionGetBook" - "Arn" Id: "BookTarget" Input: "{\"source\":\"warmer\"}" ScheduledRuleCart: Type: AWS::Events::Rule Properties: ScheduleExpression: "rate(10 minutes)" State: "ENABLED" Targets: - Arn: Fn::GetAtt: - "FunctionAddToCart" - "Arn" Id: "AddToCartTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionUpdateCart" - "Arn" Id: "UpdateCartTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionListItemsInCart" - "Arn" Id: "ListItemsInCartTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionRemoveFromCart" - "Arn" Id: "RemoveFromCartTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionGetCartItem" - "Arn" Id: "GetCartItemTarget" Input: "{\"source\":\"warmer\"}" ScheduledRuleOrdersBooks: Type: AWS::Events::Rule Properties: ScheduleExpression: "rate(10 minutes)" State: "ENABLED" Targets: - Arn: Fn::GetAtt: - "FunctionCheckout" - "Arn" Id: "CheckoutTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionListOrders" - "Arn" Id: "ListOrdersTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionListBooks" - "Arn" Id: "ListBooksTarget" Input: "{\"source\":\"warmer\"}" - Arn: Fn::GetAtt: - "FunctionGetBook" - "Arn" Id: "GetBookTarget" Input: "{\"source\":\"warmer\"}" Outputs: CodeRepository: Description: Code repository for the web application. Export: Name: !Sub '${ProjectName}CodeRepository' Value: !GetAtt AssetsCodeRepository.Name WebApplication: Description: The URL for the web application Export: Name: !Sub '${ProjectName}WebApp' Value: !Sub - https://${Domain} - { Domain: !GetAtt AssetsCDN.DomainName }