refactor: extract lia.js and health.js routes, fix /api/health closure bug
Bug fix (agreed pre-existing bug #3 of 3): the .catch() branch of localDb.getLocalDbStats() referenced status/roaAge, both only declared inside the sibling .then() callback's closure -- out of scope in .catch(), so any real DB-stats failure threw ReferenceError with no further catch, hanging the response until client timeout instead of degrading gracefully. Fixed: roaAge computed once before the promise chain (same pattern as the existing aspaAge); the .catch() branch gets its own locally-scoped `status = "degraded"` (a DB-stats fetch failure can't confirm "ok", matching the honest-degradation pattern already used throughout this codebase). Also deduplicated the identical delta_last computation that was repeated verbatim in both branches into one aspaAdoptionDelta() helper. Verified via node --check, eslint no-undef (2 known bugs remain: compare, webhooks), and smoke-test harness (28/28 match). server.js: 3556 -> 3346 lines.
This commit is contained in:
parent
3399392f1f
commit
b82c61e03c
177
server.js
177
server.js
@ -220,6 +220,8 @@ function proxyToApiServer(req, res, targetUrl) {
|
|||||||
const staticRoutes = require("./server/routes/static");
|
const staticRoutes = require("./server/routes/static");
|
||||||
const searchRoutes = require("./server/routes/search");
|
const searchRoutes = require("./server/routes/search");
|
||||||
const feedbackRoutes = require("./server/routes/feedback");
|
const feedbackRoutes = require("./server/routes/feedback");
|
||||||
|
const liaRoutes = require("./server/routes/lia");
|
||||||
|
const healthRoute = require("./server/routes/health");
|
||||||
|
|
||||||
const server = http.createServer(async (req, res) => {
|
const server = http.createServer(async (req, res) => {
|
||||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||||
@ -282,197 +284,34 @@ const server = http.createServer(async (req, res) => {
|
|||||||
return staticRoutes.handleFavicon(req, res);
|
return staticRoutes.handleFavicon(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lia'''s Atlas Paradise - Easter egg page
|
// Lia's Atlas Paradise - Easter egg page
|
||||||
if (reqPath === "/lia" || reqPath === "/lia/") {
|
if (reqPath === "/lia" || reqPath === "/lia/") {
|
||||||
return staticRoutes.handleLia(req, res);
|
return staticRoutes.handleLia(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Lia's Atlas Paradise: Atlas probe coverage endpoint
|
// Lia's Atlas Paradise: Atlas probe coverage endpoint
|
||||||
// ============================================================
|
|
||||||
if (reqPath === "/api/atlas/coverage") {
|
if (reqPath === "/api/atlas/coverage") {
|
||||||
res.setHeader("Content-Type", "application/json");
|
return liaRoutes.handleAtlasCoverage(req, res);
|
||||||
if (!atlasState.cache) {
|
|
||||||
res.writeHead(503);
|
|
||||||
return res.end(JSON.stringify({ error: "Atlas probe data is still loading. Please try again in a minute." }));
|
|
||||||
}
|
|
||||||
return res.end(JSON.stringify(atlasState.cache, null, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Lia's Paradise: File parsing endpoint (for binary uploads)
|
// Lia's Paradise: File parsing endpoint (for binary uploads)
|
||||||
// ============================================================
|
|
||||||
if (reqPath === "/api/lia/parse-file" && req.method === "POST") {
|
if (reqPath === "/api/lia/parse-file" && req.method === "POST") {
|
||||||
res.setHeader("Content-Type", "application/json");
|
return liaRoutes.handleParseFile(req, res);
|
||||||
let body = "";
|
|
||||||
req.on("data", function(chunk) { body += chunk; });
|
|
||||||
req.on("end", function() {
|
|
||||||
try {
|
|
||||||
var parsed = JSON.parse(body);
|
|
||||||
var filename = parsed.filename || "";
|
|
||||||
var ext = filename.split(".").pop().toLowerCase();
|
|
||||||
// For text-based formats, decode base64 and extract text
|
|
||||||
if (ext === "csv" || ext === "txt") {
|
|
||||||
var text = Buffer.from(parsed.data, "base64").toString("utf8");
|
|
||||||
return res.end(JSON.stringify({ text: text }));
|
|
||||||
}
|
|
||||||
// For binary formats (PDF, XLS, DOC), we can't parse server-side without
|
|
||||||
// heavy dependencies. Return helpful error.
|
|
||||||
return res.end(JSON.stringify({
|
|
||||||
error: "Binary file parsing (" + ext.toUpperCase() + ") requires client-side extraction. Please use CSV or TXT format, or copy-paste the content.",
|
|
||||||
suggestion: "Export your spreadsheet as CSV first, then upload the CSV file."
|
|
||||||
}));
|
|
||||||
} catch(e) {
|
|
||||||
return res.end(JSON.stringify({ error: "Parse error: " + e.message }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Lia's Paradise: Combined PeeringDB + Atlas coverage data
|
// Lia's Paradise: Combined PeeringDB + Atlas coverage data
|
||||||
// ============================================================
|
|
||||||
if (reqPath === "/api/lia/coverage") {
|
if (reqPath === "/api/lia/coverage") {
|
||||||
res.setHeader("Content-Type", "application/json");
|
return liaRoutes.handleLiaCoverage(req, res);
|
||||||
if (!atlasState.cache) {
|
|
||||||
res.writeHead(503);
|
|
||||||
return res.end(JSON.stringify({ error: "Atlas probe data is still loading. Please try again in a minute." }));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache this expensive response for 30 min
|
|
||||||
var liaCacheKey = "lia_coverage";
|
|
||||||
var liaCached = cacheGet(liaCacheKey);
|
|
||||||
if (liaCached) return res.end(liaCached);
|
|
||||||
|
|
||||||
// Fetch PeeringDB network list (all networks with status "ok")
|
|
||||||
// Use pre-cached org→country map (loaded at startup, 16MB response cached in memory)
|
|
||||||
fetchPeeringDB("/net?status=ok&depth=0").then(function(pdbData) {
|
|
||||||
if (!pdbData || !pdbData.data) {
|
|
||||||
return res.end(JSON.stringify({ error: "Could not fetch PeeringDB networks" }));
|
|
||||||
}
|
|
||||||
|
|
||||||
var probeAsns = new Set(atlasState.cache.asns_with_probes || []);
|
|
||||||
|
|
||||||
var enriched = pdbData.data.map(function(n) {
|
|
||||||
var org = pdbOrgState.map.get(n.org_id) || {};
|
|
||||||
var cc = org.country || "";
|
|
||||||
return {
|
|
||||||
asn: n.asn,
|
|
||||||
name: n.name || "",
|
|
||||||
org_name: org.name || "",
|
|
||||||
country: cc,
|
|
||||||
country_name: cc,
|
|
||||||
info_type: n.info_type || "",
|
|
||||||
has_probe: probeAsns.has(n.asn),
|
|
||||||
};
|
|
||||||
}).filter(function(n) { return n.asn > 0 && n.country; });
|
|
||||||
|
|
||||||
var result = JSON.stringify({
|
|
||||||
networks: enriched,
|
|
||||||
total: enriched.length,
|
|
||||||
with_probes: enriched.filter(function(n) { return n.has_probe; }).length,
|
|
||||||
without_probes: enriched.filter(function(n) { return !n.has_probe; }).length,
|
|
||||||
atlas_unique_asns: probeAsns.size,
|
|
||||||
org_countries_loaded: pdbOrgState.map.size,
|
|
||||||
fetched_at: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
cacheSet(liaCacheKey, result, 30 * 60 * 1000);
|
|
||||||
res.end(result);
|
|
||||||
}).catch(function(e) {
|
|
||||||
res.end(JSON.stringify({ error: "PeeringDB fetch failed: " + e.message }));
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader("Content-Type", "application/json");
|
res.setHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
// Health endpoint — extended with cache status, ASPA metrics, and local DB stats
|
// Health endpoint — extended with cache status, ASPA metrics, and local DB stats
|
||||||
if (reqPath === "/api/health") {
|
if (reqPath === "/api/health") {
|
||||||
const mem = process.memoryUsage();
|
return healthRoute.handleHealth(req, res);
|
||||||
const rpkiAspaLastFetch = getRpkiAspaLastFetch();
|
|
||||||
const aspaAge = rpkiAspaLastFetch ? Math.floor((Date.now() - rpkiAspaLastFetch) / 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: aspaAdoptionDailyHistory.length >= 2
|
|
||||||
? aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 1].aspa_objects - aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 2].aspa_objects
|
|
||||||
: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
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
|
|
||||||
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: aspaAdoptionDailyHistory.length >= 2
|
|
||||||
? aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 1].aspa_objects - aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 2].aspa_objects
|
|
||||||
: 0,
|
|
||||||
},
|
|
||||||
}, null, 2)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ASPA Deep Verification endpoint: /api/aspa/verify?asn=X
|
// ASPA Deep Verification endpoint: /api/aspa/verify?asn=X
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
104
server/routes/health.js
Normal file
104
server/routes/health.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
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 };
|
||||||
98
server/routes/lia.js
Normal file
98
server/routes/lia.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
const { atlasState } = require("../atlas-probes");
|
||||||
|
const { pdbOrgState } = require("../pdb-org-countries");
|
||||||
|
const { fetchPeeringDB } = require("../services/peeringdb");
|
||||||
|
const { cacheGet, cacheSet } = require("../caches/response-caches");
|
||||||
|
|
||||||
|
// Lia's Atlas Paradise: Atlas probe coverage endpoint
|
||||||
|
function handleAtlasCoverage(req, res) {
|
||||||
|
res.setHeader("Content-Type", "application/json");
|
||||||
|
if (!atlasState.cache) {
|
||||||
|
res.writeHead(503);
|
||||||
|
return res.end(JSON.stringify({ error: "Atlas probe data is still loading. Please try again in a minute." }));
|
||||||
|
}
|
||||||
|
return res.end(JSON.stringify(atlasState.cache, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lia's Paradise: File parsing endpoint (for binary uploads)
|
||||||
|
function handleParseFile(req, res) {
|
||||||
|
res.setHeader("Content-Type", "application/json");
|
||||||
|
let body = "";
|
||||||
|
req.on("data", function(chunk) { body += chunk; });
|
||||||
|
req.on("end", function() {
|
||||||
|
try {
|
||||||
|
var parsed = JSON.parse(body);
|
||||||
|
var filename = parsed.filename || "";
|
||||||
|
var ext = filename.split(".").pop().toLowerCase();
|
||||||
|
// For text-based formats, decode base64 and extract text
|
||||||
|
if (ext === "csv" || ext === "txt") {
|
||||||
|
var text = Buffer.from(parsed.data, "base64").toString("utf8");
|
||||||
|
return res.end(JSON.stringify({ text: text }));
|
||||||
|
}
|
||||||
|
// For binary formats (PDF, XLS, DOC), we can't parse server-side without
|
||||||
|
// heavy dependencies. Return helpful error.
|
||||||
|
return res.end(JSON.stringify({
|
||||||
|
error: "Binary file parsing (" + ext.toUpperCase() + ") requires client-side extraction. Please use CSV or TXT format, or copy-paste the content.",
|
||||||
|
suggestion: "Export your spreadsheet as CSV first, then upload the CSV file."
|
||||||
|
}));
|
||||||
|
} catch(e) {
|
||||||
|
return res.end(JSON.stringify({ error: "Parse error: " + e.message }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lia's Paradise: Combined PeeringDB + Atlas coverage data
|
||||||
|
function handleLiaCoverage(req, res) {
|
||||||
|
res.setHeader("Content-Type", "application/json");
|
||||||
|
if (!atlasState.cache) {
|
||||||
|
res.writeHead(503);
|
||||||
|
return res.end(JSON.stringify({ error: "Atlas probe data is still loading. Please try again in a minute." }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache this expensive response for 30 min
|
||||||
|
var liaCacheKey = "lia_coverage";
|
||||||
|
var liaCached = cacheGet(liaCacheKey);
|
||||||
|
if (liaCached) return res.end(liaCached);
|
||||||
|
|
||||||
|
// Fetch PeeringDB network list (all networks with status "ok")
|
||||||
|
// Use pre-cached org→country map (loaded at startup, 16MB response cached in memory)
|
||||||
|
fetchPeeringDB("/net?status=ok&depth=0").then(function(pdbData) {
|
||||||
|
if (!pdbData || !pdbData.data) {
|
||||||
|
return res.end(JSON.stringify({ error: "Could not fetch PeeringDB networks" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
var probeAsns = new Set(atlasState.cache.asns_with_probes || []);
|
||||||
|
|
||||||
|
var enriched = pdbData.data.map(function(n) {
|
||||||
|
var org = pdbOrgState.map.get(n.org_id) || {};
|
||||||
|
var cc = org.country || "";
|
||||||
|
return {
|
||||||
|
asn: n.asn,
|
||||||
|
name: n.name || "",
|
||||||
|
org_name: org.name || "",
|
||||||
|
country: cc,
|
||||||
|
country_name: cc,
|
||||||
|
info_type: n.info_type || "",
|
||||||
|
has_probe: probeAsns.has(n.asn),
|
||||||
|
};
|
||||||
|
}).filter(function(n) { return n.asn > 0 && n.country; });
|
||||||
|
|
||||||
|
var result = JSON.stringify({
|
||||||
|
networks: enriched,
|
||||||
|
total: enriched.length,
|
||||||
|
with_probes: enriched.filter(function(n) { return n.has_probe; }).length,
|
||||||
|
without_probes: enriched.filter(function(n) { return !n.has_probe; }).length,
|
||||||
|
atlas_unique_asns: probeAsns.size,
|
||||||
|
org_countries_loaded: pdbOrgState.map.size,
|
||||||
|
fetched_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
cacheSet(liaCacheKey, result, 30 * 60 * 1000);
|
||||||
|
res.end(result);
|
||||||
|
}).catch(function(e) {
|
||||||
|
res.end(JSON.stringify({ error: "PeeringDB fetch failed: " + e.message }));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { handleAtlasCoverage, handleParseFile, handleLiaCoverage };
|
||||||
Loading…
x
Reference in New Issue
Block a user