// backend/metrics.js (ESM) // Mount with: import registerMetrics from './metrics.js'; registerMetrics(app, { query: sql }); export default function registerMetrics(app, db) { if (!app || !db || typeof db.query !== 'function') { throw new Error('metrics: app and a {query(sql, [params])} db are required'); } let cache = { at: 0, data: null }; const CACHE_MS = 5_000; // front-end polls at 10–60 min; 5s is fine here app.get('/api/metrics', async (_req, res) => { try { const now = Date.now(); if (cache.data && (now - cache.at) < CACHE_MS) { res.setHeader('Cache-Control', 'no-store'); return res.json(cache.data); } // Users = distinct payer addresses who have at least one successful upload (cid not null) const u = await db.query(` SELECT COUNT(DISTINCT lower(address))::bigint AS users FROM public.payments WHERE address IS NOT NULL AND address <> '' AND cid IS NOT NULL `); // Uploads + total bytes = only rows with a CID const p = await db.query(` SELECT COUNT(*)::bigint AS uploads, COALESCE(SUM(size_bytes), 0)::bigint AS bytes FROM public.payments WHERE cid IS NOT NULL `); const payload = { users: Number(u.rows?.[0]?.users ?? 0), uploads: Number(p.rows?.[0]?.uploads?? 0), bytes: Number(p.rows?.[0]?.bytes ?? 0), }; cache = { at: now, data: payload }; res.setHeader('Cache-Control', 'no-store'); res.json(payload); } catch (err) { console.error('metrics route error:', err); res.status(500).json({ ok:false, error:'metrics_failed' }); } }); }