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
60 changes: 59 additions & 1 deletion docs/InfrastructureGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,40 @@ const tableName = process.env.TASK_TABLE_NAME;
- **DynamoDB**: Write access (PutItem) to the Task table
- **CloudWatch Logs**: Write access to its log group

#### Update Task Function

**Resource Type**: AWS Lambda Function

**Configuration**:

- **Function Name**: `{app-name}-update-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}-update-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 (GetItem, UpdateItem) 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 @@ -526,8 +560,31 @@ const tableName = process.env.TASK_TABLE_NAME;

- **POST /tasks**: Create a new task
- Integration: Lambda proxy integration with Create Task Function
- Request Body: JSON object with task properties (title, description, status)
- Request Body: JSON object with task properties
- `title` (required): string, max 100 characters
- `detail` (optional): string, max 1000 characters
- `dueAt` (optional): ISO8601 timestamp
- `isComplete` (optional): boolean, defaults to false
- Response: JSON object with created task including ID and timestamps
- Success Status: 201 Created
- Error Responses:
- 400 Bad Request: Invalid request body or validation error
- 500 Internal Server Error: Failed to create task

- **PUT /tasks/{taskId}**: Update an existing task
- Integration: Lambda proxy integration with Update Task Function
- Path Parameter: `taskId` - The unique identifier of the task
- Request Body: JSON object with task properties
- `title` (required): string, max 100 characters
- `detail` (optional): string, max 1000 characters - omit to remove from task
- `dueAt` (optional): ISO8601 timestamp - omit to remove from task
- `isComplete` (required): boolean
- Response: JSON object with updated task
- Success Status: 200 OK
- Error Responses:
- 400 Bad Request: Invalid request body or validation error
- 404 Not Found: Task ID does not exist
- 500 Internal Server Error: Failed to update task

**Outputs**:

Expand All @@ -536,6 +593,7 @@ const tableName = process.env.TASK_TABLE_NAME;
- `ListTasksFunctionArn`: The List Tasks Lambda function ARN
- `GetTaskFunctionArn`: The Get Task Lambda function ARN
- `CreateTaskFunctionArn`: The Create Task Lambda function ARN
- `UpdateTaskFunctionArn`: The Update Task Lambda function ARN

**Logging Configuration**:

Expand Down
50 changes: 42 additions & 8 deletions infrastructure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,16 +273,47 @@ npm run cdk destroy --all
- Runtime: Node.js 24.x
- Memory: 256 MB
- Timeout: 10 seconds
- Log Format: JSON (structured logging)
- Log Retention:
- `prd`: 30 days
- Other environments: 7 days
- Log Removal Policy:
- `prd`: `RETAIN` (logs preserved on stack deletion)
- Other environments: `DESTROY` (logs deleted on stack deletion)
- Handler: Retrieves all tasks from DynamoDB
- IAM Permissions: Read access to Task table (Scan)

- **Get Task Function** (`get-task-{env}`)
- Runtime: Node.js 24.x
- Memory: 256 MB
- Timeout: 10 seconds
- Handler: Retrieves a single task by ID
- IAM Permissions: Read access to Task table (GetItem)

- **Create Task Function** (`create-task-{env}`)
- Runtime: Node.js 24.x
- Memory: 256 MB
- Timeout: 10 seconds
- Handler: Creates a new task in DynamoDB
- IAM Permissions: Write access to Task table (PutItem)

- **Update Task Function** (`update-task-{env}`)
- Runtime: Node.js 24.x
- Memory: 256 MB
- Timeout: 10 seconds
- Handler: Updates an existing task in DynamoDB
- IAM Permissions: Read-write access to Task table (GetItem, UpdateItem)

**Common Lambda Configuration:**

- Log Format: JSON (structured logging)
- Log Retention:
- `prd`: 30 days
- Other environments: 7 days
- Log Removal Policy:
- `prd`: `RETAIN` (logs preserved on stack deletion)
- Other environments: `DESTROY` (logs deleted on stack deletion)

- **Lambda Starter API** (`lambda-starter-api-{env}`)
- Type: REST API
- Endpoints:
- `GET /tasks` - List all tasks
- `GET /tasks/{taskId}` - Get a specific task
- `POST /tasks` - Create a new task
- `PUT /tasks/{taskId}` - Update an existing task
- CORS: Enabled with preflight OPTIONS support
- Throttling: Rate and burst limits configured
- Stage: `{env}` (e.g., `dev`, `prd`)
Expand All @@ -291,7 +322,10 @@ npm run cdk destroy --all

- `ApiUrl`: The API Gateway endpoint URL
- `ApiId`: The API Gateway ID
- `ListTasksFunctionArn`: The Lambda function ARN
- `ListTasksFunctionArn`: The List Tasks Lambda function ARN
- `GetTaskFunctionArn`: The Get Task Lambda function ARN
- `CreateTaskFunctionArn`: The Create Task Lambda function ARN
- `UpdateTaskFunctionArn`: The Update Task Lambda function ARN

**Logging Configuration:**

Expand Down
49 changes: 49 additions & 0 deletions infrastructure/stacks/lambda-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ describe('LambdaStack', () => {
});
});

it('should create an update task Lambda function', () => {
template.hasResourceProperties('AWS::Lambda::Function', {
FunctionName: 'lambda-starter-update-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 @@ -122,6 +132,12 @@ describe('LambdaStack', () => {
});
});

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

it('should integrate API Gateway with Lambda', () => {
template.hasResourceProperties('AWS::ApiGateway::Method', {
Integration: {
Expand Down Expand Up @@ -225,6 +241,39 @@ describe('LambdaStack', () => {
},
});
});

it('should export update task function ARN', () => {
template.hasOutput('UpdateTaskFunctionArn', {
Export: {
Name: 'lambda-starter-update-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',
'dynamodb:ConditionCheckItem',
'dynamodb:BatchWriteItem',
'dynamodb:PutItem',
'dynamodb:UpdateItem',
'dynamodb:DeleteItem',
'dynamodb:DescribeTable',
]),
}),
]),
},
});
});
});

describe('prd environment', () => {
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 @@ -66,6 +66,11 @@ export class LambdaStack extends cdk.Stack {
*/
public readonly createTaskFunction: NodejsFunction;

/**
* The update task Lambda function.
*/
public readonly updateTaskFunction: NodejsFunction;

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

Expand Down Expand Up @@ -162,6 +167,37 @@ export class LambdaStack extends cdk.Stack {
// Grant the Lambda function write access to the DynamoDB table
props.taskTable.grantWriteData(this.createTaskFunction);

// Create the update task Lambda function
this.updateTaskFunction = new NodejsFunction(this, 'UpdateTaskFunction', {
functionName: `${props.appName}-update-task-${props.envName}`,
runtime: lambda.Runtime.NODEJS_24_X,
handler: 'handler',
entry: path.join(__dirname, '../../src/handlers/update-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, 'UpdateTaskFunctionLogGroup', {
logGroupName: `/aws/lambda/${props.appName}-update-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.updateTaskFunction);

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

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

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

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