Surfaced in the #3523 (#3502 batch 2) review. Both are pre-existing behaviors carried verbatim from server.ts into the new src/server/routes/backupRoutes.ts module — now isolated and easy to fix.
-
Sync fs.existsSync in an async handler — the GET /system/backup/download/:dirname handler does const fs = await import('fs'); if (!fs.existsSync(backupPath)), which blocks the event loop. Switch to fs.promises.access/stat (the fs/promises import is already used elsewhere in the module).
-
res.status(500).json() after headers sent — the archiver error handler can fire after archive.pipe(res) has started streaming, throwing "Cannot set headers after they are sent." Guard with if (!res.headersSent).
Optional while here: a happy-path test for GET /system/backup/download/:dirname (tar.gz streaming) and for GET /apprise/urls (+ its ENOENT fallback), and promoting the per-request await import('archiver'|'fs') to static top-level imports.
Low priority / robustness only — no current functional regression.
Surfaced in the #3523 (#3502 batch 2) review. Both are pre-existing behaviors carried verbatim from
server.tsinto the newsrc/server/routes/backupRoutes.tsmodule — now isolated and easy to fix.Sync
fs.existsSyncin an async handler — theGET /system/backup/download/:dirnamehandler doesconst fs = await import('fs'); if (!fs.existsSync(backupPath)), which blocks the event loop. Switch tofs.promises.access/stat(thefs/promisesimport is already used elsewhere in the module).res.status(500).json()after headers sent — the archivererrorhandler can fire afterarchive.pipe(res)has started streaming, throwing "Cannot set headers after they are sent." Guard withif (!res.headersSent).Optional while here: a happy-path test for
GET /system/backup/download/:dirname(tar.gz streaming) and forGET /apprise/urls(+ its ENOENT fallback), and promoting the per-requestawait import('archiver'|'fs')to static top-level imports.Low priority / robustness only — no current functional regression.