Skip to content

refactor(routes): extract traceroutes, route-segments, neighbor-info, ignored-nodes, announce (Phase 2 Batch 1)#3519

Merged
Yeraze merged 2 commits into
mainfrom
fix/3502-phase2-route-extraction
Jun 17, 2026
Merged

refactor(routes): extract traceroutes, route-segments, neighbor-info, ignored-nodes, announce (Phase 2 Batch 1)#3519
Yeraze merged 2 commits into
mainfrom
fix/3502-phase2-route-extraction

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

Continues the server.ts decomposition started in #3509. Extracts 11 inline apiRouter handlers (~270 lines) from the monolithic server.ts into 5 dedicated route modules, each with a companion test file.

Changes

  • src/server/routes/tracerouteRoutes.tsGET /traceroutes/recent, GET /traceroutes/history/:fromNodeNum/:toNodeNum
  • src/server/routes/routeSegmentRoutes.tsGET /route-segments/longest-active, GET /route-segments/record-holder, DELETE /route-segments/record-holder
  • src/server/routes/neighborInfoRoutes.tsGET /neighbor-info, GET /neighbor-info/:nodeNum (uses getEffectiveDbNodePosition directly, removing the local wrapper)
  • src/server/routes/ignoredNodeRoutes.tsGET /ignored-nodes, DELETE /ignored-nodes/:nodeId
  • src/server/routes/announceRoutes.tsPOST /announce/send, GET /announce/last, GET /announce/preview
  • src/server/server.ts — mounts the 5 new modules via apiRouter.use(...) and removes all corresponding inline handlers

Issues Resolved

Relates to #3502

Documentation Updates

No documentation changes needed.

Testing

  • 36 new unit tests across 5 test files (all passing)
  • Full Vitest suite passes
  • TypeScript compiles cleanly (no new errors introduced)
  • Route behavior is unchanged — refactor only, no logic modifications

🤖 Generated with Claude Code

… ignored-nodes, announce into dedicated modules (Phase 2 Batch 1)

Part of #3502 — continues the server.ts decomposition. Extracts 11
inline apiRouter handlers (~270 lines) into 5 route modules with 36
tests covering happy paths, error paths, and source-scoping.

Co-Authored-By: Claude Sonnet 4.6 <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 — Phase 2 Route Extraction

  • Read route files
  • Read test files
  • Review server.ts changes
  • Provide comprehensive feedback

Summary

This is a well-executed refactoring PR. The decomposition is clean, tests are solid, and the logic faithfully mirrors the original inline handlers. A few observations worth reviewing:


Issues Found

1. Missing requirePermission on GET /traceroutes/recent (Potential Security Regression)

tracerouteRoutes.ts:8 — The original GET /traceroutes/recent in server.ts had no requirePermission guard either, so this is a faithful copy — but it's worth flagging that this endpoint is publicly accessible without authentication unlike the sibling GET /traceroutes/history/:fromNodeNum/:toNodeNum which does require traceroute:read. This may be intentional, but it's worth confirming.

2. return res.status(400) Style Inconsistency in announceRoutes.ts

announceRoutes.ts:41 — This uses the old return res.status(400).json(...) pattern:

return res.status(400).json({ error: 'Missing message parameter' });

All other new route files use the newer two-statement pattern (res.status(400).json(...); return;). Minor, but inconsistent.

3. maxNodeAgeHours Setting Key Mismatch in neighborInfoRoutes.ts

neighborInfoRoutes.ts:14-15:

const maxNodeAgeStr = await databaseService.settings.getSetting('maxNodeAge');

The setting key is 'maxNodeAge' here but in tracerouteRoutes.ts:18 it's 'maxNodeAgeHours':

const maxNodeAgeHours = parseInt(await databaseService.settings.getSetting('maxNodeAgeHours') || '24');

This is preserved from the original server.ts (the neighbor-info handler used 'maxNodeAge' and the traceroute handler used 'maxNodeAgeHours'), so it's not a regression — but the inconsistency is worth noting.

4. N+1 Query Pattern in neighborInfoRoutes.ts

neighborInfoRoutes.ts:20-41 — The Promise.all over neighborInfo.map(...) fires one getNode() call per nodeNum and one per neighborNodeNum, resulting in up to 2 * N sequential-or-parallel DB roundtrips per request. This existed in the original code, so not a regression, but worth a future optimization issue.


Test Coverage

