← back to Archive Agent
fix: wrap all async DB route handlers in try/catch to prevent process crash on PG error
8c0551149007ec52a2df6de01fc00075f68f8686 · 2026-05-30 21:23:02 -0700 · SteveStudio2
GET /, GET /alerts, GET /summary, and POST /ack were missing error handling — an
unhandled rejected promise from pg-pool caused Node to crash the process instead of
returning a 500. Added try/catch to each handler so DB failures return HTTP 500
without killing the server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Files touched
Diff
commit 8c0551149007ec52a2df6de01fc00075f68f8686
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Sat May 30 21:23:02 2026 -0700
fix: wrap all async DB route handlers in try/catch to prevent process crash on PG error
GET /, GET /alerts, GET /summary, and POST /ack were missing error handling — an
unhandled rejected promise from pg-pool caused Node to crash the process instead of
returning a 500. Added try/catch to each handler so DB failures return HTTP 500
without killing the server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
server.js | 86 +++++++++++++++++++++++++++++++++++++--------------------------
1 file changed, 51 insertions(+), 35 deletions(-)
diff --git a/server.js b/server.js
index 1ad9d6a..baded66 100644
--- a/server.js
+++ b/server.js
@@ -245,39 +245,47 @@ app.post('/scan', async (req, res) => {
});
app.get('/alerts', async (req, res) => {
- const cls = req.query.class || null;
- const vendor = req.query.vendor || null;
- const acknowledged = req.query.ack === '1';
- const limit = Math.min(parseInt(req.query.limit || '500', 10), 5000);
- const params = [];
- const where = [];
- if (cls) { params.push(cls); where.push(`classification = $${params.length}`); }
- if (vendor) { params.push(vendor); where.push(`vendor = $${params.length}`); }
- where.push(`acknowledged_at IS ${acknowledged ? 'NOT NULL' : 'NULL'}`);
- params.push(limit);
- const { rows } = await pool.query(
- `SELECT * FROM archive_alerts WHERE ${where.join(' AND ')} ORDER BY detected_at DESC LIMIT $${params.length}`,
- params
- );
- res.json({ count: rows.length, rows });
+ try {
+ const cls = req.query.class || null;
+ const vendor = req.query.vendor || null;
+ const acknowledged = req.query.ack === '1';
+ const limit = Math.min(parseInt(req.query.limit || '500', 10), 5000);
+ const params = [];
+ const where = [];
+ if (cls) { params.push(cls); where.push(`classification = $${params.length}`); }
+ if (vendor) { params.push(vendor); where.push(`vendor = $${params.length}`); }
+ where.push(`acknowledged_at IS ${acknowledged ? 'NOT NULL' : 'NULL'}`);
+ params.push(limit);
+ const { rows } = await pool.query(
+ `SELECT * FROM archive_alerts WHERE ${where.join(' AND ')} ORDER BY detected_at DESC LIMIT $${params.length}`,
+ params
+ );
+ res.json({ count: rows.length, rows });
+ } catch (e) {
+ res.status(500).json({ error: e.message });
+ }
});
app.get('/summary', async (req, res) => {
- const { rows: byClass } = await pool.query(`
- SELECT classification, COUNT(*)::int AS n,
- COUNT(*) FILTER (WHERE acknowledged_at IS NULL)::int AS unacked
- FROM archive_alerts GROUP BY 1 ORDER BY n DESC`);
- const { rows: byVendor } = await pool.query(`
- SELECT vendor, COUNT(*)::int AS n,
- COUNT(*) FILTER (WHERE classification IN ('WRONG_PRICED','WRONG_SAMPLE','DRIFT'))::int AS wrongful
- FROM archive_alerts WHERE acknowledged_at IS NULL
- GROUP BY 1 ORDER BY wrongful DESC, n DESC LIMIT 30`);
- const { rows: byDay } = await pool.query(`
- SELECT DATE(shopify_updated) AS day, COUNT(*)::int AS n,
- COUNT(*) FILTER (WHERE classification IN ('WRONG_PRICED','WRONG_SAMPLE','DRIFT'))::int AS wrongful
- FROM archive_alerts WHERE shopify_updated > now() - interval '30 days'
- GROUP BY 1 ORDER BY 1 DESC`);
- res.json({ lastScan, scanResult, byClass, byVendor, byDay });
+ try {
+ const { rows: byClass } = await pool.query(`
+ SELECT classification, COUNT(*)::int AS n,
+ COUNT(*) FILTER (WHERE acknowledged_at IS NULL)::int AS unacked
+ FROM archive_alerts GROUP BY 1 ORDER BY n DESC`);
+ const { rows: byVendor } = await pool.query(`
+ SELECT vendor, COUNT(*)::int AS n,
+ COUNT(*) FILTER (WHERE classification IN ('WRONG_PRICED','WRONG_SAMPLE','DRIFT'))::int AS wrongful
+ FROM archive_alerts WHERE acknowledged_at IS NULL
+ GROUP BY 1 ORDER BY wrongful DESC, n DESC LIMIT 30`);
+ const { rows: byDay } = await pool.query(`
+ SELECT DATE(shopify_updated) AS day, COUNT(*)::int AS n,
+ COUNT(*) FILTER (WHERE classification IN ('WRONG_PRICED','WRONG_SAMPLE','DRIFT'))::int AS wrongful
+ FROM archive_alerts WHERE shopify_updated > now() - interval '30 days'
+ GROUP BY 1 ORDER BY 1 DESC`);
+ res.json({ lastScan, scanResult, byClass, byVendor, byDay });
+ } catch (e) {
+ res.status(500).json({ error: e.message });
+ }
});
app.post('/restore', async (req, res) => {
@@ -289,16 +297,21 @@ app.post('/restore', async (req, res) => {
});
app.post('/ack', async (req, res) => {
- const ids = req.body?.ids;
- const by = req.body?.by || 'manual';
- if (!Array.isArray(ids) || !ids.length) return res.status(400).json({ error: 'ids[] required' });
- await pool.query(`UPDATE archive_alerts SET acknowledged_at = now(), acknowledged_by=$1 WHERE shopify_id = ANY($2::text[])`, [by, ids]);
- res.json({ ok: true, acked: ids.length });
+ try {
+ const ids = req.body?.ids;
+ const by = req.body?.by || 'manual';
+ if (!Array.isArray(ids) || !ids.length) return res.status(400).json({ error: 'ids[] required' });
+ await pool.query(`UPDATE archive_alerts SET acknowledged_at = now(), acknowledged_by=$1 WHERE shopify_id = ANY($2::text[])`, [by, ids]);
+ res.json({ ok: true, acked: ids.length });
+ } catch (e) {
+ res.status(500).json({ error: e.message });
+ }
});
app.get('/owner/:vendor', (req, res) => res.json({ vendor: req.params.vendor, owner: ownerFor(req.params.vendor) }));
app.get('/', async (req, res) => {
+ try {
const { rows: byClass } = await pool.query(`
SELECT classification, COUNT(*)::int AS n,
COUNT(*) FILTER (WHERE acknowledged_at IS NULL)::int AS unacked
@@ -364,6 +377,9 @@ ${recent.map(r => `<tr>
<p class="meta">API: <code>GET /summary</code> · <code>GET /alerts?class=WRONG_PRICED</code> · <code>POST /scan {days:N}</code> · <code>POST /restore {ids:[...]}</code> · <code>POST /ack {ids:[...]}</code></p>
</body></html>`);
+ } catch (e) {
+ res.status(500).send(`<pre>DB error: ${e.message}</pre>`);
+ }
});
// ---------- Bootstrap ----------
← 6f849da security: strip hardcoded dw_admin DSN password -> env-first
·
back to Archive Agent
·
auto-save: 2026-06-21T18:53:00 (2 files) — ecosystem.config. 6ab943e →