PeerCortex/server/routes/peers-find.js
Rene Fichtmueller 49aa46fef3 refactor: extract relationships/compare/quick-ix/peers-find/prefix-detail/ix-detail/topology/whois/enrich routes
Bug fix (agreed pre-existing bug #2 of 3): /api/compare called
fetchRPKIPerPrefix(asn, prefix), never defined anywhere in the codebase --
threw ReferenceError whenever either ASN had >=1 sample prefix, caught by
the outer try/catch, returning 500. Replaced both call sites with
validateRPKIWithCache(asn, prefix), the existing single-prefix RPKI check
used everywhere else in the codebase -- its return shape ({status, ...})
is exactly what the surrounding code already expects.

Verified via node --check, eslint no-undef (only 1 known bug remains:
webhooks POST's `secret`), and smoke-test harness (28/28 match). The
compare fix itself isn't exercised by the smoke test's fixed test ASNs
(both return empty prefix samples locally), but the fix is a direct,
same-shape substitution of an already-proven function.

server.js: 1736 -> 989 lines.
2026-07-17 07:57:04 +02:00

107 lines
3.7 KiB
JavaScript

const { fetchPeeringDB } = require("../services/peeringdb");
// Peer Matching endpoint: /api/peers/find?ix=NAME&policy=open&min_speed=10000
async function handlePeersFind(req, res, url) {
const ixName = url.searchParams.get("ix") || "";
const policy = url.searchParams.get("policy") || "";
const minSpeed = parseInt(url.searchParams.get("min_speed") || "0");
const netType = url.searchParams.get("type") || "";
if (!ixName) {
res.writeHead(400);
return res.end(JSON.stringify({ error: "Missing ix parameter (IX name)" }));
}
const start = Date.now();
try {
// Search for IX by name
const ixSearch = await fetchPeeringDB("/ix?name__contains=" + encodeURIComponent(ixName));
const ixResults = ixSearch?.data || [];
if (ixResults.length === 0) {
return res.end(JSON.stringify({ error: "No IX found matching: " + ixName, matches: [] }));
}
// Use first matching IX
const ix = ixResults[0];
const ixId = ix.id;
// Get ixlan for this IX
const ixlanData = await fetchPeeringDB("/ixlan?ix_id=" + ixId);
const ixlans = ixlanData?.data || [];
if (ixlans.length === 0) {
return res.end(JSON.stringify({ ix: { id: ixId, name: ix.name }, matches: [] }));
}
const ixlanId = ixlans[0].id;
// Get all networks at this IX
const netixlanData = await fetchPeeringDB("/netixlan?ixlan_id=" + ixlanId);
const netixlans = netixlanData?.data || [];
// Get unique net_ids
const netIds = [...new Set(netixlans.map(n => n.net_id))];
// Fetch network details in batches
const networks = [];
const batchSize = 20;
for (let i = 0; i < Math.min(netIds.length, 200); i += batchSize) {
const batch = netIds.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(nid => fetchPeeringDB("/net/" + nid))
);
batchResults.forEach(r => {
if (r?.data?.[0]) networks.push(r.data[0]);
});
}
// Filter and rank
let filtered = networks.map(net => {
const nix = netixlans.filter(n => n.net_id === net.id);
const maxSpeed = Math.max(...nix.map(n => n.speed || 0));
return {
asn: net.asn,
name: net.name || "",
policy: net.policy_general || "",
type: net.info_type || "",
speed_mbps: maxSpeed,
speed_gbps: maxSpeed >= 1000 ? (maxSpeed / 1000) + " Gbps" : maxSpeed + " Mbps",
traffic: net.info_traffic || "",
website: net.website || "",
peeringdb_id: net.id,
ipv4: nix[0]?.ipaddr4 || null,
ipv6: nix[0]?.ipaddr6 || null,
};
});
// Apply filters
if (policy) {
filtered = filtered.filter(n => n.policy.toLowerCase().includes(policy.toLowerCase()));
}
if (minSpeed > 0) {
filtered = filtered.filter(n => n.speed_mbps >= minSpeed);
}
if (netType) {
filtered = filtered.filter(n => n.type.toLowerCase().includes(netType.toLowerCase()));
}
// Sort by speed desc
filtered.sort((a, b) => b.speed_mbps - a.speed_mbps);
// Also find common IXPs for each match (check if they share other IXPs)
const duration = Date.now() - start;
return res.end(JSON.stringify({
meta: { duration_ms: duration, timestamp: new Date().toISOString() },
ix: { id: ixId, name: ix.name, ixlan_id: ixlanId },
total_members: netixlans.length,
filtered_count: filtered.length,
matches: filtered.slice(0, 50),
}, null, 2));
} catch (err) {
res.writeHead(500);
return res.end(JSON.stringify({ error: "Peer matching failed", message: err.message }));
}
}
module.exports = { handlePeersFind };