AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: 'Deploy Serverless image gallery application.' Outputs: ApiUrl: Description: URL of your API endpoint Value: !Sub "https://${SrvrLsAppApi}.execute-api.${AWS::Region}.amazonaws.com/Stage" DDBTable: Description: DynamoDB Table Value: Ref: ImageMetadata RestApiId: Description: API Gateway API ID Value: Ref: SrvrLsAppApi GalleryS3Bucket: Description: Bucket for storing uploaded images Value: Ref: GalleryBkt S3ReplAccessRoleId: Description: Cross region replication role Value: !Sub "${S3ReplAccessRole.Arn}" Resources: SrvrLsAppApi: Type: AWS::Serverless::Api Properties: StageName: prod DefinitionBody: swagger: "2.0" info: version: "2018-04-24T06:43:00Z" title: "GalleryAppAPI" basePath: "/prod" schemes: - "https" paths: /searchimage: get: consumes: - "application/json" produces: - "application/json" parameters: - name: "searchKey" in: "query" required: true type: "string" responses: "200": description: "200 response" schema: $ref: "#/definitions/Empty" headers: Access-Control-Allow-Origin: type: "string" x-amazon-apigateway-integration: uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SearchHandler.Arn}/invocations" responses: default: statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" requestTemplates: application/json: "{\n \"searchKey\": \"$input.params('searchKey')\"\ \n}" passthroughBehavior: "when_no_match" httpMethod: "POST" contentHandling: "CONVERT_TO_TEXT" type: "aws" options: consumes: - "application/json" produces: - "application/json" responses: "200": description: "200 response" schema: $ref: "#/definitions/Empty" headers: Access-Control-Allow-Origin: type: "string" Access-Control-Allow-Methods: type: "string" Access-Control-Allow-Headers: type: "string" x-amazon-apigateway-integration: responses: default: statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" 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-Origin: "'*'" requestTemplates: application/json: "{\"statusCode\": 200}" passthroughBehavior: "when_no_match" type: "mock" /upload-photo: post: produces: - "application/json" responses: "200": description: "200 response" schema: $ref: "#/definitions/Empty" security: - sigv4: [] x-amazon-apigateway-integration: uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UploadS3.Arn}/invocations" responses: default: statusCode: "200" passthroughBehavior: "when_no_match" httpMethod: "POST" contentHandling: "CONVERT_TO_TEXT" type: "aws_proxy" options: consumes: - "application/json" produces: - "application/json" responses: "200": description: "200 response" schema: $ref: "#/definitions/Empty" headers: Access-Control-Allow-Origin: type: "string" Access-Control-Allow-Methods: type: "string" Access-Control-Allow-Headers: type: "string" x-amazon-apigateway-integration: responses: default: statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'" method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Origin: "'*'" requestTemplates: application/json: "{\"statusCode\": 200}" passthroughBehavior: "when_no_match" type: "mock" definitions: Empty: type: "object" title: "Empty Schema" UploadS3: Type: 'AWS::Serverless::Function' Properties: Handler: index.handler Runtime: nodejs6.10 InlineCode: "const AWS = require('aws-sdk');\n const s3 = new AWS.S3({apiVersion: '2006-03-01'});\n exports.handler = (event, context, callback) => {\n const body = JSON.parse(event.body);\n const fileName = body.fileName;\n const contentType = body.contentType;\n const bucket = process.env.BUCKET;\n s3.getSignedUrl('putObject', {\n Bucket: bucket,\n Expires: 60 * 60,\n Key: fileName,\n ContentType: contentType\n }, function(err, url) {\n if (err) {\n console.log('Error : ' + err);\n callback(null, {\n statusCode: '500',\n headers: {'Access-Control-Allow-Origin': '*'},\n body: JSON.stringify({'message': err.message})\n });\n } else {\n callback(null, {\n statusCode: '200',\n headers: {'Access-Control-Allow-Origin': '*'},\n body: JSON.stringify({'url': url})\n });\n }\n });\n };\n" Description: '' MemorySize: 128 Timeout: 3 Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 's3:PutObject' Resource: !Sub "${GalleryBkt.Arn}/*" - Effect: Allow Action: - 'logs:*' Resource: "*" Events: uploadapi: Type: Api Properties: Path: /upload-photo Method: POST RestApiId: Ref: SrvrLsAppApi Environment: Variables: BUCKET: Ref : GalleryBkt SearchHandler: Type: 'AWS::Serverless::Function' Properties: Handler: index.handler Runtime: nodejs6.10 Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - "dynamodb:GetItem" - "dynamodb:Query" - "dynamodb:Scan" Resource: !Sub "${ImageMetadata.Arn}" - Effect: Allow Action: - "s3:Get*" - "s3:List*" Resource: !Sub "${GalleryBkt.Arn}/*" - Effect: Allow Action: - 'logs:*' Resource: "*" InlineCode: " const util = require('util');\n const AWS = require('aws-sdk');\n const ddb = new AWS.DynamoDB({ apiVersion: '2012-10-08' });\n const s3 = new AWS.S3({apiVersion: '2006-03-01'});\n exports.handler = (event, context, callback) => {\n console.log('Reading input from event:', util.inspect(event, { depth: 5 }));\n const srcKey = event.searchKey;\n var filelist = [];\n var items = searchDDB(srcKey, function(items) {\n items.forEach(function(item, index, array) {\n var imgJson = {};\n imgJson['name'] = item.img_id.S;\n imgJson['sUrl'] = getSignedS3URL(item.img_id.S);\n imgJson['confidence'] = item.confidence.N;\n filelist.push(imgJson);\n });\n console.log('Reading output:', util.inspect(filelist, { depth: 5 }));\n context.done(null, filelist);\n });\n };\n var searchDDB = function(searchKey, callback) {\n var params = {\n TableName: process.env.TBL_NAME,\n KeyConditionExpression: '#nm = :nVal',\n ExpressionAttributeNames:{\n '#nm': 'name'\n },\n ExpressionAttributeValues:{\n ':nVal': {'S':searchKey}\n }\n };\n ddb.query(params, function(err, data) {\n if (err) {\n console.log('Error', err);\n return false;\n } else {\n data.Items.forEach(function(element, index, array) {\n console.log(element.name.S + ' (' + element.img_id.S + ')');\n });\n callback(data.Items);\n }\n });\n }\n var getSignedS3URL = function(imgId) {\n var params = { Bucket: process.env.BUCKET, Key: imgId };\n return s3.getSignedUrl('getObject', params);\n }\n" Description: '' MemorySize: 128 Timeout: 3 Events: searchApi: Type: Api Properties: Path: /searchimage Method: GET RestApiId: Ref: SrvrLsAppApi Environment: Variables: BUCKET: Ref : GalleryBkt TBL_NAME: Ref : ImageMetadata GatherMetadata: Type: 'AWS::Serverless::Function' Properties: Handler: index.handler Runtime: nodejs6.10 Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'dynamodb:PutItem' Resource: !Sub "${ImageMetadata.Arn}" - Effect: Allow Action: - 'logs:*' Resource: "*" - Effect: Allow Action: - 'rekognition:*' Resource: "*" - Effect: Allow Action: - "s3:Get*" - "s3:List*" Resource: "*" InlineCode: "const util = require('util');\n const AWS = require('aws-sdk');\n const rekognition = new AWS.Rekognition();\n const ddb = new AWS.DynamoDB({ apiVersion: '2012-10-08' });\n exports.handler = function(event, context) {\n console.log('Reading input from event:', util.inspect(event, { depth: 5 }));\n const srcBucket = event.Records[0].s3.bucket.name;\n const srcKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\\+/g, ' '));\n var params = {\n Image: {\n S3Object: {\n Bucket: srcBucket,\n Name: srcKey\n }\n },\n MaxLabels: 10,\n MinConfidence: 60\n };\n \n rekognition.detectLabels(params).promise().then(function(data) {\n console.log(' Data for src ' + srcKey);\n console.log(' Labels : ' + util.inspect(data, { depth: 3 }));\n addMetadata(event, srcKey, data);\n }).catch(function(err) {\n console.log('Error updating the metadata for' + srcKey);\n });\n };\n var addMetadata = function(event, srcKey, data) {\n var params = {\n TableName: 'ImageMetadata',\n Item: {\n 'IMG_ID': { S: srcKey },\n 'CUSTOMER_NAME': { S: 'Richard Roe' },\n }\n };\n data.Labels.forEach(function(label) {\n var confidence = parseFloat(label.Confidence);\n console.log(' In addMetadata function ' + confidence);\n var params = {\n TableName: process.env.TBL_NAME,\n Item: {\n 'img_id': { S: srcKey },\n 'name': { S: label.Name },\n 'confidence': { N: label.Confidence + '' }\n }\n };\n\n console.log(' In addMetadata function ' + util.inspect(params, { depth: 3 }));\n ddb.putItem(params, function(err, data) {\n if (err) {\n console.log('Error', err);\n } else {\n console.log('Success', data);\n }\n });\n })\n }\n" Description: '' MemorySize: 128 Timeout: 3 Events: BucketEvent1: Type: S3 Properties: Bucket: Ref: GalleryBkt Events: - 's3:ObjectCreated:*' Environment: Variables: TBL_NAME: !Ref ImageMetadata GalleryBkt: Type: AWS::S3::Bucket Properties: AccessControl: Private VersioningConfiguration: Status: Enabled CorsConfiguration: CorsRules: - AllowedHeaders: - '*' AllowedMethods: - PUT AllowedOrigins: - '*' Id: AllowHTTP MaxAge: '300' S3ReplAccessRole: Properties: AssumeRolePolicyDocument: Statement: - Action: "sts:AssumeRole" Effect: Allow Principal: Service: - s3.amazonaws.com Version: "2012-10-17" Path: / Policies: - PolicyDocument: Statement: - Action: - "s3:GetReplicationConfiguration" - "s3:ListBucket" Effect: Allow Resource: !Sub "${GalleryBkt.Arn}" - Action: - "s3:GetObjectVersion" - "s3:GetObjectVersionAcl" - "s3:GetObjectVersionTagging" Effect: Allow Resource: !Sub "${GalleryBkt.Arn}/*" - Action: - "s3:ReplicateObject" - "s3:ReplicateDelete" - "s3:ReplicateTags" Effect: Allow Resource: "arn:aws:s3:::*/*" Version: '2012-10-17' PolicyName: S3ReplAccessPolicy Type: AWS::IAM::Role ImageMetadata: Properties: AttributeDefinitions: - AttributeName: name AttributeType: S - AttributeName: img_id AttributeType: S KeySchema: - AttributeName: name KeyType: HASH - AttributeName: img_id KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 StreamSpecification: StreamViewType: "NEW_AND_OLD_IMAGES" TableName : !Sub "${AWS::StackName}_ImgMetadata" Type: AWS::DynamoDB::Table