Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0ea417a
readme
mwarman May 31, 2025
3426e3c
instructions
mwarman May 31, 2025
4fc007d
initial reqs
mwarman May 31, 2025
e751bd3
create a task
mwarman Jun 2, 2025
5a1ff4e
initial list tasks requirements
mwarman Jun 2, 2025
0093b41
requirements
mwarman Jun 2, 2025
1e4e3cf
requirements
mwarman Jun 2, 2025
087347f
list tasks
mwarman Jun 2, 2025
aefd90a
get task by ID
mwarman Jun 3, 2025
6888c4c
handler logging
mwarman Jun 3, 2025
0e92a7e
implement update task functionality with validation and logging
mwarman Jun 3, 2025
50ad78e
refactor updateTask to handle removal of detail and dueAt fields when…
mwarman Jun 3, 2025
ac54b37
implement delete task functionality with Lambda handler, service meth…
mwarman Jun 3, 2025
3fd9908
add CI workflow requirements for GitHub Actions to automate testing a…
mwarman Jun 3, 2025
5810c0b
add CI workflow for AWS Lambda REST API with linting, testing, and de…
mwarman Jun 3, 2025
514d38a
add support for manual triggering of CI workflow
mwarman Jun 3, 2025
08ef844
mark several packages as development dependencies in package-lock.json
mwarman Jun 3, 2025
6e23b89
add CORS header integration requirements for Task Service API
mwarman Jun 6, 2025
354cedf
add CORS support to API responses and configuration
mwarman Jun 6, 2025
6a63540
update project description in Copilot instructions for Task Service
mwarman Jun 7, 2025
26cf40f
fix: correct typos and improve clarity in task creation requirements
mwarman Jun 7, 2025
103d00e
feat: implement OPTIONS method for CORS support in Tasks API
mwarman Jun 9, 2025
7070ca2
feat: add OPTIONS method for CORS preflight support in tasks API
mwarman Jun 9, 2025
f74ad41
fix: update due date format in task schemas to ISO-8601 specification
mwarman Jun 10, 2025
7dd6e51
fix: update due date format in createTask test to ISO-8601 specification
mwarman Jun 10, 2025
7140d53
chore: update dependencies to latest versions
mwarman Jun 10, 2025
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
116 changes: 91 additions & 25 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ You are a **Senior TypeScript developer** working on an AWS Lambda REST API proj

---

## Project Overview

- **Component:** Task Service **task-service**
- **Description:** This service provides a REST API for managing tasks, including creating, retrieving, updating, and deleting tasks. It uses AWS Lambda functions triggered by API Gateway events, with business logic encapsulated in service modules. The project follows best practices for TypeScript development, AWS CDK infrastructure management, and unit testing with Vitest.

---

## Language & Stack

