Skip to content

refactor(server): Phase 1 — extract /health, /cleanup, /maintenance, /themes, /purge into route modules (#3502)#3509

Merged
Yeraze merged 1 commit into
mainfrom
fix/3502-extract-server-routes
Jun 16, 2026
Merged

refactor(server): Phase 1 — extract /health, /cleanup, /maintenance, /themes, /purge into route modules (#3502)#3509
Yeraze merged 1 commit into
mainfrom
fix/3502-extract-server-routes

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 1 of #3502: moves 17 inline apiRouter handlers out of the 11,322-line server.ts monolith into five dedicated src/server/routes/ modules, leaving server.ts responsible only for wiring them up. This is the low-coupling group from the issue's extraction recipe — each handler touches only databaseService, databaseMaintenanceService, or resolveSourceManager with no shared inline helpers, making them safe proof-of-pattern extractions.

Changes

  • healthRoutes.ts — GET /health (both registered handlers, behaviour-preserving)
  • cleanupRoutes.ts — POST /cleanup/{messages,nodes,channels} (3 handlers, all requireAdmin())
  • maintenanceRoutes.ts — GET /maintenance/{status,size}, POST /maintenance/run (3 handlers, requirePermission('configuration', …))
  • themeRoutes.ts — GET/POST/PUT/DELETE /themes and /themes/:slug (5 handlers, mixed optionalAuth / requirePermission('themes', 'write'))
  • purgeRoutes.ts — POST /purge/{nodes,telemetry,messages,traceroutes} (4 handlers, all requireAdmin())
  • server.ts — removed 386 lines of inline handlers; imports and mounts the five new modules

Issues Resolved

Relates to #3502

Documentation Updates

No documentation changes needed — this is a pure internal refactor with no behavior changes.

Testing

  • Unit tests pass (6549 tests, 0 failures — 35 new tests added for the new modules)
  • TypeScript compiles cleanly (pre-existing 57 errors on main are unchanged)
  • All 5 new route modules have dedicated test files covering happy paths, auth (401/403), and edge cases
  • Reviewer: verify /api/health, /api/cleanup/*, /api/maintenance/*, /api/themes*, /api/purge/* endpoints respond identically to pre-PR behaviour

🤖 Generated with Claude Code

…/themes, /purge handlers from server.ts

Moves 17 inline apiRouter handlers (Phase 1 of #3502) into dedicated
route modules, leaving server.ts as a thinner composition root:

- healthRoutes.ts  — GET /health (2 handlers)
- cleanupRoutes.ts — POST /cleanup/{messages,nodes,channels}
- maintenanceRoutes.ts — GET /maintenance/{status,size}, POST /maintenance/run
- themeRoutes.ts   — GET|POST|PUT|DELETE /themes and /themes/:slug
- purgeRoutes.ts   — POST /purge/{nodes,telemetry,messages,traceroutes}

Each module imports only what it needs; all auth/permission middleware is
applied per-route inside the module matching the original handler behaviour.
Test files added for all five modules (35 new tests, all green).

Refs: #3502

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: Phase 1 — Extract Server Routes

Tasks:

  • Gather context
  • Read new route modules
  • Read test files
  • Read modified server.ts
  • Analyze and provide review

Overall this is a clean, well-executed refactor. The extraction is behaviour-preserving, auth middleware is correctly applied, and the new test files follow the project's established patterns. A few issues worth addressing before merge:

Bug: Duplicate GET / in healthRoutes.ts

src/server/routes/healthRoutes.ts:16-34 — two handlers are registered for the same GET / path. In Express, the second handler will never be reached; the first one always responds. If these were two different handlers in the original server.ts, the original may have had distinct paths or used different middleware chains. Verify what the original two /health handlers looked like:

// Both registered to "GET /" — second is dead code
router.get('/', (_req, res) => { ... });          // line 16
router.get('/', optionalAuth(), (_req, res) => { ... }); // line 27 — unreachable

Action required: Either delete the second handler if it's truly redundant, or fix the path (e.g., /info or /env). This is a behaviour change introduced by this PR if the original server.ts mounted these on different sub-paths.


Bug: Fire-and-forget auditLogAsync calls in purgeRoutes.ts

src/server/routes/purgeRoutes.ts:19,44,69,89databaseService.auditLogAsync(...) is called without await. If the audit write fails, the error is silently swallowed. This pattern appears to be carried over from the original server.ts, but it's worth flagging. At minimum, errors should be caught:

// Current (unhandled rejection risk):
databaseService.auditLogAsync(req.user!.id, ...);

// Better:
databaseService.auditLogAsync(req.user!.id, ...).catch((e) =>
  logger.error('Audit log failed:', e)
);

If this was intentionally fire-and-forget in the original (audit failures shouldn't block the response), a comment explaining the intent would help.


Inconsistency: router.use(requireAdmin()) vs per-route middleware

cleanupRoutes.ts:8 and purgeRoutes.ts:9 use router.use(requireAdmin()) to guard all routes on the router, which is correct and consistent. maintenanceRoutes.ts applies requirePermission(...) per-handler instead. Both patterns work, but mixing them makes it easy to miss adding auth when a new route is added. Consider either:

  • Using router.use(requirePermission('configuration', 'read')) before the read-only routes and a separate check for write, or
  • Adding a comment explaining why per-handler middleware is used here (different read vs. write permissions).

Test coverage gaps

healthRoutes.test.ts — only tests the first GET / handler. The second handler (with optionalAuth() returning nodeEnv) has zero test coverage. Given the duplicate-route bug above, a test would also reveal the dead code.

cleanupRoutes.test.ts — missing 403 tests for /cleanup/nodes and /cleanup/channels (only /cleanup/messages has a 403 test). For consistency with the other test files, those two should be covered.

purgeRoutes.test.ts — no test for the sourceId-scoped paths (e.g., purging nodes for a specific source). The handler branches on purgeNodesSourceId to build the response message and passes it to resolveSourceManager, so a test with a sourceId body would improve confidence.


Minor: parseInt without radix on user input

cleanupRoutes.ts:12 and cleanupRoutes.ts:24:

const days = parseInt(req.body.days) || 30;

parseInt without a radix is technically safe for decimal strings, but ESLint's radix rule flags it. Prefer parseInt(req.body.days, 10). Additionally, parseInt on undefined returns NaN, and NaN || 30 correctly falls back — but the intent is clearer with an explicit guard or Number():

const days = Number(req.body.days) || 30;

Minor: Unused isAdmin parameter in createApp test helper

cleanupRoutes.test.ts:34 and themeRoutes.test.ts:46createApp accepts an isAdmin parameter but never uses it. The admin check is driven by the mocked getUserPermissionSetAsync return value, not this parameter. Remove the dead parameter to avoid confusion.


Positive highlights

  • Auth guards are correctly placed and tested (401/403 coverage).
  • themeRoutes.ts has the most thorough validation logic and the best test coverage of the five modules.
  • The server.ts wiring is minimal and clean — imports + apiRouter.use(...) only.
  • Test structure (mock patterns, createApp factory, beforeEach resets) is consistent with the rest of the codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant