import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; interface IxMatrixQuery { ix_id?: string; } async function fetchWithRetry(url: string, retries = 1, timeout = 10000): Promise { for (let i = 0; i <= retries; i++) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { if (i === retries) throw new Error(`HTTP ${response.status}`); continue; } return await response.json(); } catch (e) { clearTimeout(timeoutId); if (i === retries) throw e; await new Promise(r => setTimeout(r, 1500)); } } } export async function ixMatrixRoutes(fastify: FastifyInstance): Promise { fastify.get<{ Querystring: IxMatrixQuery }>( '/ix-matrix', async (request: FastifyRequest<{ Querystring: IxMatrixQuery }>, reply: FastifyReply) => { let ixIdStr = request.query.ix_id || ''; const ixId = ixIdStr.replace(/[^0-9]/g, ''); reply.header('Access-Control-Allow-Origin', '*'); reply.header('Cache-Control', 'public, max-age=3600'); if (!ixId) { return reply.status(400).send({ error: 'ix_id required' }); } try { const PEERINGDB_API_URL = 'https://www.peeringdb.com/api'; const [netixData, ixData] = await Promise.all([ fetchWithRetry(`${PEERINGDB_API_URL}/netixlan?ix_id=${ixId}&depth=1&limit=200`, 1, 15000), fetchWithRetry(`${PEERINGDB_API_URL}/ix/${ixId}`, 1, 10000), ]); const ix = ixData && ixData.data && ixData.data[0]; const members = ((netixData && netixData.data) || []).map((m: any) => ({ asn: m.asn, name: m.name, speed: m.speed, ipaddr4: m.ipaddr4, ipaddr6: m.ipaddr6, policy: m.policy_general })); members.sort((a: any, b: any) => (b.speed || 0) - (a.speed || 0)); return reply.status(200).send({ ix_id: ixId, ix_name: ix && ix.name, ix_city: ix && ix.city, members, member_count: members.length }); } catch (e: any) { return reply.status(500).send({ error: e.message }); } } ); }