Test coverage is good. A few gaps:

  • tracerouteRoutes.test.ts: No test for GET /recent with an explicit ?sourceId=src1 passed to getAllTraceroutes. The limit path is tested but source-scoping on /recent is not.
  • neighborInfoRoutes.test.ts: No test covering the case where ni.node?.lastHeard is null but the neighbor still passes through the Math.max(reportSec, rxSec) >= cutoffTime branch.
  • routeSegmentRoutes.test.ts: No test for GET /record-holder with explicit sourceId propagation (the DELETE path tests it but the GET paths don't).

These are minor gaps — the core happy and error paths are well covered.


Code Quality

The new modules follow the project's existing conventions closely:

  • Correct use of Router(), requirePermission, databaseService, and logger
  • getEffectiveDbNodePosition used directly (removing the single-use getEffectivePosition wrapper that existed in server.ts) — a nice cleanup
  • resolveRequestSourceId and resolveSourceManager correctly imported from their utility modules
  • Mocks are properly structured with vi.mock hoisting and mockResolvedValue (not mockReturnValue) for async methods

Verdict

Approve with optional nits. The refactoring is faithful, tests pass the key scenarios, and the code follows project conventions. The return res.status(400) inconsistency in announceRoutes.ts is the only item worth fixing before merge.

…ix hoisting error

vi.mock factories are hoisted before top-level variable declarations; wrap
the manager object in vi.hoisted() so it's initialized in time.

Co-Authored-By: Claude Sonnet 4.6 <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 — Phase 2 Route Extraction

  • Read route files
  • Read test files
  • Review server.ts changes
  • Provide comprehensive feedback

Summary

This is a clean, faithful refactor. The 5 new route modules mirror the original server.ts inline handlers accurately and the 36 tests provide solid coverage. A few items are worth addressing before merge:


Issues Found

1. return res.status(400) Style Inconsistency in announceRoutes.tsMinor

announceRoutes.ts:41 uses the old Express 4 return res.status(...).json(...) pattern:

return res.status(400).json({ error: 'Missing message parameter' });

All other new route files use the two-statement form (res.status(400).json(...); return;). Inconsistent style within the same PR.

2. getLatestNeighborInfoPerNodeScoped is synchronous but read without awaitConfirmed OK

neighborInfoRoutes.ts:12:

const neighborInfo = databaseService.getLatestNeighborInfoPerNodeScoped(neighborInfoSourceId);

This is a sync in-memory cache read (_neighborsCache), so no await is correct. However, this is the only synchronous databaseService call in these 5 modules — worth a quick comment to prevent a future "missing await" lint fix from breaking it.

3. GET /traceroutes/recent Has No Auth Guard — Pre-existing, worth confirming

tracerouteRoutes.ts:8 — No requirePermission on this endpoint, unlike the GET /history/:from/:to sibling at line 51. This was faithfully preserved from server.ts, but since this is a refactor that touches the route surface, now is a good time to confirm this is intentional (public read) and not an oversight.

4. maxNodeAge vs maxNodeAgeHours Setting Key Inconsistency — Pre-existing, worth a future ticket

neighborInfoRoutes.ts:14 uses 'maxNodeAge' while tracerouteRoutes.ts:18 uses 'maxNodeAgeHours'. These are preserved from the original server.ts, so not a regression, but the inconsistency is now surfaced across two clearly separated files and easier to spot. A follow-up ticket to normalize these would be beneficial.


Test Coverage Gaps

These are minor — core happy/error paths are well covered:

  • tracerouteRoutes.test.ts: No test for GET /recent with ?sourceId=src1 being passed through to getAllTraceroutes. The limit path is tested but source-scoping on /recent is untested.
  • routeSegmentRoutes.test.ts: No test for GET /record-holder with ?sourceId= propagation (the DELETE path tests it at line 126, but the GET paths don't).
  • neighborInfoRoutes.test.ts: The filter branch where ni.node?.lastHeard is null but the neighbor has recent lastRxTime (the Math.max(reportSec, rxSec) >= cutoffTime fallback, lines 45-47) has no dedicated test case.

Code Quality

The new modules follow project conventions correctly:

  • Proper Router(), requirePermission, databaseService, and logger usage
  • getEffectiveDbNodePosition used directly — nice removal of the server.ts single-use wrapper
  • resolveRequestSourceId and resolveSourceManager correctly imported from utility modules
  • Test mocks use vi.hoisted (in announceRoutes.test.ts) and mockResolvedValue correctly
  • Error response shapes are consistent within each file (ignoredNodeRoutes uses the structured ApiErrorResponse shape; others use plain { error: string })

Verdict

Approve with optional nits. The refactoring is faithful, tests pass the key scenarios, and the code follows project conventions. The return res.status(400) style inconsistency in announceRoutes.ts:41 is the most actionable fix. Everything else is pre-existing or minor.

View job

@Yeraze Yeraze merged commit 1ffc501 into main Jun 17, 2026
20 checks passed
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 Yeraze deleted the fix/3502-phase2-route-extraction branch June 17, 2026 19:16
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