diff --git a/.gitignore b/.gitignore index 9af2e04..e4d30c2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules # CDK asset staging directory .cdk.staging cdk.out +.DS_Store diff --git a/lambdas/feedback/getUploadImageSignedUrl.ts b/lambdas/feedback/getUploadImageSignedUrl.ts new file mode 100644 index 0000000..8d74fd2 --- /dev/null +++ b/lambdas/feedback/getUploadImageSignedUrl.ts @@ -0,0 +1,26 @@ +/* Lambda that returns a signed URL for uploading an image to S3. + * The frontend will use this URL to upload the image directly to S3. + * and store it with the feedback form data. + */ + +import { APIGatewayProxyHandler, APIGatewayProxyEvent } from "aws-lambda"; +import { S3 } from "aws-sdk"; +import * as uuid from "uuid"; + +export const handler: APIGatewayProxyHandler = async ( + event: APIGatewayProxyEvent +) => { + const s3 = new S3(); + const params = { + Bucket: process.env.BUCKET_NAME!, + Key: `${uuid.v4()}.jpeg`, + ContentType: "image/jpeg", + }; + + const url = await s3.getSignedUrlPromise("putObject", params); + + return { + statusCode: 200, + body: JSON.stringify({ url }), + }; +}; diff --git a/lib/cdk-ishihara.ts b/lib/cdk-ishihara.ts index bbf461c..ea81ee0 100644 --- a/lib/cdk-ishihara.ts +++ b/lib/cdk-ishihara.ts @@ -8,6 +8,8 @@ import { getLambdaSimpleApi } from "./resources/lambda-api-get-plates"; import { getLambdaApiSaveFeedback, getFeedbackTable, + getUploadImageLambda, + getFeedbackImageBucket, } from "./resources/save-feedback-resources"; import { createAPIGateway } from "./resources/api-gateway-get-plates"; import { createFeedbackGatewayApi } from "./resources/api-gateway-feedback"; @@ -31,12 +33,21 @@ export class CdkIshiharaStack extends Stack { //--- Fedback resources ----- const feedbackTable = getFeedbackTable(this); + const feedbackImageBucket = getFeedbackImageBucket(this); + // Lambda used to generate signed url to upload image to s3 + const feedbackImageUploadUrlLambda = getUploadImageLambda(this, { + environment: { + BUCKET_NAME: feedbackImageBucket.bucketName, + }, + }); + const feedbackLambda = getLambdaApiSaveFeedback(this, { environment: { TABLE_NAME: feedbackTable.tableName }, }); feedbackTable.grantReadWriteData(feedbackLambda); - + feedbackImageBucket.grantReadWrite(feedbackImageUploadUrlLambda); + //--- End feedback resources ----- configureEventBridgeCron(this, plateGenerator); const hostedZone = getHostedZone(this); diff --git a/lib/resources/save-feedback-resources.ts b/lib/resources/save-feedback-resources.ts index b215e42..d1383e3 100644 --- a/lib/resources/save-feedback-resources.ts +++ b/lib/resources/save-feedback-resources.ts @@ -1,19 +1,13 @@ /* create cdk resources for feedback feature (lambda and dynamodb table) */ import { Table, TableProps } from "aws-cdk-lib/aws-dynamodb"; -import { - Function, - FunctionProps, - Runtime, - Code, -} from "aws-cdk-lib/aws-lambda"; +import { Function, FunctionProps, Runtime, Code } from "aws-cdk-lib/aws-lambda"; import { Construct } from "constructs"; import { getNamespace } from "../util"; import { AttributeType } from "aws-cdk-lib/aws-dynamodb"; import { Bucket, BucketAccessControl, HttpMethods } from "aws-cdk-lib/aws-s3"; - export function getFeedbackTable(scope: Construct, props?: TableProps): Table { return new Table(scope, `FeedbackTable${getNamespace()}`, { partitionKey: { @@ -26,7 +20,7 @@ export function getFeedbackTable(scope: Construct, props?: TableProps): Table { export function getLambdaApiSaveFeedback( scope: Construct, - //optional + //optional props?: Partial ): Function { return new Function(scope, `LambdaApiSaveFeedback${getNamespace()}`, { @@ -36,3 +30,28 @@ export function getLambdaApiSaveFeedback( ...props, }); } + +export function getUploadImageLambda( + scope: Construct, + props?: Partial +): Function { + return new Function(scope, `LambdaUploadImage${getNamespace()}`, { + runtime: Runtime.NODEJS_14_X, + handler: "uploadImage.handler", + code: Code.fromAsset("lambdas/getUploadImageSignedUrl"), + ...props, + }); +} + +export function getFeedbackImageBucket(scope: Construct): Bucket { + return new Bucket(scope, `FeedbackImageBucket${getNamespace()}`, { + accessControl: BucketAccessControl.PRIVATE, + cors: [ + { + allowedOrigins: ["*"], + allowedMethods: [HttpMethods.GET, HttpMethods.PUT, HttpMethods.HEAD], + }, + ], + bucketName: `feedback-image-${getNamespace()}`, + }); +}