Skip to content

refactor(server): extract backup + notification route groups (Refs #3502)#3523

Merged
Yeraze merged 1 commit into
mainfrom
fix/3502-phase2-batch2-routes
Jun 17, 2026
Merged

refactor(server): extract backup + notification route groups (Refs #3502)#3523
Yeraze merged 1 commit into
mainfrom
fix/3502-phase2-batch2-routes

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 2 Batch 2 of the server.ts inline-route extraction tracked in #3502 (continuing #3509 Phase 1 and #3519 Phase 2 Batch 1). Moves four cohesive inline route groups out of the server.ts composition root into dedicated routes/ modules, behaviour-preserving — handler bodies moved verbatim, auth/validation middleware (requirePermission, requireAuth, optionalAuth, requireAdmin) unchanged.

Refs #3502 (this is an increment, not a close).

Route groups extracted

Group New module Router Mounted at
/backup/* (5) routes/backupRoutes.ts backupRouter /backup
/system/backup/* (5) routes/backupRoutes.ts systemBackupRouter /system/backup
/push/* (8) routes/notificationRoutes.ts pushRouter /push
/apprise/* (6) routes/notificationRoutes.ts appriseRouter /apprise

24 inline handlers moved total.

Notes on coupling

  • The notification helpers (getUserNotificationPreferencesAsync, saveUserNotificationPreferencesAsync, applyNodeNamePrefixAsync) were already exported from utils/notificationFiltering.js, so no server-local helper needed to move.
  • The push-test / apprise-test handlers keep using the meshtasticManager default-export singleton exactly as before (imported into the route module).
  • Removed now-orphaned server.ts imports: systemBackupService and the three notification helpers.

Inline-handler count in server.ts

  • Before: 154 inline apiRouter.{get,post,put,delete,patch}(...) handlers (~10,476 LOC)
  • After: 130 inline handlers (~9,449 LOC) — net -1,033 lines

What remains

Per #3502's inventory, still inline (future batches): /config (15), /channels (11), /nodes (17), /admin (17), /settings remaining (17), /system (8), /telemetry (7), /connection (5), /device (5), /scripts (5), plus assorted smaller groups.

Tests

  • New: backupRoutes.test.ts and notificationRoutes.test.ts (39 tests covering happy paths, validation/400s, path-traversal guards, audit logging, and Apprise scheme allow-listing).
  • Full Vitest suite green: 7261 tests, 0 failures, 0 failed suites (success: true).
  • tsc --noEmit clean. New route files lint clean (backupRoutes.ts 0 warnings; notificationRoutes.ts only pre-existing no-explicit-any warnings carried over verbatim from the original handlers).

🤖 Generated with Claude Code

)

Phase 2 Batch 2 of the server.ts route extraction (#3502). Moves four
cohesive inline route groups out of the server.ts composition root into
dedicated routes/ modules, behaviour-preserving (handler bodies moved
verbatim, auth/validation middleware unchanged).

Extracted:
- /backup/*        -> backupRoutes.ts (backupRouter)
- /system/backup/* -> backupRoutes.ts (systemBackupRouter)
- /push/*          -> notificationRoutes.ts (pushRouter)
- /apprise/*       -> notificationRoutes.ts (appriseRouter)

Notification helpers (getUserNotificationPreferencesAsync,
saveUserNotificationPreferencesAsync, applyNodeNamePrefixAsync) were
already importable from utils/notificationFiltering.js, so no helper had
to move; the test/Apprise handlers keep using the meshtasticManager
default-export singleton exactly as before. Orphaned server.ts imports
(systemBackupService + the three notification helpers) removed.

server.ts: 154 -> 130 inline apiRouter handlers (-24); ~10,476 -> ~9,449 LOC.

Adds backupRoutes.test.ts and notificationRoutes.test.ts (39 new tests).
Full Vitest suite green (7261 tests, 0 failures); tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: refactor(server): extract backup + notification route groups

Tasks:

  • Gather context and understand the request
  • Review backupRoutes.ts
  • Review notificationRoutes.ts
  • Review backupRoutes.test.ts
  • Review notificationRoutes.test.ts
  • Review server.ts changes
  • Compile and post final review

Summary

This is a clean, well-structured refactor that correctly extracts 24 route handlers out of server.ts into two dedicated modules. Behavior is preserved, the module boundaries are sensible, and the new test files are thorough. A few issues are worth raising below.


Findings

notificationRoutes.ts — Deprecated singleton import

Lines 7, 121, 420 — Both the push /test and apprise /test handlers use the deprecated meshtasticManager default-export singleton to get localNodeInfo:

import meshtasticManager from '../meshtasticManager.js';
// ...
const localNodeInfo = meshtasticManager.getLocalNodeInfo();

The PR notes this is "exactly as before" (carried over verbatim), but since these handlers are already being source-scoped for Apprise (they accept a sourceId), this is a good opportunity to look up the local node info via sourceManagerRegistry.getManager(sourceId) instead of the deprecated singleton — especially for the Apprise test handler at line 420, where sourceId is always present (it's required). The push test handler at line 121 doesn't have a scoped sourceId, so it's harder to fix there, but the Apprise one could be straightforward.

This is a low-urgency finding since it's preexisting behavior, but it's worth flagging given CLAUDE.md's explicit rule: "No global meshtasticManager singleton. Look up per-source instances via sourceManagerRegistry.getManager(sourceId)".


notificationRoutes.ts — VAPID subject format not validated

Lines 38–52 — The /push/vapid-subject PUT handler validates that subject is a non-empty string, but the Web Push spec requires the VAPID subject to be either a mailto: URI or an https: URL:

if (!subject || typeof subject !== 'string') {
  return res.status(400).json({ error: 'Subject is required and must be a string' });
}

An arbitrary string like "hello" will be accepted and passed to the notification service. This matches the pre-refactor behavior but is worth noting as a latent bug / missing validation.


notificationRoutes.ts — Inconsistent error response shape in /apprise/test

Lines 393–408 — The apprise /test handler returns { success: false, message: ... } for 401/400 errors, while all other error handlers in this file return { error: ... }. This inconsistency exists in the original code (carried over verbatim), but it's now more visible:

// apprise/test uses:
return res.status(401).json({ success: false, message: 'Not authenticated' });

// All other handlers use:
return res.status(400).json({ error: 'Some error' });

The test at line 233 (notificationRoutes.test.ts) validates against res.body.message, which will continue to work — but callers consuming these endpoints need to be aware of the inconsistency.


backupRoutes.tsfs.existsSync inside async handler

Line 204 — The system backup download handler uses fs.existsSync (synchronous) inside an async route handler to check whether a backup directory exists:

const fs = await import('fs');
if (!fs.existsSync(backupPath)) {

This blocks the event loop. The fs/promises module (also dynamically imported on line 459 in notificationRoutes.ts) is already available. Using fs.promises.access or fs.promises.stat in an await call would be preferable. This is preexisting behavior but straightforward to fix in the route module.


backupRoutes.ts — Response after archive error may fail

Lines 217–220 — If the archiver fires an error event after headers have already been sent (e.g., after archive.pipe(res) begins streaming), calling res.status(500).json(...) will throw "Cannot set headers after they are sent":

archive.on('error', err => {
  logger.error('❌ Error creating archive:', err);
  res.status(500).json({ error: 'Failed to create archive' });
});

A guard like if (!res.headersSent) would prevent the crash. Again, this is carried over verbatim from the original code, but it's now isolated to a module where it's easy to fix.


Test coverage — Missing GET /system/backup/download happy path

backupRoutes.test.ts covers the invalid-dirname 400 case (line 169–172) and both delete happy/sad paths, but there is no test for the GET /system/backup/download/:dirname success path (the tar.gz streaming path). The archiver interaction is complex (mocking archiver's TarArchive export is nontrivial), so it's understandable that this was omitted — but it's a gap worth noting for completeness.


notificationRoutes.test.ts — Apprise URLs endpoint not tested

GET /apprise/urls and the ENOENT fallback path for that handler are not covered by the test file. These would be straightforward to add.


Minor / Non-blocking

  • notificationRoutes.ts lines 148–197 and 200–361: the handler body indentation is one level deeper than the pushRouter.get/post(...) call — this is a pre-existing style inherited from the original code and doesn't affect behavior.
  • The dynamic await import('archiver') and await import('fs') on every download request (lines 198–201) creates new module resolution overhead per request. Since both are unconditional for the download path, top-level static imports would be more idiomatic; this is a micro-optimization.

Verdict

The refactor is solid and safe to merge. The issues above are all inherited from the original server.ts code — nothing was introduced by this PR — and the new test files catch the most important validation paths. The deprecated singleton usage in the notification test handlers and the sync existsSync are the items most worth addressing, either in a follow-up or as part of the source-scoping work still to come.

@Yeraze Yeraze merged commit eadc32a into main Jun 17, 2026
19 checks passed
@Yeraze Yeraze deleted the fix/3502-phase2-batch2-routes branch June 17, 2026 15:35
Yeraze added a commit that referenced this pull request Jun 17, 2026
… & status route groups (Refs #3502) (#3525)

Continues the server.ts route-extraction effort (#3502) by moving 21
inline apiRouter handlers into five cohesive routes/ modules, matching
the established Router-per-module pattern (PRs #3519, #3523).

Extracted (behaviour-preserving, middleware order identical):
- meshRequestRoutes.ts  — /traceroute, /position/request,
  /nodeinfo/request, /neighborinfo/request, /telemetry/request
  (carries the neighbor-info rate-limit map)
- telemetryRoutes.ts    — /direct-neighbors and the /telemetry/* query
  endpoints (:nodeId, /rates, /smarthops, /linkquality,
  DELETE :nodeId/:type, /available/nodes)
- connectionRoutes.ts   — /connection (GET, /disconnect, /reconnect,
  /info, /configure)
- deviceStatusRoutes.ts — /device/tx-status, /device/security-keys
- statusRoutes.ts       — /virtual-node/status, /automation/airtime-status

Mounted connectionRoutes at /connection; the mixed-prefix routers mount
at / to keep full paths verbatim. server.ts inline handler count drops
from 130 to 109. All handler dependencies were already imported from
utils/ modules, so no server.ts-local helpers needed exporting. Added a
*.test.ts per new module (33 new tests).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Yeraze added a commit that referenced this pull request Jun 17, 2026
…3524) (#3529)

Harden the system-backup download handler (carried verbatim from server.ts in
#3523, now isolated and easy to fix):

- Replace the synchronous fs.existsSync (which blocked the event loop in the
  request path) with an async fsp.access check via a static fs/promises import.
- Guard both the archiver 'error' handler and the catch block with
  res.headersSent — the archive can error / finalize() can throw after
  archive.pipe(res) has begun streaming, which previously threw 'Cannot set
  headers after they are sent'. When already streaming, terminate via res.end().

Tests: add a 404 case (missing backup → async access rejects) and a happy-path
streaming case (real temp dir → real archiver → 200 tar.gz + audit log).

Closes #3524.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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