- **Language:** TypeScript
Expand Down Expand Up @@ -52,13 +59,13 @@ You are a **Senior TypeScript developer** working on an AWS Lambda REST API proj
```
/src
/handlers
getUser.ts # Lambda handler
getUser.test.ts # Unit test for getUser
getTask.ts # Lambda handler
getTask.test.ts # Unit test for getTask
/services
userService.ts # Business logic
userService.test.ts # Unit test for userService
taskService.ts # Business logic
taskService.test.ts # Unit test for taskService
/models
User.ts
Task.ts
/utils
response.ts # Helper for formatting Lambda responses
response.test.ts
Expand All @@ -82,16 +89,16 @@ vitest.config.ts # Vitest config
- Example: Basic Lambda + API Gateway route:

```ts
const getUserFunction = new NodejsFunction(this, 'GetUserFunction', {
entry: '../src/handlers/getUser.ts',
handler: 'getUser',
const getTaskFunction = new NodejsFunction(this, 'GetTaskFunction', {
entry: '../src/handlers/getTask.ts',
handler: 'getTask',
environment: {
USERS_TABLE: usersTable.tableName,
TASKS_TABLE: tasksTable.tableName,
},
});

const api = new RestApi(this, 'UsersApi');
api.root.addResource('users').addResource('{userId}').addMethod('GET', new LambdaIntegration(getUserFunction));
const api = new RestApi(this, 'TasksApi');
api.root.addResource('tasks').addResource('{taskId}').addMethod('GET', new LambdaIntegration(getTaskFunction));
```

---
Expand All @@ -105,20 +112,79 @@ api.root.addResource('users').addResource('{userId}').addMethod('GET', new Lambd
### Handler Format Example

```ts
export const getUser = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const userId = event.pathParameters?.userId;
if (!userId) return badRequest('Missing userId');
// Zod schema for request validation
const requestSchema = z.object({
pathParameters: z.object({
taskId: z.string().min(1, 'taskId path variable is required'),
}),
});
type Request = z.infer<typeof requestSchema>;

// Lambda handler function
export const getTask = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
// Validate input
const result = requestSchema.safeParse<APIGatewayProxyEvent, Request>(event);
if (!result.success) return badRequest('Invalid request');

// Extract validated data
const request: Request = result.data;
const { taskId } = request.pathParameters;

try {
const user = await userService.getUserById(userId);
return user ? ok(user) : notFound('User not found');
// Call service to get task
const task = await TaskService.getTaskById(taskId);
return task ? ok(task) : notFound('Task not found');
} catch (err) {
console.error('Failed to get user:', err);
console.error('Failed to get task:', err);
return internalServerError('Unexpected error');
}
};
```

### Service Format Example

```ts
import { PutCommand } from '@aws-sdk/lib-dynamodb';
import { v4 as uuidv4 } from 'uuid';
import { Task, CreateTaskRequest } from '@/models/Task.js';
import { dynamoDocClient } from '@/utils/awsClients.js';
import { logger } from '@/utils/logger.js';
import { config } from '@/utils/config.js';

// Service function to create a new task
const createTask = async (createTaskRequest: CreateTaskRequest): Promise<Task> => {
// Generate a new ID
const taskId = uuidv4();

// Create the complete task object
const task: Task = {
id: taskId,
title: createTaskRequest.title,
detail: createTaskRequest.detail,
isComplete: createTaskRequest.isComplete ?? false,
dueAt: createTaskRequest.dueAt,
};

// Log the task creation
logger.info(`Creating task with ID: ${taskId}`, { task });

// Save to DynamoDB
await dynamoDocClient.send(
new PutCommand({
TableName: config.TASKS_TABLE,
Item: task,
}),
);

return task;
};

// Define and export the TaskService with the methods to handle task operations
export const TaskService = {
createTask,
};
```

---

## Co-located Testing Guidelines
Expand All @@ -136,18 +202,18 @@ export const getUser = async (event: APIGatewayProxyEvent): Promise<APIGatewayPr
- Mock external calls (e.g., AWS SDK, databases).
- Prefer unit tests over integration tests in this repo.

### Example Test File (`getUser.test.ts`)
### Example Test File (`getTask.test.ts`)

```ts
import { getUser } from './getUser';
import { getTask } from './getTask';
import { APIGatewayProxyEvent } from 'aws-lambda';

it('returns 400 if userId is missing', async () => {
it('returns 400 if taskId is missing', async () => {
// Arrange
const event = { pathParameters: {} } as unknown as APIGatewayProxyEvent;

// Act
const response = await getUser(event);
const response = await getTask(event);

// Assert
expect(response.statusCode).toBe(400);
Expand All @@ -169,12 +235,12 @@ it('returns 400 if userId is missing', async () => {

```ts
/**
* Lambda Handler: getUser
* Lambda Handler: getTask
*
* Copilot Instructions:
* - Parse userId from event.pathParameters
* - Call userService.getUserById(userId)
* - Return 200 with user object or 404 if not found
* - Parse taskId from event.pathParameters
* - Call taskService.getTaskById(taskId)
* - Return 200 with task object or 404 if not found
* - Validate input; return 400 if invalid
* - Catch unexpected errors; log and return 500
*/
Expand Down
158 changes: 158 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# AWS Lambda REST API CI Workflow
# This workflow runs the following checks:
# - Code formatting and linting
# - Unit tests with coverage
# - Infrastructure validation
# - Dependency checks
name: Continuous Integration

on:
workflow_dispatch:
push:
branches:
- main
- 'releases/*'
- 'hotfix/*'
- 'feature/*'
pull_request:
branches:
- main

jobs:
lint-format:
name: Lint and Format Check
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: Check Format with Prettier
run: npx prettier --check .

- name: Lint with ESLint
run: npm run lint

test:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: Run Unit Tests with Coverage
run: npm run test:coverage

- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: coverage/

cdk-validation:
name: Infrastructure Validation
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: CDK Synthesis
run: npx cdk synth

dependency-check:
name: Dependency Checks
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: Audit Dependencies
run: npm audit --production
continue-on-error: true

- name: Check Outdated Dependencies
run: npm outdated
continue-on-error: true

- name: Generate Dependency Report
run: |
echo "## Dependency Audit Summary" > dependency-report.md
echo "Report generated on $(date)" >> dependency-report.md
echo "### Security Audit" >> dependency-report.md
npm audit --json | jq 'if has("vulnerabilities") then .vulnerabilities else {} end' | jq 'length' | xargs -I {} echo "* Found {} vulnerabilities" >> dependency-report.md
echo "### Outdated Packages" >> dependency-report.md
npm outdated --json | jq 'length' | xargs -I {} echo "* Found {} outdated packages" >> dependency-report.md
continue-on-error: true

- name: Upload Dependency Report
uses: actions/upload-artifact@v4
with:
name: dependency-report
path: dependency-report.md
continue-on-error: true

workflow-summary:
name: Workflow Summary
needs: [lint-format, test, cdk-validation, dependency-check]
runs-on: ubuntu-latest
if: always()
steps:
- name: Create Summary
run: |
echo "## CI Workflow Summary" >> $GITHUB_STEP_SUMMARY
echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY
echo "| --- | ------ |" >> $GITHUB_STEP_SUMMARY
echo "| Lint and Format | ${{ needs.lint-format.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Unit Tests | ${{ needs.test.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Infrastructure | ${{ needs.cdk-validation.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Dependencies | ${{ needs.dependency-check.result == 'success' && '✅ Passed' || '⚠️ Warnings' }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

if [[ "${{ needs.lint-format.result }}" == "success" && "${{ needs.test.result }}" == "success" && "${{ needs.cdk-validation.result }}" == "success" ]]; then
echo "Overall Status: ✅ All Required Checks Passed" >> $GITHUB_STEP_SUMMARY
else
echo "Overall Status: ❌ Some Checks Failed" >> $GITHUB_STEP_SUMMARY
fi

- name: Fail Workflow if Required Jobs Failed
if: >-
needs.lint-format.result != 'success' ||
needs.test.result != 'success' ||
needs.cdk-validation.result != 'success'
run: exit 1
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ build
dist

# AWS CDK
cdk.output
cdk.out
.cdk.staging
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.16.0
Loading