import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Patch, Post, Put, Query, Req, Res, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import type { Response } from 'express'; import { AuditService } from '../../audit/audit.service'; import { RequirePermissions } from '../../authorization/decorators/require-permissions.decorator'; import { CurrentAuth } from '../../auth/decorators/current-auth.decorator'; import type { AuthPrincipal, RequestWithContext, } from '../../common/http/request-context'; import { AuditAction } from '../../database/entities'; import { SmtpDeliveryService } from '../../inspection-reports/smtp-delivery.service'; import { MAX_INSPECTION_SIGNATURE_BYTES, type UploadedInspectionSignatureFile, } from '../../inspection-closing/inspection-signature-file'; import { administrationAuditContext } from '../common/administration-audit'; import { ChangeUserStatusDto } from './dto/change-user-status.dto'; import { CreateUserDto } from './dto/create-user.dto'; import { ListUsersQueryDto } from './dto/list-users-query.dto'; import { ReplaceUserRolesDto } from './dto/replace-user-roles.dto'; import { ResetUserPasswordDto } from './dto/reset-user-password.dto'; import { UpdateSelfProfileDto } from './dto/update-self-profile.dto'; import { UpdateUserSmtpSettingsDto } from './dto/update-user-smtp-settings.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor( private readonly users: UsersService, private readonly smtp: SmtpDeliveryService, private readonly audit: AuditService, ) {} @Get() @RequirePermissions('users.read') list(@Query() query: ListUsersQueryDto) { return this.users.list(query); } @Get('self/profile') selfProfile(@CurrentAuth() principal: AuthPrincipal) { return this.users.getSelfProfile(principal.userId); } @Patch('self/profile') updateSelfProfile( @Body() dto: UpdateSelfProfileDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.users.updateSelfProfile(dto, principal, request); } @Get('self/signature') selfSignature(@CurrentAuth() principal: AuthPrincipal) { return this.users.getSelfSignature(principal.userId); } @Get('self/signature/content') async selfSignatureContent( @CurrentAuth() principal: AuthPrincipal, @Res() response: Response, ): Promise { const signature = await this.users.getSelfSignatureContent(principal.userId); response.setHeader('Content-Type', 'image/png'); response.setHeader('Content-Length', String(signature.buffer.length)); response.setHeader('Cache-Control', 'private, no-store'); response.setHeader('ETag', `"${signature.sha256}"`); response.setHeader('X-Content-Type-Options', 'nosniff'); response.send(signature.buffer); } @Put('self/signature') @UseInterceptors(FileInterceptor('file', { limits: { fileSize: MAX_INSPECTION_SIGNATURE_BYTES, files: 1 }, })) updateSelfSignature( @UploadedFile() file: UploadedInspectionSignatureFile | undefined, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.users.updateSelfSignature(file, principal, request); } @Delete('self/signature') deleteSelfSignature( @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.users.deleteSelfSignature(principal, request); } @Get('self/smtp') selfSmtp(@CurrentAuth() principal: AuthPrincipal) { return this.smtp.publicUserSettings(principal.userId); } @Put('self/smtp') async updateSelfSmtp( @Body() dto: UpdateUserSmtpSettingsDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { const before = await this.smtp.publicUserSettings(principal.userId); const after = await this.smtp.saveUserSettings(principal.userId, dto); await this.audit.record({ ...administrationAuditContext(principal, request), action: AuditAction.USER_SMTP_SETTINGS_UPDATED, entityType: 'user_smtp_settings', entityId: principal.userId, beforeData: before as Record, afterData: after as Record, metadata: { passwordNeverReturned: true, scope: 'SELF' }, }); return after; } @Post('self/smtp/test') async testSelfSmtp( @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { const profile = await this.users.getSelfProfile(principal.userId); if (!profile.email) throw new Error('El usuario no tiene email configurado'); const sent = await this.smtp.send({ to: profile.email, subject: 'DH Inspección · Prueba de correo personal', text: 'Este correo confirma que tu configuración de correo en DH Inspección funciona correctamente.', attachment: { filename: 'dh-inspeccion-prueba-correo.txt', mimeType: 'text/plain', content: Buffer.from('DH Inspección · Correo personal OK\n', 'utf8'), }, }, principal.userId); await this.audit.record({ ...administrationAuditContext(principal, request), action: AuditAction.USER_SMTP_TEST_SENT, entityType: 'user_smtp_settings', entityId: principal.userId, afterData: { recipient: profile.email, messageId: sent.messageId }, metadata: { scope: 'SELF' }, }); return { ok: true, recipient: profile.email, messageId: sent.messageId }; } @Post() @RequirePermissions('users.create') create( @Body() dto: CreateUserDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.users.create(dto, principal, request); } @Get(':id') @RequirePermissions('users.read') getById(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) { return this.users.getById(id); } @Patch(':id') @RequirePermissions('users.update') update( @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, @Body() dto: UpdateUserDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.users.update(id, dto, principal, request); } @Patch(':id/status') @RequirePermissions('users.change_status') changeStatus( @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, @Body() dto: ChangeUserStatusDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.users.changeStatus(id, dto, principal, request); } @Post(':id/reset-password') @RequirePermissions('users.update') resetPassword( @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, @Body() dto: ResetUserPasswordDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.users.resetPassword(id, dto, principal, request); } @Put(':id/roles') @RequirePermissions('users.assign_roles') replaceRoles( @Param('id', new ParseUUIDPipe({ version: '4' })) id: string, @Body() dto: ReplaceUserRolesDto, @CurrentAuth() principal: AuthPrincipal, @Req() request: RequestWithContext, ) { return this.users.replaceRoles(id, dto, principal, request); } }