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,100 @@
import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
Query,
UseGuards,
Request,
} from "@nestjs/common";
import { TasksService } from "./tasks.service";
import { CreateTaskDto, UpdateTaskDto, QueryTasksDto } from "./dto";
import { AuthGuard } from "../auth/guards/auth.guard";
/**
* Controller for task endpoints
* All endpoints require authentication
*/
@Controller("tasks")
@UseGuards(AuthGuard)
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
/**
* POST /api/tasks
* Create a new task
*/
@Post()
async create(@Body() createTaskDto: CreateTaskDto, @Request() req: any) {
const workspaceId = req.user?.workspaceId;
const userId = req.user?.id;
if (!workspaceId || !userId) {
throw new Error("User workspaceId or userId not found");
}
return this.tasksService.create(workspaceId, userId, createTaskDto);
}
/**
* GET /api/tasks
* Get paginated tasks with optional filters
*/
@Get()
async findAll(@Query() query: QueryTasksDto, @Request() req: any) {
const workspaceId = req.user?.workspaceId || query.workspaceId;
return this.tasksService.findAll({ ...query, workspaceId });
}
/**
* GET /api/tasks/:id
* Get a single task by ID
*/
@Get(":id")
async findOne(@Param("id") id: string, @Request() req: any) {
const workspaceId = req.user?.workspaceId;
if (!workspaceId) {
throw new Error("User workspaceId not found");
}
return this.tasksService.findOne(id, workspaceId);
}
/**
* PATCH /api/tasks/:id
* Update a task
*/
@Patch(":id")
async update(
@Param("id") id: string,
@Body() updateTaskDto: UpdateTaskDto,
@Request() req: any
) {
const workspaceId = req.user?.workspaceId;
const userId = req.user?.id;
if (!workspaceId || !userId) {
throw new Error("User workspaceId not found");
}
return this.tasksService.update(id, workspaceId, userId, updateTaskDto);
}
/**
* DELETE /api/tasks/:id
* Delete a task
*/
@Delete(":id")
async remove(@Param("id") id: string, @Request() req: any) {
const workspaceId = req.user?.workspaceId;
const userId = req.user?.id;
if (!workspaceId || !userId) {
throw new Error("User workspaceId not found");
}
return this.tasksService.remove(id, workspaceId, userId);
}
}