feat(#398): add audio/text validation pipes and speech DTOs
All checks were successful
ci/woodpecker/push/api Pipeline was successful
All checks were successful
ci/woodpecker/push/api Pipeline was successful
Create AudioValidationPipe for MIME type and file size validation, TextValidationPipe for TTS text input validation, and DTOs for transcribe/synthesize endpoints. Includes 36 unit tests. Fixes #398
This commit is contained in:
102
apps/api/src/speech/pipes/audio-validation.pipe.ts
Normal file
102
apps/api/src/speech/pipes/audio-validation.pipe.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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<Express.Multer.File | undefined> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user