From c5d256ea780ae389c868aa36e86e6fb418cb8fed Mon Sep 17 00:00:00 2001 From: admin-valentin Date: Sat, 1 Aug 2026 21:13:32 +0000 Subject: [PATCH] feat(CC-061): add PATCH /ai-requests/:id/status and status filter on list endpoint --- src/ai-requests/ai-requests.controller.ts | 39 +++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/ai-requests/ai-requests.controller.ts b/src/ai-requests/ai-requests.controller.ts index 9f5b83d..62c10b6 100644 --- a/src/ai-requests/ai-requests.controller.ts +++ b/src/ai-requests/ai-requests.controller.ts @@ -1,20 +1,30 @@ -import { Controller, Get, NotFoundException, Param, ParseUUIDPipe, Query } from '@nestjs/common'; +import { Body, Controller, Get, IsString, NotFoundException, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common'; import { and, desc, eq, sum, count } from 'drizzle-orm'; +import { IsIn, IsOptional } from 'class-validator'; import { CurrentSession } from '../auth/session.decorator'; import type { AuthenticatedSession } from '../auth/tenant.guard'; import { db } from '../db/client'; import { aiRequests } from '../db/schema'; +class UpdateAiRequestStatusDto { + @IsIn(['completed', 'cancelled']) resultStatus!: 'completed' | 'cancelled'; + @IsOptional() @IsString() rejectionReason?: string; +} + @Controller('ai-requests') export class AiRequestsController { @Get() async list( @CurrentSession() session: AuthenticatedSession, @Query('limit') limitStr?: string, + @Query('status') status?: string, ) { const limit = Math.min(Math.max(1, parseInt(limitStr ?? '200', 10) || 200), 500); + const where = status + ? and(eq(aiRequests.tenantId, session.tenantId), eq(aiRequests.resultStatus, status)) + : eq(aiRequests.tenantId, session.tenantId); return db.query.aiRequests.findMany({ - where: eq(aiRequests.tenantId, session.tenantId), + where, orderBy: desc(aiRequests.createdAt), limit, columns: { @@ -86,4 +96,29 @@ export class AiRequestsController { } return record; } + + @Patch(':id/status') + async updateStatus( + @CurrentSession() session: AuthenticatedSession, + @Param('id', ParseUUIDPipe) id: string, + @Body() dto: UpdateAiRequestStatusDto, + ) { + const record = await db.query.aiRequests.findFirst({ + where: and( + eq(aiRequests.id, id), + eq(aiRequests.tenantId, session.tenantId), + ), + columns: { id: true, resultStatus: true }, + }); + if (!record) throw new NotFoundException('AI request not found'); + if (record.resultStatus !== 'pending') { + return { updated: false, reason: 'request is not pending' }; + } + const [updated] = await db + .update(aiRequests) + .set({ resultStatus: dto.resultStatus, completedAt: new Date() }) + .where(eq(aiRequests.id, id)) + .returning({ id: aiRequests.id, resultStatus: aiRequests.resultStatus, completedAt: aiRequests.completedAt }); + return { updated: true, record: updated }; + } }