Files
dyolink/backend/src/common/body-parsers.ts

46 lines
1.8 KiB
TypeScript
Raw Normal View History

fix(backend): restore the large-body limit on the voice route POST /voice/extract returned 500 for any real recording. The threshold was exactly 100 kb — Express's body-parser default — which is about 20 seconds of audio, so the endpoint was unusable at its own 2-minute cap. The scoped parser was registered as a path-mounted json() stacked in front of a default one, which relied on two implicit behaviours: Express stripping the mount path, and body-parser skipping a request another parser had already handled. That coupling broke when the surrounding middleware order shifted, and it broke silently — the parser was still registered, just no longer the one that ran. Bisected by dumping the Express layer stack and confirming the raw error was `entity.too.large` with `limit: 102400`. Replaced with a single middleware that picks a parser by path. No mount-path stripping, no dependence on parser ordering. Extracted to common/body-parsers.ts so it is covered by a unit test rather than only reachable through main.ts, which createTestingModule never executes. The test is mutation-checked: forcing the default parser fails 2 of its 5 cases. It also pins that the larger limit does not leak app-wide, and that a merely similar path (/api/voice/extract/extra) does not get it. Verified against the compiled server: 300 kb now reaches /api/voice/extract, /api/auth/login still rejects it, and ordinary requests are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 22:12:16 +03:30
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 {
return path.toLowerCase().replace(/\/+$/, '') === VOICE_EXTRACT_PATH;
}
fix(backend): restore the large-body limit on the voice route POST /voice/extract returned 500 for any real recording. The threshold was exactly 100 kb — Express's body-parser default — which is about 20 seconds of audio, so the endpoint was unusable at its own 2-minute cap. The scoped parser was registered as a path-mounted json() stacked in front of a default one, which relied on two implicit behaviours: Express stripping the mount path, and body-parser skipping a request another parser had already handled. That coupling broke when the surrounding middleware order shifted, and it broke silently — the parser was still registered, just no longer the one that ran. Bisected by dumping the Express layer stack and confirming the raw error was `entity.too.large` with `limit: 102400`. Replaced with a single middleware that picks a parser by path. No mount-path stripping, no dependence on parser ordering. Extracted to common/body-parsers.ts so it is covered by a unit test rather than only reachable through main.ts, which createTestingModule never executes. The test is mutation-checked: forcing the default parser fails 2 of its 5 cases. It also pins that the larger limit does not leak app-wide, and that a merely similar path (/api/voice/extract/extra) does not get it. Verified against the compiled server: 300 kb now reaches /api/voice/extract, /api/auth/login still rejects it, and ordinary requests are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 22:12:16 +03:30
export function createJsonBodyParser(): RequestHandler {
const voiceParser = json({ limit: VOICE_BODY_LIMIT });
const defaultParser = json();
return (req: Request, res: Response, next: NextFunction) =>
isVoiceExtractPath(req.path)
fix(backend): restore the large-body limit on the voice route POST /voice/extract returned 500 for any real recording. The threshold was exactly 100 kb — Express's body-parser default — which is about 20 seconds of audio, so the endpoint was unusable at its own 2-minute cap. The scoped parser was registered as a path-mounted json() stacked in front of a default one, which relied on two implicit behaviours: Express stripping the mount path, and body-parser skipping a request another parser had already handled. That coupling broke when the surrounding middleware order shifted, and it broke silently — the parser was still registered, just no longer the one that ran. Bisected by dumping the Express layer stack and confirming the raw error was `entity.too.large` with `limit: 102400`. Replaced with a single middleware that picks a parser by path. No mount-path stripping, no dependence on parser ordering. Extracted to common/body-parsers.ts so it is covered by a unit test rather than only reachable through main.ts, which createTestingModule never executes. The test is mutation-checked: forcing the default parser fails 2 of its 5 cases. It also pins that the larger limit does not leak app-wide, and that a merely similar path (/api/voice/extract/extra) does not get it. Verified against the compiled server: 300 kb now reaches /api/voice/extract, /api/auth/login still rejects it, and ordinary requests are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 22:12:16 +03:30
? voiceParser(req, res, next)
: defaultParser(req, res, next);
}