Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/InfrastructureGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,40 @@ const tableName = process.env.TASK_TABLE_NAME;
- **DynamoDB**: Read-write access (GetItem, UpdateItem) to the Task table
- **CloudWatch Logs**: Write access to its log group

#### Delete Task Function

**Resource Type**: AWS Lambda Function

**Configuration**:

- **Function Name**: `{app-name}-delete-task-{env}`
- **Runtime**: Node.js 24.x
- **Handler**: `handler` (bundled with esbuild)
- **Memory**: 256 MB
- **Timeout**: 10 seconds
- **Log Format**: JSON (structured logging)
- **Bundling**: Automatic TypeScript compilation with esbuild
- **Environment Variables**:
- `TASKS_TABLE`: DynamoDB table name
- `ENABLE_LOGGING`: Logging enabled flag (from `CDK_APP_ENABLE_LOGGING`)
- `LOG_LEVEL`: Minimum log level (from `CDK_APP_LOGGING_LEVEL`)
- `LOG_FORMAT`: Log output format (from `CDK_APP_LOGGING_FORMAT`)

**CloudWatch Logs**:

- **Log Group**: `/aws/lambda/{app-name}-delete-task-{env}`
- **Log Retention**:
- `prd`: 30 days
- Other environments: 7 days
- **Removal Policy**:
- `prd`: `RETAIN` (logs preserved on stack deletion)
- Other environments: `DESTROY` (logs deleted on stack deletion)

**IAM Permissions**:

- **DynamoDB**: Read-write access (DeleteItem) to the Task table
- **CloudWatch Logs**: Write access to its log group

#### Lambda Starter API

**Resource Type**: API Gateway REST API
Expand Down Expand Up @@ -586,6 +620,15 @@ const tableName = process.env.TASK_TABLE_NAME;
- 404 Not Found: Task ID does not exist
- 500 Internal Server Error: Failed to update task

- **DELETE /tasks/{taskId}**: Delete an existing task
- Integration: Lambda proxy integration with Delete Task Function
- Path Parameter: `taskId` - The unique identifier of the task
- Response: No content on successful deletion
- Success Status: 204 No Content
- Error Responses:
- 404 Not Found: Task ID does not exist or path parameter is missing
- 500 Internal Server Error: Failed to delete task

**Outputs**:

- `ApiUrl`: The API Gateway endpoint URL (e.g., `https://abc123.execute-api.us-east-1.amazonaws.com/dev/`)
Expand All @@ -594,6 +637,7 @@ const tableName = process.env.TASK_TABLE_NAME;
- `GetTaskFunctionArn`: The Get Task Lambda function ARN
- `CreateTaskFunctionArn`: The Create Task Lambda function ARN
- `UpdateTaskFunctionArn`: The Update Task Lambda function ARN
- `DeleteTaskFunctionArn`: The Delete Task Lambda function ARN

**Logging Configuration**:

Expand Down
9 changes: 9 additions & 0 deletions infrastructure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,13 @@ npm run cdk destroy --all
- Handler: Updates an existing task in DynamoDB
- IAM Permissions: Read-write access to Task table (GetItem, UpdateItem)

- **Delete Task Function** (`delete-task-{env}`)
- Runtime: Node.js 24.x
- Memory: 256 MB
- Timeout: 10 seconds
- Handler: Deletes an existing task from DynamoDB
- IAM Permissions: Read-write access to Task table (DeleteItem)

**Common Lambda Configuration:**

- Log Format: JSON (structured logging)
Expand All @@ -314,6 +321,7 @@ npm run cdk destroy --all
- `GET /tasks/{taskId}` - Get a specific task
- `POST /tasks` - Create a new task
- `PUT /tasks/{taskId}` - Update an existing task
- `DELETE /tasks/{taskId}` - Delete an existing task
- CORS: Enabled with preflight OPTIONS support
- Throttling: Rate and burst limits configured
- Stage: `{env}` (e.g., `dev`, `prd`)
Expand All @@ -326,6 +334,7 @@ npm run cdk destroy --all
- `GetTaskFunctionArn`: The Get Task Lambda function ARN
- `CreateTaskFunctionArn`: The Create Task Lambda function ARN
- `UpdateTaskFunctionArn`: The Update Task Lambda function ARN
- `DeleteTaskFunctionArn`: The Delete Task Lambda function ARN

**Logging Configuration:**

Expand Down
16 changes: 8 additions & 8 deletions infrastructure/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions infrastructure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
"esbuild": "0.27.0",
"jest": "30.2.0",
"rimraf": "6.1.2",
"ts-jest": "29.4.5",
"ts-jest": "29.4.6",
"ts-node": "10.9.2",
"typescript": "5.9.3"
},
"dependencies": {
"aws-cdk-lib": "2.230.0",
"aws-cdk-lib": "2.231.0",
"constructs": "10.4.3",
"dotenv": "17.2.3",
"source-map-support": "0.5.21",
Expand Down
32 changes: 26 additions & 6 deletions infrastructure/stacks/lambda-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ describe('LambdaStack', () => {
});
});

