import { json, type NextFunction, type Request, type RequestHandler, type Response, } from 'express'; /** The one route that accepts a large body, and how large. */ export const VOICE_EXTRACT_PATH = '/api/voice/extract'; export const VOICE_BODY_LIMIT = '10mb'; /** * JSON body parsing for the whole app. * * Voice recordings are base64 JSON and pass Express's 100 kb default at roughly 20 seconds * of audio, so that one route needs a larger limit while every other endpoint keeps the * default — a large body should not become acceptable everywhere. * * Deliberately a single middleware that *chooses* a parser, rather than a path-mounted * parser stacked in front of a default one. That arrangement relied on Express's * mount-path stripping plus body-parser skipping an already-parsed request, and it * silently stopped applying when the surrounding middleware order shifted — at which point * the endpoint rejected every real recording with a 500. One explicit branch has no such * coupling, and is covered by body-parsers.spec.ts. */ /** * Express routes case-insensitively and ignores a trailing slash unless configured * otherwise, so `/API/Voice/Extract/` reaches the same controller. Matching only the * canonical spelling would hand those requests the 100 kb parser and 413 every real * recording — a failure that looks like a broken microphone, not a routing detail. */ function isVoiceExtractPath(path: string): boolean { // Exactly one trailing slash, because that is exactly what Express ignores. Stripping // every trailing slash would hand the 10 MB parser to `/api/voice/extract//`, which // buffers the body and then 404s — memory spent on a request that never routes. return path.toLowerCase().replace(/\/$/, '') === VOICE_EXTRACT_PATH; } export function createJsonBodyParser(): RequestHandler { const voiceParser = json({ limit: VOICE_BODY_LIMIT }); const defaultParser = json(); return (req: Request, res: Response, next: NextFunction) => isVoiceExtractPath(req.path) ? voiceParser(req, res, next) : defaultParser(req, res, next); }