import { Body, Controller, Get, Param, ParseUUIDPipe, Post, Put, Req, Res, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import type { Response } from 'express'; import { CurrentAuth } from '../auth/decorators/current-auth.decorator'; import { RequirePermissions } from '../authorization/decorators/require-permissions.decorator'; import type { AuthPrincipal, RequestWithContext } from '../common/http/request-context'; import { CloseInspectionActDto } from './dto/close-inspection-act.dto'; import { PrepareInspectionActDto } from './dto/prepare-inspection-act.dto'; import { CreateCompanyOutcomeDto } from './dto/create-company-outcome.dto'; import { CreateInspectionSignatureDto } from './dto/create-inspection-signature.dto'; import { CreateCompanySignatureDto } from './dto/create-company-signature.dto'; import { UpsertInspectionResponsibleDto } from './dto/upsert-inspection-responsible.dto'; import { InspectionClosingService } from './inspection-closing.service'; import { MAX_INSPECTION_SIGNATURE_BYTES, type UploadedInspectionSignatureFile, } from './inspection-signature-file'; @Controller('inspection-acts/:actId') export class InspectionClosingController { constructor(private readonly closing: InspectionClosingService) {} @Get('closure') @RequirePermissions('inspection_closure.read') get(@Param('actId', new ParseUUIDPipe({ version: '4' })) actId: string) { return this.closing.get(actId); } @Put('responsible') @RequirePermissions('inspection_closure.prepare') responsible( @Param('actId', new ParseUUIDPipe({ version: '4' })) actId: string, @Body() dto: UpsertInspectionResponsibleDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.closing.upsertResponsible(actId, dto, principal, request); } @Post('lock') @RequirePermissions('inspection_closure.prepare') lock( @Param('actId', new ParseUUIDPipe({ version: '4' })) actId: string, @Body() dto: PrepareInspectionActDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.closing.prepare(actId, dto, principal, request); } @Post('signatures/inspector') @RequirePermissions('inspection_closure.sign') @UseInterceptors(FileInterceptor('file', { limits: { fileSize: MAX_INSPECTION_SIGNATURE_BYTES, files: 1 }, })) inspectorSignature( @Param('actId', new ParseUUIDPipe({ version: '4' })) actId: string, @Body() dto: CreateInspectionSignatureDto, @UploadedFile() file: UploadedInspectionSignatureFile | undefined, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.closing.signInspector(actId, dto, file, principal, request); } @Post('signatures/company') @RequirePermissions('inspection_closure.sign') @UseInterceptors(FileInterceptor('file', { limits: { fileSize: MAX_INSPECTION_SIGNATURE_BYTES, files: 1 }, })) companySignature( @Param('actId', new ParseUUIDPipe({ version: '4' })) actId: string, @Body() dto: CreateCompanySignatureDto, @UploadedFile() file: UploadedInspectionSignatureFile | undefined, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.closing.signCompany(actId, dto, file, principal, request); } @Post('company-outcome') @RequirePermissions('inspection_closure.sign') companyOutcome( @Param('actId', new ParseUUIDPipe({ version: '4' })) actId: string, @Body() dto: CreateCompanyOutcomeDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.closing.recordCompanyOutcome(actId, dto, principal, request); } @Post('seal') @RequirePermissions('inspection_closure.close') seal( @Param('actId', new ParseUUIDPipe({ version: '4' })) actId: string, @Body() dto: CloseInspectionActDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.closing.close(actId, dto, principal, request); } } @Controller('inspection-act-signatures') export class InspectionSignatureContentController { constructor(private readonly closing: InspectionClosingService) {} @Get(':signatureId/content') @RequirePermissions('inspection_closure.read') async content( @Param('signatureId', new ParseUUIDPipe({ version: '4' })) signatureId: string, @Res() response: Response, ): Promise { const { filePath, signature } = await this.closing.signatureContent(signatureId); response.setHeader('Content-Type', 'image/png'); response.setHeader('Content-Length', String(signature.sizeBytes)); response.setHeader('Content-Disposition', 'inline; filename="firma.png"'); response.setHeader('Cache-Control', 'private, no-store'); response.setHeader('X-Content-Type-Options', 'nosniff'); response.setHeader('Content-Security-Policy', "sandbox; default-src 'none'"); await new Promise((resolveSend, rejectSend) => { response.sendFile(filePath, (error) => { if (error) rejectSend(error); else resolveSend(); }); }); } }