/** * AudioValidationPipe * * NestJS PipeTransform that validates uploaded audio files. * Checks MIME type against an allow-list and file size against a configurable maximum. * * Usage: * ```typescript * @Post('transcribe') * @UseInterceptors(FileInterceptor('file')) * async transcribe( * @UploadedFile(new AudioValidationPipe()) file: Express.Multer.File, * ) { ... } * ``` * * Issue #398 */ import { BadRequestException } from "@nestjs/common"; import type { PipeTransform } from "@nestjs/common"; /** * Default accepted MIME types for audio uploads. */ const DEFAULT_ALLOWED_MIME_TYPES: readonly string[] = [ "audio/wav", "audio/mp3", "audio/mpeg", "audio/webm", "audio/ogg", "audio/flac", "audio/x-m4a", ] as const; /** * Default maximum upload size in bytes (25 MB). */ const DEFAULT_MAX_FILE_SIZE = 25_000_000; /** * Options for customizing AudioValidationPipe behavior. */ export interface AudioValidationPipeOptions { /** Maximum file size in bytes. Defaults to 25 MB. */ maxFileSize?: number; /** List of accepted MIME types. Defaults to common audio formats. */ allowedMimeTypes?: string[]; } /** * Format bytes into a human-readable string (e.g., "25.0 MB"). */ function formatBytes(bytes: number): string { if (bytes < 1024) { return `${String(bytes)} B`; } if (bytes < 1024 * 1024) { return `${(bytes / 1024).toFixed(1)} KB`; } return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } export class AudioValidationPipe implements PipeTransform { private readonly maxFileSize: number; private readonly allowedMimeTypes: readonly string[]; constructor(options?: AudioValidationPipeOptions) { this.maxFileSize = options?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE; this.allowedMimeTypes = options?.allowedMimeTypes ?? DEFAULT_ALLOWED_MIME_TYPES; } /** * Validate the uploaded file's MIME type and size. * * @param file - The uploaded file from Multer * @returns The validated file, unchanged * @throws {BadRequestException} If the file is missing, has an unsupported MIME type, or exceeds the size limit */ transform(file: Express.Multer.File | undefined): Express.Multer.File { if (!file) { throw new BadRequestException("No audio file provided"); } // Validate MIME type if (!this.allowedMimeTypes.includes(file.mimetype)) { throw new BadRequestException( `Unsupported audio format: ${file.mimetype}. ` + `Supported formats: ${this.allowedMimeTypes.join(", ")}` ); } // Validate file size if (file.size > this.maxFileSize) { throw new BadRequestException( `File size ${formatBytes(file.size)} exceeds maximum allowed size of ${formatBytes(this.maxFileSize)}` ); } return file; } }