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

#### Get Task Function

**Resource Type**: AWS Lambda Function

**Configuration**:

- **Function Name**: `{app-name}-get-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}-get-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 access (GetItem) to the Task table
- **CloudWatch Logs**: Write access to its log group

#### Create Task Function

**Resource Type**: AWS Lambda Function
Expand Down Expand Up @@ -482,6 +516,14 @@ const tableName = process.env.TASK_TABLE_NAME;
- Integration: Lambda proxy integration with List Tasks Function
- Response: JSON array of tasks

- **GET /tasks/{taskId}**: Get a specific task by ID
- Integration: Lambda proxy integration with Get Task Function
- Path Parameter: `taskId` - The unique identifier of the task
- Response: JSON object with the requested task
- Error Responses:
- 404 Not Found: Task ID does not exist or path parameter is missing
- 500 Internal Server Error: Failed to retrieve task

- **POST /tasks**: Create a new task
- Integration: Lambda proxy integration with Create Task Function
- Request Body: JSON object with task properties (title, description, status)
Expand All @@ -492,6 +534,7 @@ const tableName = process.env.TASK_TABLE_NAME;
- `ApiUrl`: The API Gateway endpoint URL (e.g., `https://abc123.execute-api.us-east-1.amazonaws.com/dev/`)
- `ApiId`: The API Gateway ID
- `ListTasksFunctionArn`: The List Tasks Lambda function ARN
- `GetTaskFunctionArn`: The Get Task Lambda function ARN
- `CreateTaskFunctionArn`: The Create Task Lambda function ARN

**Logging Configuration**:
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import eslintConfigPrettier from 'eslint-config-prettier/flat';

export default defineConfig(
{
ignores: ['dist', 'coverage', 'cdk.out'],
ignores: ['**/dist', '**/coverage', '**/cdk.out'],
},
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
Expand Down
34 changes: 29 additions & 5 deletions infrastructure/stacks/lambda-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ describe('LambdaStack', () => {
});
});

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

it('should create a create task Lambda function', () => {
template.hasResourceProperties('AWS::Lambda::Function', {
FunctionName: 'lambda-starter-create-task-dev',
Expand Down Expand Up @@ -88,12 +98,18 @@ describe('LambdaStack', () => {
});

it('should create a /tasks resource', () => {
template.resourceCountIs('AWS::ApiGateway::Resource', 1);
template.resourceCountIs('AWS::ApiGateway::Resource', 2);
template.hasResourceProperties('AWS::ApiGateway::Resource', {
PathPart: 'tasks',
});
});

it('should create a /tasks/{taskId} resource', () => {
template.hasResourceProperties('AWS::ApiGateway::Resource', {
PathPart: '{taskId}',
});
});

it('should create a GET method on /tasks', () => {
template.hasResourceProperties('AWS::ApiGateway::Method', {
HttpMethod: 'GET',
Expand Down Expand Up @@ -173,31 +189,39 @@ describe('LambdaStack', () => {
it('should export API URL', () => {
template.hasOutput('ApiUrl', {
Export: {
Name: 'dev-tasks-api-url',
Name: 'lambda-starter-tasks-api-url-dev',
},
});
});

it('should export API ID', () => {
template.hasOutput('ApiId', {
Export: {
Name: 'dev-tasks-api-id',
Name: 'lambda-starter-tasks-api-id-dev',
},
});
});

it('should export Lambda function ARN', () => {
template.hasOutput('ListTasksFunctionArn', {
Export: {
Name: 'dev-list-tasks-function-arn',
Name: 'lambda-starter-list-tasks-function-arn-dev',
},
});
});

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

it('should export get task function ARN', () => {
template.hasOutput('GetTaskFunctionArn', {
Export: {
Name: 'lambda-starter-get-task-function-arn-dev',
},
});
});
Expand Down
57 changes: 53 additions & 4 deletions infrastructure/stacks/lambda-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ export class LambdaStack extends cdk.Stack {
*/
public readonly listTasksFunction: NodejsFunction;

/**
* The get task Lambda function.
*/
public readonly getTaskFunction: NodejsFunction;

/**
* The create task Lambda function.
*/
Expand Down Expand Up @@ -95,6 +100,37 @@ export class LambdaStack extends cdk.Stack {
// Grant the Lambda function read access to the DynamoDB table
props.taskTable.grantReadData(this.listTasksFunction);

// Create the get task Lambda function
this.getTaskFunction = new NodejsFunction(this, 'GetTaskFunction', {
functionName: `${props.appName}-get-task-${props.envName}`,
runtime: lambda.Runtime.NODEJS_24_X,
handler: 'handler',
entry: path.join(__dirname, '../../src/handlers/get-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, 'GetTaskFunctionLogGroup', {
logGroupName: `/aws/lambda/${props.appName}-get-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 access to the DynamoDB table
props.taskTable.grantReadData(this.getTaskFunction);

// Create the create task Lambda function
this.createTaskFunction = new NodejsFunction(this, 'CreateTaskFunction', {
functionName: `${props.appName}-create-task-${props.envName}`,
Expand Down Expand Up @@ -151,32 +187,45 @@ export class LambdaStack extends cdk.Stack {
// Add POST method to /tasks
tasksResource.addMethod('POST', new apigateway.LambdaIntegration(this.createTaskFunction));

// Create /tasks/{taskId} resource
const taskResource = tasksResource.addResource('{taskId}');

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

// Output the API URL
new cdk.CfnOutput(this, 'ApiUrl', {
value: this.api.url,
description: 'URL of the Tasks API',
exportName: `${props.envName}-tasks-api-url`,
exportName: `${props.appName}-tasks-api-url-${props.envName}`,
});

// Output the API Gateway ID
new cdk.CfnOutput(this, 'ApiId', {
value: this.api.restApiId,
description: 'ID of the Tasks API',
exportName: `${props.envName}-tasks-api-id`,
exportName: `${props.appName}-tasks-api-id-${props.envName}`,
});

// Output the list tasks function ARN
new cdk.CfnOutput(this, 'ListTasksFunctionArn', {
value: this.listTasksFunction.functionArn,
description: 'ARN of the list tasks Lambda function',
exportName: `${props.envName}-list-tasks-function-arn`,
exportName: `${props.appName}-list-tasks-function-arn-${props.envName}`,
});

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

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