const { fetchJSONWithRetry } = require("../services/http-helpers"); const { PEERINGDB_API_KEY } = require("../services/peeringdb"); const { trackVisitor } = require("../visitors"); // Name Search (RIPE Stat + PeeringDB combined) async function handleSearch(req, res) { const params = new URL(req.url, 'http://localhost').searchParams; const q = (params.get('q') || '').trim(); res.setHeader('Content-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Cache-Control', 'public, max-age=120'); if (!q || q.length < 2) { res.writeHead(400); return res.end(JSON.stringify({error:'query too short'})); } try { const results = []; const seen = new Set(); // Source 1: RIPE Stat searchcomplete (fast, covers ASNs + org names) try { const ripeUrl = 'https://stat.ripe.net/data/searchcomplete/data.json?resource=' + encodeURIComponent(q); const ripeData = await fetchJSONWithRetry(ripeUrl, { timeout: 6000 }); const cats = ripeData && ripeData.data && ripeData.data.categories || []; for (var ci = 0; ci < cats.length; ci++) { var suggs = cats[ci].suggestions || []; for (var si = 0; si < suggs.length; si++) { var s = suggs[si]; var val = (s.value || '').toString(); // Only ASN results if (/^AS\d+$/i.test(val) && !seen.has(val)) { seen.add(val); // Use description (e.g. "FLEXOPTIX, DE") as the display label var ripeName = s.description || s.label || val; results.push({ asn: val.replace(/^AS/i,''), label: ripeName, description: '', source: 'RIPE Stat' }); } } } } catch(e) { /* RIPE Stat failed, continue */ } // Source 2: PeeringDB name search (best for network operator names) try { var pdbUrl = 'https://www.peeringdb.com/api/net?name__icontains=' + encodeURIComponent(q) + '&depth=1&limit=10'; if (PEERINGDB_API_KEY) pdbUrl += '&key=' + PEERINGDB_API_KEY; const pdbData = await fetchJSONWithRetry(pdbUrl, { timeout: 8000 }); var nets = pdbData && pdbData.data || []; for (var ni = 0; ni < nets.length; ni++) { var net = nets[ni]; var asnKey = 'AS' + net.asn; if (net.asn && !seen.has(asnKey)) { seen.add(asnKey); var pdbDesc = [net.info_type, net.country].filter(Boolean).join(' · '); results.push({ asn: String(net.asn), label: net.name || asnKey, description: pdbDesc, source: 'PeeringDB' }); } } } catch(e) { /* PeeringDB failed, continue */ } // Sort: RIPE results first (usually more relevant for ASN lookup), then PeeringDB results.sort((a, b) => { if (a.source === b.source) return 0; return a.source === 'RIPE Stat' ? -1 : 1; }); res.writeHead(200); return res.end(JSON.stringify({ q: q, results: results.slice(0, 12) })); } catch(e) { res.writeHead(500); return res.end(JSON.stringify({error: e.message})); } } // GET /api/visitors — unique visitor count function handleVisitors(req, res) { res.setHeader("Content-Type", "application/json"); res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Cache-Control", "no-store"); const count = trackVisitor(req); res.writeHead(200); return res.end(JSON.stringify({ visitors: count })); } module.exports = { handleSearch, handleVisitors };