73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { BadRequestException } from '@nestjs/common';
|
|
import { AssetMediaKind } from '../database/entities';
|
|
|
|
export const MAX_ASSET_MEDIA_BYTES = 15 * 1024 * 1024;
|
|
|
|
export interface UploadedAssetFile {
|
|
buffer: Buffer;
|
|
originalname: string;
|
|
mimetype?: string;
|
|
size: number;
|
|
}
|
|
|
|
export interface InspectedAssetFile {
|
|
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_ASSET_FILE', message });
|
|
}
|
|
|
|
function detectedType(buffer: Buffer): Omit<InspectedAssetFile, 'originalName'> | 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 inspectAssetFile(
|
|
file: UploadedAssetFile | undefined,
|
|
kind: AssetMediaKind,
|
|
): InspectedAssetFile {
|
|
if (!file?.buffer || file.size <= 0 || file.buffer.length <= 0) {
|
|
throw invalidFile('Debe seleccionar un archivo no vacío');
|
|
}
|
|
if (file.size > MAX_ASSET_MEDIA_BYTES || file.buffer.length > MAX_ASSET_MEDIA_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 === AssetMediaKind.PHOTO && !detected.mimeType.startsWith('image/')) {
|
|
throw invalidFile('Una fotografía debe ser JPG, PNG o WebP');
|
|
}
|
|
if (kind === AssetMediaKind.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 };
|
|
}
|