From 5af23d9956abc5af41739c70ee295908eee084a9 Mon Sep 17 00:00:00 2001 From: "Aaron K. Clark" Date: Sun, 17 May 2026 21:14:38 -0500 Subject: [PATCH] fix(router): timeentry/export.csv must come before /:id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #68 added /v1/timeentry/export.csv but placed the route AFTER the existing /v1/timeentry/:id block. Express tries patterns top-down, so a GET to /v1/timeentry/export.csv matched the :id route first, the intIdParam validator parsed "export.csv" → NaN → 400 with "expected number". The test that asserts 403 on missing authKey was flaking on this path. The export handler was never reached. Mirrors the search-before-:id ordering #64 used for customer. Added a comment block flagging the rule for future contributors. All four timeentry CRUD routes still resolve correctly — they sit AFTER the literals now, which is the correct order. Suite: 261 / 261 + 4 integration skipped (post-fix). Co-Authored-By: Claude Opus 4.7 (1M context) --- app/routers/router.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/routers/router.js b/app/routers/router.js index 211087b..def28e3 100644 --- a/app/routers/router.js +++ b/app/routers/router.js @@ -101,6 +101,13 @@ router.post( v.body(timeEntrySchemas.createTimeEntryBody), timeEntry.create, ); +// Literal paths declared BEFORE /:id so express doesn't try to +// parse "export.csv" / "bycompany" as a customer id. +router.get( + '/v1/timeentry/export.csv', + v.query(timeEntrySchemas.exportCsvQuery), + timeEntry.exportCsv, +); router.get( '/v1/timeentry/bycompany/:id', v.params(timeEntrySchemas.intIdParam), @@ -123,11 +130,6 @@ router.delete( v.params(timeEntrySchemas.intIdParam), timeEntry.remove, ); -router.get( - '/v1/timeentry/export.csv', - v.query(timeEntrySchemas.exportCsvQuery), - timeEntry.exportCsv, -); // v1 worker routes. router.post(