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 one middleware that *chooses* a parser, not a path-mounted parser stacked in * front of a default one: that arrangement depended on Express's mount-path stripping and on * body-parser skipping an already-parsed request, and silently stopped applying whenever the * middleware order shifted. One explicit branch has no such coupling. */ /** * Express routes case-insensitively and ignores exactly one trailing slash, so * `/API/Voice/Extract/` reaches the same controller and must get the same limit — otherwise * it 413s every real recording, which reads as a broken microphone rather than a route. * Two slashes never route, so they must not buy a 10 MB buffer either. */ function isVoiceExtractPath(path: string): boolean { 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); }