it('should create a delete task Lambda function', () => {
template.hasResourceProperties('AWS::Lambda::Function', {
FunctionName: 'lambda-starter-delete-task-dev',
Runtime: 'nodejs24.x',
Handler: 'handler',
Timeout: 10,
MemorySize: 256,
});
});

it('should configure Lambda environment variables', () => {
template.hasResourceProperties('AWS::Lambda::Function', {
Environment: {
Expand Down Expand Up @@ -138,6 +148,12 @@ describe('LambdaStack', () => {
});
});

it('should create a DELETE method on /tasks/{taskId}', () => {
template.hasResourceProperties('AWS::ApiGateway::Method', {
HttpMethod: 'DELETE',
});
});

it('should integrate API Gateway with Lambda', () => {
template.hasResourceProperties('AWS::ApiGateway::Method', {
Integration: {
Expand Down Expand Up @@ -168,16 +184,14 @@ describe('LambdaStack', () => {
PolicyDocument: {
Statement: Match.arrayWith([
Match.objectLike({
Action: [
Action: Match.arrayWith([
'dynamodb:BatchGetItem',
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:Query',
'dynamodb:GetItem',
'dynamodb:Scan',
'dynamodb:ConditionCheckItem',
'dynamodb:DescribeTable',
],
]),
}),
]),
},
Expand Down Expand Up @@ -250,15 +264,21 @@ describe('LambdaStack', () => {
});
});

it('should export delete task function ARN', () => {
template.hasOutput('DeleteTaskFunctionArn', {
Export: {
Name: 'lambda-starter-delete-task-function-arn-dev',
},
});
});

it('should grant Lambda read-write access to DynamoDB for update function', () => {
template.hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: Match.arrayWith([
Match.objectLike({
Action: Match.arrayWith([
'dynamodb:BatchGetItem',
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:Query',
'dynamodb:GetItem',
'dynamodb:Scan',
Expand Down
46 changes: 46 additions & 0 deletions infrastructure/stacks/lambda-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export class LambdaStack extends cdk.Stack {
*/
public readonly updateTaskFunction: NodejsFunction;

/**
* The delete task Lambda function.
*/
public readonly deleteTaskFunction: NodejsFunction;

constructor(scope: Construct, id: string, props: LambdaStackProps) {
super(scope, id, props);

Expand Down Expand Up @@ -198,6 +203,37 @@ export class LambdaStack extends cdk.Stack {
// Grant the Lambda function read and write access to the DynamoDB table
props.taskTable.grantReadWriteData(this.updateTaskFunction);

// Create the delete task Lambda function
this.deleteTaskFunction = new NodejsFunction(this, 'DeleteTaskFunction', {
functionName: `${props.appName}-delete-task-${props.envName}`,
runtime: lambda.Runtime.NODEJS_24_X,
handler: 'handler',
entry: path.join(__dirname, '../../src/handlers/delete-task.ts'),
environment: {
TASKS_TABLE: props.taskTable.tableName,
ENABLE_LOGGING: props.enableLogging.toString(),
LOG_LEVEL: props.loggingLevel,
LOG_FORMAT: props.loggingFormat,
},
timeout: cdk.Duration.seconds(10),
memorySize: 256,
bundling: {
minify: true,
sourceMap: true,
},
loggingFormat: lambda.LoggingFormat.JSON,
applicationLogLevelV2: lambda.ApplicationLogLevel.INFO,
systemLogLevelV2: lambda.SystemLogLevel.INFO,
logGroup: new logs.LogGroup(this, 'DeleteTaskFunctionLogGroup', {
logGroupName: `/aws/lambda/${props.appName}-delete-task-${props.envName}`,
retention: props.envName === 'prd' ? logs.RetentionDays.ONE_MONTH : logs.RetentionDays.ONE_WEEK,
removalPolicy: props.envName === 'prd' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
}),
});

// Grant the Lambda function read and write access to the DynamoDB table
props.taskTable.grantReadWriteData(this.deleteTaskFunction);

// Create API Gateway REST API
this.api = new apigateway.RestApi(this, 'LambdaStarterApi', {
restApiName: `${props.appName}-api-${props.envName}`,
Expand Down Expand Up @@ -232,6 +268,9 @@ export class LambdaStack extends cdk.Stack {
// Add PUT method to /tasks/{taskId}
taskResource.addMethod('PUT', new apigateway.LambdaIntegration(this.updateTaskFunction));

// Add DELETE method to /tasks/{taskId}
taskResource.addMethod('DELETE', new apigateway.LambdaIntegration(this.deleteTaskFunction));

// Output the API URL
new cdk.CfnOutput(this, 'ApiUrl', {
value: this.api.url,
Expand Down Expand Up @@ -273,5 +312,12 @@ export class LambdaStack extends cdk.Stack {
description: 'ARN of the update task Lambda function',
exportName: `${props.appName}-update-task-function-arn-${props.envName}`,
});

// Output the delete task function ARN
new cdk.CfnOutput(this, 'DeleteTaskFunctionArn', {
value: this.deleteTaskFunction.functionArn,
description: 'ARN of the delete task Lambda function',
exportName: `${props.appName}-delete-task-function-arn-${props.envName}`,
});
}
}
Loading