diff --git a/src/decisions/decisions.controller.ts b/src/decisions/decisions.controller.ts index a86e9f6..112c4f0 100644 --- a/src/decisions/decisions.controller.ts +++ b/src/decisions/decisions.controller.ts @@ -1,34 +1,92 @@ -import { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post } from '@nestjs/common'; +import { Body, Controller, Get, Param, Patch, Post, Query } from '@nestjs/common'; +import { IsArray, IsOptional, IsString } from 'class-validator'; +import { and, desc, eq, isNull, isNotNull, lte } from 'drizzle-orm'; import { CurrentSession } from '../auth/session.decorator'; import type { AuthenticatedSession } from '../auth/tenant.guard'; -import { CreateDecisionDto, UpdateDecisionDto } from './dto'; -import { DecisionsService } from './decisions.service'; +import { db } from '../db/client'; +import { decisions } from '../db/schema'; + +class CreateDecisionDto { + @IsString() context: string; + @IsOptional() @IsArray() options?: string[]; + @IsOptional() @IsArray() assumptions?: string[]; + @IsOptional() @IsString() workspaceId?: string; + @IsOptional() @IsString() reviewDueAt?: string; +} + +class UpdateDecisionDto { + @IsOptional() @IsString() selectedOption?: string; + @IsOptional() @IsString() outcomeReview?: string; + @IsOptional() @IsArray() evidence?: unknown[]; +} @Controller('decisions') export class DecisionsController { - constructor(private readonly decisionsService: DecisionsService) {} - @Get() - list(@CurrentSession() session: AuthenticatedSession) { - return this.decisionsService.list(session); + async list( + @CurrentSession() session: AuthenticatedSession, + @Query('limit') limitStr?: string, + ) { + const limit = Math.min(Math.max(1, parseInt(limitStr ?? '50', 10) || 50), 200); + return db.query.decisions.findMany({ + where: eq(decisions.tenantId, session.tenantId), + orderBy: [desc(decisions.createdAt)], + limit, + }); } - @Get(':id') - getById(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) { - return this.decisionsService.getById(session, id); + @Get('pending-review') + async pendingReview(@CurrentSession() session: AuthenticatedSession) { + const now = new Date(); + return db.query.decisions.findMany({ + where: and( + eq(decisions.tenantId, session.tenantId), + isNotNull(decisions.reviewDueAt), + lte(decisions.reviewDueAt, now), + isNull(decisions.outcomeReview), + ), + orderBy: [desc(decisions.reviewDueAt)], + limit: 20, + }); } @Post() - create(@CurrentSession() session: AuthenticatedSession, @Body() dto: CreateDecisionDto) { - return this.decisionsService.create(session, dto); + async create( + @CurrentSession() session: AuthenticatedSession, + @Body() dto: CreateDecisionDto, + ) { + const [created] = await db + .insert(decisions) + .values({ + tenantId: session.tenantId, + workspaceId: dto.workspaceId ?? null, + ownerUserId: session.userId, + context: dto.context, + options: dto.options ?? [], + assumptions: dto.assumptions ?? [], + evidence: [], + reviewDueAt: dto.reviewDueAt ? new Date(dto.reviewDueAt) : null, + }) + .returning(); + return created; } @Patch(':id') - update( + async update( @CurrentSession() session: AuthenticatedSession, - @Param('id', ParseUUIDPipe) id: string, + @Param('id') id: string, @Body() dto: UpdateDecisionDto, ) { - return this.decisionsService.update(session, id, dto); + const [updated] = await db + .update(decisions) + .set({ + ...(dto.selectedOption !== undefined ? { selectedOption: dto.selectedOption } : {}), + ...(dto.outcomeReview !== undefined ? { outcomeReview: dto.outcomeReview } : {}), + ...(dto.evidence !== undefined ? { evidence: dto.evidence } : {}), + updatedAt: new Date(), + }) + .where(and(eq(decisions.id, id), eq(decisions.tenantId, session.tenantId))) + .returning(); + return updated; } }