import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common'; import { IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; import { and, desc, eq, isNull } from 'drizzle-orm'; import { CurrentSession } from '../auth/session.decorator'; import type { AuthenticatedSession } from '../auth/tenant.guard'; import { db } from '../db/client'; import { actionPlans } from '../db/schema'; class CreateActionPlanDto { @IsString() title!: string; @IsOptional() @IsString() description?: string; @IsOptional() @IsIn(['draft','active','completed','cancelled']) status?: string; @IsOptional() @IsIn(['low','medium','high','critical']) priority?: string; @IsOptional() @IsString() owner?: string; @IsOptional() @IsString() dueDate?: string; @IsOptional() @IsUUID() decisionId?: string; @IsOptional() @IsUUID() goalId?: string; @IsOptional() @IsUUID() projectId?: string; } class UpdateActionPlanDto { @IsOptional() @IsString() title?: string; @IsOptional() @IsString() description?: string; @IsOptional() @IsIn(['draft','active','completed','cancelled']) status?: string; @IsOptional() @IsIn(['low','medium','high','critical']) priority?: string; @IsOptional() @IsString() owner?: string; @IsOptional() @IsString() dueDate?: string; } @Controller('action-plans') export class ActionPlansController { @Get() async list( @CurrentSession() session: AuthenticatedSession, @Query('status') status?: string, ) { const tid = session.tenantId; const where = status ? and(eq(actionPlans.tenantId, tid), isNull(actionPlans.deletedAt), eq(actionPlans.status, status)) : and(eq(actionPlans.tenantId, tid), isNull(actionPlans.deletedAt)); return db.query.actionPlans.findMany({ where, orderBy: [desc(actionPlans.createdAt)], limit: 200 }); } @Post() async create(@CurrentSession() session: AuthenticatedSession, @Body() dto: CreateActionPlanDto) { const [row] = await db.insert(actionPlans).values({ tenantId: session.tenantId, title: dto.title, description: dto.description, status: dto.status ?? 'draft', priority: dto.priority ?? 'medium', owner: dto.owner, dueDate: dto.dueDate ?? null, decisionId: dto.decisionId ?? null, goalId: dto.goalId ?? null, projectId: dto.projectId ?? null, }).returning(); return row; } @Patch(':id') async update( @CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateActionPlanDto, ) { const updates: Record = { updatedAt: new Date() }; if (dto.title !== undefined) updates.title = dto.title; if (dto.description !== undefined) updates.description = dto.description; if (dto.status !== undefined) { updates.status = dto.status; if (dto.status === 'completed') updates.completedAt = new Date(); } if (dto.priority !== undefined) updates.priority = dto.priority; if (dto.owner !== undefined) updates.owner = dto.owner; if (dto.dueDate !== undefined) updates.dueDate = dto.dueDate; const [row] = await db.update(actionPlans) .set(updates) .where(and(eq(actionPlans.id, id), eq(actionPlans.tenantId, session.tenantId))) .returning(); return row; } @Delete(':id') async remove(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) { await db.update(actionPlans) .set({ deletedAt: new Date() }) .where(and(eq(actionPlans.id, id), eq(actionPlans.tenantId, session.tenantId))); return { deleted: true }; } }