const localDb = require("../../local-db-client"); const { getRpkiAspaLastFetch, rpkiAspaMap } = require("../services/rpki"); const { pdbSourceCache } = require("../caches/pdb-source-cache"); const { roaStore } = require("../caches/roa-store"); const { ripeStatCache } = require("../services/ripe-stat"); const { responseCache } = require("../caches/response-caches"); const { aspaAdoptionDailyHistory } = require("../aspa-adoption/tracker"); const BGPROUTES_API_KEY = process.env.BGPROUTES_API_KEY || ""; function aspaAdoptionDelta() { return aspaAdoptionDailyHistory.length >= 2 ? aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 1].aspa_objects - aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 2].aspa_objects : 0; } // Health endpoint — extended with cache status, ASPA metrics, and local DB stats function handleHealth(req, res) { const mem = process.memoryUsage(); const rpkiAspaLastFetch = getRpkiAspaLastFetch(); const aspaAge = rpkiAspaLastFetch ? Math.floor((Date.now() - rpkiAspaLastFetch) / 60000) : -1; // Bug fix 2026-07-17: previously only declared inside the .then() callback // below, so the .catch() branch's reference to it threw ReferenceError // (never caught -- the response just hung until client timeout). const roaAge = roaStore.lastBuild ? Math.floor((Date.now() - roaStore.lastBuild) / 60000) : -1; const pdbTotal = pdbSourceCache.hits + pdbSourceCache.misses; // Query local DB stats (async, but return partial if needed) localDb.getLocalDbStats().then(function(dbStats) { // Determine health status based on local DB data availability const hasLocalBgp = dbStats && dbStats.bgp_routes > 100000; // should have >2M rows normally const hasLocalRpki = dbStats && dbStats.rpki_roas > 100000; // should have >500k rows normally const status = (hasLocalBgp && hasLocalRpki && aspaAge < 300) ? "ok" : "degraded"; const healthResponse = { status, service: "PeerCortex", version: "0.6.9", timestamp: new Date().toISOString(), uptime_seconds: Math.floor(process.uptime()), memory_mb: Math.round(mem.heapUsed / 1024 / 1024), bgproutes_configured: !!BGPROUTES_API_KEY, caches: { aspa_map: { entries: rpkiAspaMap.size, age_minutes: aspaAge }, pdb_net: { entries: pdbSourceCache.net.size, hit_rate_pct: pdbTotal > 0 ? Math.round(pdbSourceCache.hits / pdbTotal * 100) : 0 }, pdb_netixlan: { entries: pdbSourceCache.netixlan.size }, pdb_netfac: { entries: pdbSourceCache.netfac.size }, ripe_stat: { entries: ripeStatCache.size }, response_cache: { entries: responseCache.size }, }, local_db: dbStats ? { bgp_routes: dbStats.bgp_routes, rpki_roas: dbStats.rpki_roas, threat_intel: dbStats.threat_intel, rdap_cache_entries: dbStats.rdap_cache_entries, source: "PostgreSQL (local)", healthy: hasLocalBgp && hasLocalRpki, } : null, aspa_adoption: { total_objects: rpkiAspaMap.size, roa_count: roaStore.count, history_samples: aspaAdoptionDailyHistory.length, delta_last: aspaAdoptionDelta(), }, }; return res.end(JSON.stringify(healthResponse, null, 2)); }).catch(function(e) { console.error('[/api/health] Local DB stats error:', e.message); // Return health without local DB stats on error. Local DB stats failed // to fetch, so "ok" can't be confirmed -- report degraded, matching the // honest-degradation pattern used throughout this codebase. const status = "degraded"; return res.end( JSON.stringify({ status, service: "PeerCortex", version: "0.6.9", timestamp: new Date().toISOString(), uptime_seconds: Math.floor(process.uptime()), memory_mb: Math.round(mem.heapUsed / 1024 / 1024), bgproutes_configured: !!BGPROUTES_API_KEY, caches: { roa_store: { entries: roaStore.count, age_minutes: roaAge, ready: roaStore.ready }, aspa_map: { entries: rpkiAspaMap.size, age_minutes: aspaAge }, pdb_net: { entries: pdbSourceCache.net.size, hit_rate_pct: pdbTotal > 0 ? Math.round(pdbSourceCache.hits / pdbTotal * 100) : 0 }, pdb_netixlan: { entries: pdbSourceCache.netixlan.size }, pdb_netfac: { entries: pdbSourceCache.netfac.size }, ripe_stat: { entries: ripeStatCache.size }, response_cache: { entries: responseCache.size }, }, local_db: { error: "Could not fetch local DB stats", message: e.message }, aspa_adoption: { total_objects: rpkiAspaMap.size, roa_count: roaStore.count, history_samples: aspaAdoptionDailyHistory.length, delta_last: aspaAdoptionDelta(), }, }, null, 2) ); }); return; } module.exports = { handleHealth };