import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; interface RpkiHistoryQuery { asn?: string; } export async function rpkiHistoryRoutes(fastify: FastifyInstance): Promise { fastify.get<{ Querystring: RpkiHistoryQuery }>( '/rpki-history', async (request: FastifyRequest<{ Querystring: RpkiHistoryQuery }>, reply: FastifyReply) => { let asnStr = request.query.asn || ''; const asn = asnStr.replace(/[^0-9]/g, ''); reply.header('Access-Control-Allow-Origin', '*'); reply.header('Cache-Control', 'public, max-age=3600'); if (!asn) { return reply.status(400).send({ error: 'asn required' }); } try { const url = `https://stat.ripe.net/data/routing-history/data.json?resource=AS${asn}&max_rows=100`; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 6000); const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`RIPE Stat returned HTTP ${response.status}`); } const data = await response.json(); const byOrigin = (data && data.data && data.data.by_origin) || []; const prefixes: any[] = []; for (const orig of byOrigin) { if (orig.prefixes) { for (const pfx of orig.prefixes) { prefixes.push(pfx); } } } return reply.status(200).send({ asn: asn, prefixes: prefixes, source: 'RIPE Stat routing-history' }); } catch (e: any) { return reply.status(500).send({ error: e.message }); } } ); }