import { BadRequestException } from '@nestjs/common'; import { InspectionEvidenceKind } from '../database/entities'; export const MAX_INSPECTION_EVIDENCE_BYTES = 15 * 1024 * 1024; export interface UploadedInspectionEvidenceFile { buffer: Buffer; originalname: string; mimetype?: string; size: number; } export interface InspectedInspectionEvidenceFile { originalName: string; mimeType: 'image/jpeg' | 'image/png' | 'image/webp' | 'application/pdf'; extension: '.jpg' | '.png' | '.webp' | '.pdf'; } function invalidFile(message: string): BadRequestException { return new BadRequestException({ code: 'INVALID_INSPECTION_EVIDENCE_FILE', message }); } function detectedType( buffer: Buffer, ): Omit | null { if (buffer.length >= 4 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) { return { mimeType: 'image/jpeg', extension: '.jpg' }; } if ( buffer.length >= 8 && buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) ) { return { mimeType: 'image/png', extension: '.png' }; } if ( buffer.length >= 12 && buffer.subarray(0, 4).toString('ascii') === 'RIFF' && buffer.subarray(8, 12).toString('ascii') === 'WEBP' ) { return { mimeType: 'image/webp', extension: '.webp' }; } if (buffer.length >= 5 && buffer.subarray(0, 5).toString('ascii') === '%PDF-') { return { mimeType: 'application/pdf', extension: '.pdf' }; } return null; } export function inspectInspectionEvidenceFile( file: UploadedInspectionEvidenceFile | undefined, kind: InspectionEvidenceKind, ): InspectedInspectionEvidenceFile { if (!file?.buffer || file.size <= 0 || file.buffer.length <= 0) { throw invalidFile('Debe seleccionar un archivo no vacío'); } if ( file.size > MAX_INSPECTION_EVIDENCE_BYTES || file.buffer.length > MAX_INSPECTION_EVIDENCE_BYTES ) { throw invalidFile('El archivo supera el máximo permitido de 15 MB'); } const detected = detectedType(file.buffer); if (!detected) { throw invalidFile('Sólo se permiten JPG, PNG, WebP y PDF válidos'); } if (kind === InspectionEvidenceKind.PHOTO && !detected.mimeType.startsWith('image/')) { throw invalidFile('Una fotografía debe ser JPG, PNG o WebP'); } if (kind === InspectionEvidenceKind.DOCUMENT && detected.mimeType !== 'application/pdf') { throw invalidFile('Un documento debe ser un archivo PDF'); } const originalName = file.originalname .replace(/[\u0000-\u001f\u007f]/g, '') .trim() .slice(0, 255); if (!originalName) throw invalidFile('El nombre original del archivo no es válido'); return { ...detected, originalName }; }