feat(CC-063): add RisksController (risk register with probability/impact/status)
This commit is contained in:
parent
867125c60a
commit
de947d343f
1 changed files with 68 additions and 0 deletions
68
src/risks/risks.controller.ts
Normal file
68
src/risks/risks.controller.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { Body, Controller, Get, HttpCode, IsOptional, IsString, NotFoundException, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common';
|
||||||
|
import { and, desc, eq } from 'drizzle-orm';
|
||||||
|
import { CurrentSession } from '../auth/session.decorator';
|
||||||
|
import type { AuthenticatedSession } from '../auth/tenant.guard';
|
||||||
|
import { db } from '../db/client';
|
||||||
|
import { risks } from '../db/schema';
|
||||||
|
|
||||||
|
class CreateRiskDto {
|
||||||
|
@IsString() title!: string;
|
||||||
|
@IsOptional() @IsString() description?: string;
|
||||||
|
@IsOptional() @IsString() category?: string;
|
||||||
|
@IsOptional() @IsString() probability?: string;
|
||||||
|
@IsOptional() @IsString() impact?: string;
|
||||||
|
@IsOptional() @IsString() mitigation?: string;
|
||||||
|
@IsOptional() @IsString() owner?: string;
|
||||||
|
@IsOptional() @IsString() decisionId?: string;
|
||||||
|
@IsOptional() @IsString() reviewDueAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateRiskDto {
|
||||||
|
@IsOptional() @IsString() title?: string;
|
||||||
|
@IsOptional() @IsString() description?: string;
|
||||||
|
@IsOptional() @IsString() category?: string;
|
||||||
|
@IsOptional() @IsString() probability?: string;
|
||||||
|
@IsOptional() @IsString() impact?: string;
|
||||||
|
@IsOptional() @IsString() status?: string;
|
||||||
|
@IsOptional() @IsString() mitigation?: string;
|
||||||
|
@IsOptional() @IsString() owner?: string;
|
||||||
|
@IsOptional() @IsString() reviewDueAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('risks')
|
||||||
|
export class RisksController {
|
||||||
|
@Get()
|
||||||
|
async list(@CurrentSession() session: AuthenticatedSession, @Query('status') status?: string) {
|
||||||
|
const where = status
|
||||||
|
? and(eq(risks.tenantId, session.tenantId), eq(risks.status, status))
|
||||||
|
: eq(risks.tenantId, session.tenantId);
|
||||||
|
return db.query.risks.findMany({ where, orderBy: desc(risks.createdAt), limit: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async create(@CurrentSession() session: AuthenticatedSession, @Body() dto: CreateRiskDto) {
|
||||||
|
const [risk] = await db.insert(risks).values({
|
||||||
|
...dto,
|
||||||
|
tenantId: session.tenantId,
|
||||||
|
reviewDueAt: dto.reviewDueAt ? new Date(dto.reviewDueAt) : undefined,
|
||||||
|
}).returning();
|
||||||
|
return risk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
async update(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateRiskDto) {
|
||||||
|
const [updated] = await db.update(risks).set({
|
||||||
|
...dto,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
reviewDueAt: dto.reviewDueAt !== undefined ? (dto.reviewDueAt ? new Date(dto.reviewDueAt) : null) : undefined,
|
||||||
|
}).where(and(eq(risks.id, id), eq(risks.tenantId, session.tenantId))).returning();
|
||||||
|
if (!updated) throw new NotFoundException('Risk not found');
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(204) @Post(':id/close')
|
||||||
|
async close(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) {
|
||||||
|
await db.update(risks).set({ status: 'closed', updatedAt: new Date() })
|
||||||
|
.where(and(eq(risks.id, id), eq(risks.tenantId, session.tenantId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue