feat(#5): Implement CRUD APIs for tasks, events, and projects

Implements comprehensive CRUD APIs following TDD principles with 92.44%
test coverage (exceeds 85% requirement).

Features:
- Tasks API: Full CRUD with filtering, pagination, and subtask support
- Events API: Full CRUD with recurrence support and date filtering
- Projects API: Full CRUD with task/event association
- Authentication guards on all endpoints
- Workspace-scoped queries for multi-tenant isolation
- Activity logging for all operations (CREATED, UPDATED, DELETED, etc.)
- DTOs with class-validator validation
- Comprehensive test suite (221 tests, 44 for new APIs)

Implementation:
- Services: Business logic with Prisma ORM integration
- Controllers: RESTful endpoints with AuthGuard
- Modules: Properly registered in AppModule
- Documentation: Complete API reference in docs/4-api/4-crud-endpoints/

Test Coverage:
- Tasks: 96.1%
- Events: 89.83%
- Projects: 84.21%
- Overall: 92.44%

TDD Workflow:
1. RED: Wrote failing tests first
2. GREEN: Implemented minimal code to pass tests
3. REFACTOR: Improved code quality while maintaining coverage

Refs #5

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-01-28 18:43:12 -06:00
parent 05fc1c60f4
commit 132fe6ba98
30 changed files with 3812 additions and 1 deletions

View File

@@ -0,0 +1,536 @@
# CRUD API Endpoints
Complete reference for Tasks, Events, and Projects API endpoints.
## Overview
All CRUD endpoints follow standard REST conventions and require authentication. They support:
- Full CRUD operations (Create, Read, Update, Delete)
- Workspace-scoped isolation
- Pagination and filtering
- Activity logging for audit trails
## Authentication
All endpoints require Bearer token authentication:
```http
Authorization: Bearer {session_token}
```
The workspace context is extracted from the authenticated user's session.
## Tasks API
### Endpoints
```
GET /api/tasks # List tasks
GET /api/tasks/:id # Get single task
POST /api/tasks # Create task
PATCH /api/tasks/:id # Update task
DELETE /api/tasks/:id # Delete task
```
### List Tasks
```http
GET /api/tasks?status=IN_PROGRESS&page=1&limit=20
```
**Query Parameters:**
- `workspaceId` (UUID, required) — Workspace ID
- `status` (enum, optional) — `NOT_STARTED`, `IN_PROGRESS`, `PAUSED`, `COMPLETED`, `ARCHIVED`
- `priority` (enum, optional) — `LOW`, `MEDIUM`, `HIGH`
- `assigneeId` (UUID, optional) — Filter by assigned user
- `projectId` (UUID, optional) — Filter by project
- `parentId` (UUID, optional) — Filter by parent task (for subtasks)
- `dueDateFrom` (ISO 8601, optional) — Filter tasks due after this date
- `dueDateTo` (ISO 8601, optional) — Filter tasks due before this date
- `page` (integer, optional) — Page number (default: 1)
- `limit` (integer, optional) — Items per page (default: 50, max: 100)
**Response:**
```json
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440003",
"workspaceId": "550e8400-e29b-41d4-a716-446655440001",
"title": "Complete API documentation",
"description": "Write comprehensive docs for CRUD endpoints",
"status": "IN_PROGRESS",
"priority": "HIGH",
"dueDate": "2026-02-01T00:00:00.000Z",
"assigneeId": "550e8400-e29b-41d4-a716-446655440002",
"creatorId": "550e8400-e29b-41d4-a716-446655440002",
"projectId": null,
"parentId": null,
"sortOrder": 0,
"metadata": {},
"createdAt": "2026-01-28T18:00:00.000Z",
"updatedAt": "2026-01-28T18:00:00.000Z",
"completedAt": null,
"assignee": {
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "John Doe",
"email": "john@example.com"
},
"creator": {
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "John Doe",
"email": "john@example.com"
},
"project": null
}
],
"meta": {
"total": 42,
"page": 1,
"limit": 20,
"totalPages": 3
}
}
```
### Get Single Task
```http
GET /api/tasks/:id
```
**Response:** Same as task object above, plus `subtasks` array.
### Create Task
```http
POST /api/tasks
Content-Type: application/json
{
"title": "Complete API documentation",
"description": "Write comprehensive docs for CRUD endpoints",
"status": "IN_PROGRESS",
"priority": "HIGH",
"dueDate": "2026-02-01T00:00:00.000Z",
"assigneeId": "550e8400-e29b-41d4-a716-446655440002",
"projectId": null,
"parentId": null,
"sortOrder": 0,
"metadata": {}
}
```
**Fields:**
- `title` (string, required, 1-255 chars) — Task title
- `description` (string, optional, max 10000 chars) — Detailed description
- `status` (enum, optional) — Default: `NOT_STARTED`
- `priority` (enum, optional) — Default: `MEDIUM`
- `dueDate` (ISO 8601, optional) — Target completion date
- `assigneeId` (UUID, optional) — Assigned user
- `projectId` (UUID, optional) — Associated project
- `parentId` (UUID, optional) — Parent task (for subtasks)
- `sortOrder` (integer, optional, min: 0) — Display order
- `metadata` (object, optional) — Custom metadata
**Response (200):** Created task object
**Activity Log:** Automatically logs `CREATED` action for task entity.
### Update Task
```http
PATCH /api/tasks/:id
Content-Type: application/json
{
"status": "COMPLETED"
}
```
All fields are optional for partial updates. Setting `status` to `COMPLETED` automatically sets `completedAt` timestamp.
**Response (200):** Updated task object
**Activity Logs:**
- `UPDATED` — Always logged
- `COMPLETED` — Logged when status changes to `COMPLETED`
- `ASSIGNED` — Logged when `assigneeId` changes
### Delete Task
```http
DELETE /api/tasks/:id
```
**Response (200):** Empty
**Activity Log:** Logs `DELETED` action with task title in details.
**Note:** Deleting a task with subtasks will cascade delete all subtasks.
---
## Events API
### Endpoints
```
GET /api/events # List events
GET /api/events/:id # Get single event
POST /api/events # Create event
PATCH /api/events/:id # Update event
DELETE /api/events/:id # Delete event
```
### List Events
```http
GET /api/events?startFrom=2026-02-01&startTo=2026-02-28
```
**Query Parameters:**
- `workspaceId` (UUID, required) — Workspace ID
- `projectId` (UUID, optional) — Filter by project
- `startFrom` (ISO 8601, optional) — Events starting after this date
- `startTo` (ISO 8601, optional) — Events starting before this date
- `allDay` (boolean, optional) — Filter all-day events
- `page` (integer, optional) — Page number
- `limit` (integer, optional) — Items per page
**Response:**
```json
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440004",
"workspaceId": "550e8400-e29b-41d4-a716-446655440001",
"title": "Team Meeting",
"description": "Weekly sync",
"startTime": "2026-02-01T10:00:00.000Z",
"endTime": "2026-02-01T11:00:00.000Z",
"allDay": false,
"location": "Conference Room A",
"recurrence": null,
"creatorId": "550e8400-e29b-41d4-a716-446655440002",
"projectId": null,
"metadata": {},
"createdAt": "2026-01-28T18:00:00.000Z",
"updatedAt": "2026-01-28T18:00:00.000Z",
"creator": {
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "John Doe",
"email": "john@example.com"
},
"project": null
}
],
"meta": {
"total": 15,
"page": 1,
"limit": 50,
"totalPages": 1
}
}
```
### Create Event
```http
POST /api/events
Content-Type: application/json
{
"title": "Team Meeting",
"description": "Weekly sync",
"startTime": "2026-02-01T10:00:00.000Z",
"endTime": "2026-02-01T11:00:00.000Z",
"allDay": false,
"location": "Conference Room A",
"recurrence": null,
"projectId": null,
"metadata": {}
}
```
**Fields:**
- `title` (string, required, 1-255 chars) — Event title
- `description` (string, optional, max 10000 chars) — Description
- `startTime` (ISO 8601, required) — Event start time
- `endTime` (ISO 8601, optional) — Event end time
- `allDay` (boolean, optional) — Default: false
- `location` (string, optional, max 500 chars) — Location
- `recurrence` (object, optional) — Recurrence rules (RRULE format)
- `projectId` (UUID, optional) — Associated project
- `metadata` (object, optional) — Custom metadata
### Update Event
```http
PATCH /api/events/:id
Content-Type: application/json
{
"location": "Conference Room B"
}
```
All fields optional for partial updates.
### Delete Event
```http
DELETE /api/events/:id
```
---
## Projects API
### Endpoints
```
GET /api/projects # List projects
GET /api/projects/:id # Get single project
POST /api/projects # Create project
PATCH /api/projects/:id # Update project
DELETE /api/projects/:id # Delete project
```
### List Projects
```http
GET /api/projects?status=ACTIVE
```
**Query Parameters:**
- `workspaceId` (UUID, required) — Workspace ID
- `status` (enum, optional) — `PLANNING`, `ACTIVE`, `PAUSED`, `COMPLETED`, `ARCHIVED`
- `startDateFrom` (ISO 8601, optional) — Projects starting after this date
- `startDateTo` (ISO 8601, optional) — Projects starting before this date
- `page` (integer, optional) — Page number
- `limit` (integer, optional) — Items per page
**Response:**
```json
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440005",
"workspaceId": "550e8400-e29b-41d4-a716-446655440001",
"name": "API Development",
"description": "Build CRUD APIs",
"status": "ACTIVE",
"startDate": "2026-01-15",
"endDate": "2026-02-15",
"creatorId": "550e8400-e29b-41d4-a716-446655440002",
"color": "#FF5733",
"metadata": {},
"createdAt": "2026-01-28T18:00:00.000Z",
"updatedAt": "2026-01-28T18:00:00.000Z",
"creator": {
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "John Doe",
"email": "john@example.com"
},
"_count": {
"tasks": 12,
"events": 3
}
}
],
"meta": {
"total": 5,
"page": 1,
"limit": 50,
"totalPages": 1
}
}
```
### Get Single Project
```http
GET /api/projects/:id
```
Returns project with embedded tasks and events arrays.
### Create Project
```http
POST /api/projects
Content-Type: application/json
{
"name": "API Development",
"description": "Build CRUD APIs",
"status": "ACTIVE",
"startDate": "2026-01-15",
"endDate": "2026-02-15",
"color": "#FF5733",
"metadata": {}
}
```
**Fields:**
- `name` (string, required, 1-255 chars) — Project name
- `description` (string, optional, max 10000 chars) — Description
- `status` (enum, optional) — Default: `PLANNING`
- `startDate` (ISO 8601 date, optional) — Project start date
- `endDate` (ISO 8601 date, optional) — Project end date
- `color` (string, optional) — Hex color code (e.g., `#FF5733`)
- `metadata` (object, optional) — Custom metadata
### Update Project
```http
PATCH /api/projects/:id
Content-Type: application/json
{
"status": "COMPLETED"
}
```
All fields optional for partial updates.
### Delete Project
```http
DELETE /api/projects/:id
```
**Note:** Deleting a project sets `projectId` to `null` for all associated tasks and events (does NOT cascade delete).
---
## Error Responses
### 400 Bad Request
Invalid request format or parameters.
```json
{
"statusCode": 400,
"message": "Validation failed",
"error": "Bad Request"
}
```
### 401 Unauthorized
Missing or invalid authentication token.
```json
{
"statusCode": 401,
"message": "No authentication token provided",
"error": "Unauthorized"
}
```
### 404 Not Found
Resource not found or not accessible in workspace.
```json
{
"statusCode": 404,
"message": "Task with ID 550e8400-e29b-41d4-a716-446655440003 not found",
"error": "Not Found"
}
```
### 422 Unprocessable Entity
Validation errors in request body.
```json
{
"statusCode": 422,
"message": [
"title must not be empty",
"priority must be a valid TaskPriority"
],
"error": "Unprocessable Entity"
}
```
## Activity Logging
All CRUD operations automatically create activity log entries for audit trails:
- **Tasks:** `CREATED`, `UPDATED`, `DELETED`, `COMPLETED`, `ASSIGNED`
- **Events:** `CREATED`, `UPDATED`, `DELETED`
- **Projects:** `CREATED`, `UPDATED`, `DELETED`
See [Activity Logging API](../3-activity-logging/README.md) for querying audit trails.
## Examples
### Create Task with Project Association
```bash
curl -X POST http://localhost:3001/api/tasks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-d '{
"title": "Design database schema",
"description": "Create ERD for new features",
"priority": "HIGH",
"projectId": "550e8400-e29b-41d4-a716-446655440005",
"dueDate": "2026-02-05T00:00:00.000Z"
}'
```
### Filter Tasks by Multiple Criteria
```bash
curl "http://localhost:3001/api/tasks?\
workspaceId=550e8400-e29b-41d4-a716-446655440001&\
status=IN_PROGRESS&\
priority=HIGH&\
dueDateFrom=2026-02-01&\
dueDateTo=2026-02-28&\
page=1&\
limit=20" \
-H "Authorization: Bearer ${TOKEN}"
```
### Update Task Status to Completed
```bash
curl -X PATCH http://localhost:3001/api/tasks/550e8400-e29b-41d4-a716-446655440003 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-d '{
"status": "COMPLETED"
}'
```
### Create Recurring Event
```bash
curl -X POST http://localhost:3001/api/events \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-d '{
"title": "Weekly Team Sync",
"startTime": "2026-02-03T10:00:00.000Z",
"endTime": "2026-02-03T11:00:00.000Z",
"recurrence": {
"freq": "WEEKLY",
"interval": 1,
"byweekday": ["MO"]
},
"location": "Zoom"
}'
```
## Next Steps
- [Activity Logging API](../3-activity-logging/README.md)
- [Authentication](../2-authentication/1-endpoints.md)
- [API Conventions](../1-conventions/1-endpoints.md)