in sam-or-cdk/cdk/lib/signedurl-stack.ts [17:192]
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Amazon S3 storage bucket for uploaded files
const storageBucket = new Bucket(this, 'StorageBucket')
// Amazon DynamoDB table to store short urls
const urlTable = new Table(this, 'MyDdb', {
partitionKey: {name:'id', type: AttributeType.STRING},
billingMode: BillingMode.PAY_PER_REQUEST,
timeToLiveAttribute: 'TTL'
});
// AWS Lambda function to generate the signed upload URL
const uploadSignerLambda = new NodejsFunction(this, 'UploadSignerLambda', {
entry: 'lambda/uploadSigner.ts',
handler: 'handler',
environment: {
'STORAGE_BUCKET': storageBucket.bucketName
},
tracing: Tracing.ACTIVE
})
// AWS Lambda function to generate the signed download URL
const downloadSignerLambda = new NodejsFunction(this, 'DownloadSignerLambda', {
entry: 'lambda/downloadSigner.ts',
handler: 'handler',
environment: {
'STORAGE_BUCKET': storageBucket.bucketName,
},
tracing: Tracing.ACTIVE
})
// AWS Lambda function to process the shortened URL
const fetchShortUrlLambda = new NodejsFunction(this, 'FetchShortUrlLambda', {
entry: 'lambda/fetchShortUrl.ts',
handler: 'handler',
environment: {
'URL_TABLE': urlTable.tableName
},
tracing: Tracing.ACTIVE
})
// Grants read rights to the short URL processor to read from the url DynamoDB table
urlTable.grantReadData(fetchShortUrlLambda);
// Grants rights for the upload URL generator function to create the upload signed URL
uploadSignerLambda.addToRolePolicy(new PolicyStatement({
actions:['s3:PutObject', 's3:PutObjectAcl', 's3:PutLifecycleConfiguration'],
resources: [storageBucket.bucketArn, storageBucket.arnForObjects('*')]
}))
// Grants rights for the download URL generator function to create the download signed URL
downloadSignerLambda.addToRolePolicy(new PolicyStatement({
actions:['s3:GetObject', 's3:ListBucket', 's3:GetBucketLocation', 's3:GetObjectVersion', 's3:GetLifecycleConfiguration'],
resources: [storageBucket.bucketArn, storageBucket.arnForObjects('*')]
}))
// AWS Step Functions tasks
const writeToDynamoDB = new DynamoPutItem(this, 'WriteToDynamoDB', {
table: urlTable,
resultPath: '$.DynamoResults',
outputPath: '$',
item: {
'id': DynamoAttributeValue.fromString(JsonPath.stringAt('$.DownloadSignResults.Payload.id')),
'signedUrl': DynamoAttributeValue.fromString(JsonPath.stringAt('$.DownloadSignResults.Payload.signedUrl')),
'TTL': DynamoAttributeValue.numberFromString(JsonPath.stringAt('$.DownloadSignResults.Payload.ttl'))
}
})
const getUploadSignedUrl = new LambdaInvoke(this, 'GetUploadSignedUrl', {
lambdaFunction: uploadSignerLambda,
resultPath: '$.UploadSignResults'
})
const getDownloadSignedUrl = new LambdaInvoke(this, 'GetDownloadSignedUrl', {
lambdaFunction: downloadSignerLambda,
resultPath: '$.DownloadSignResults',
outputPath: '$'
})
getDownloadSignedUrl.next(writeToDynamoDB)
const formatResults = new Pass(this, 'formatResults', {
parameters:{
'UploadUrl.$': '$[0].UploadSignResults.Payload.signedUrl',
'DownloadUrl.$': '$[1].DownloadSignResults.Payload.signedUrl',
'DownloadShortId.$': '$[1].DownloadSignResults.Payload.id'
}
})
const generateSignedUrls = new Parallel(this, 'Generate Signed URLs', {
comment: 'Fetches a signed upload and download URL for the given Key',
})
generateSignedUrls.branch(getUploadSignedUrl).branch(getDownloadSignedUrl)
// Step Functions state machine
const urlStateMachine = new StateMachine(this, 'StateMachine', {
definition: generateSignedUrls.next(formatResults),
timeout: Duration.seconds(30),
tracingEnabled: true,
stateMachineType: StateMachineType.EXPRESS
})
// IAM role for HTTP APIs
const httpApiRole = new Role(this, 'hHttpApiRole', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
inlinePolicies: {
AllowSFNExec: new PolicyDocument({
statements: [
new PolicyStatement({
actions: ['states:StartSyncExecution'],
effect: Effect.ALLOW,
resources: [urlStateMachine.stateMachineArn]
})
]
})
}
})
// OpenAPI definition to handle AWS Integration
const apiDefinition = {
"openapi": "3.0.1",
"info" : {
"title" : "Signed URL Generator - Built with AWS CDK",
},
"paths": {
"/" : {
"post": {
"responses": {
"default": {
"description": "SFN Response"
}
},
"x-amazon-apigateway-integration": {
"integrationSubtype": "StepFunctions-StartSyncExecution",
"credentials": httpApiRole.roleArn,
"requestParameters": {
"Input": "$request.body",
"StateMachineArn": urlStateMachine.stateMachineArn
},
"payloadFormatVersion": "1.0",
"type": "aws_proxy",
"connectionType": "INTERNET"
}
}
}
}
}
// Amazon API Gateway HTTP APIs and
const httpApi = new HttpApi(this, 'HttpApi');
// Required to use HTTP API construct with OpenApi definition until there is a specific method
// (See: https://dev.to/wojciechmatuszewski/starting-synchronous-express-workflows-with-api-gateway-and-cdk-367i)
const cfnApi = httpApi.node.defaultChild as CfnApi;
cfnApi.addPropertyOverride('Body', apiDefinition);
cfnApi.addPropertyDeletionOverride('Name');
cfnApi.addPropertyDeletionOverride('ProtocolType');
// Adds an additional route for fetching the short URL
httpApi.addRoutes({
path: '/{id}',
methods: [HttpMethod.GET],
integration: new LambdaProxyIntegration({
handler: fetchShortUrlLambda
}),
})
// Outputs
const apiUrlOut = new CfnOutput(this, 'API url', {
value: httpApi.url!
})
}