import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; interface RibPrefixQuery { prefix?: string; router?: string; } interface RibDumpQuery { router?: string; asn?: string; limit?: string; } // risClient is injected from outside; lazy import avoids circular deps at startup let risClient: any = null; export function setRisClient(client: any): void { risClient = client; } export async function ribRoutes(fastify: FastifyInstance): Promise { // GET /api/rib/prefix fastify.get<{ Querystring: RibPrefixQuery }>( '/rib/prefix', async (request: FastifyRequest<{ Querystring: RibPrefixQuery }>, reply: FastifyReply) => { reply.header('Access-Control-Allow-Origin', '*'); reply.header('Cache-Control', 'no-store'); if (!risClient) { return reply.status(503).send({ error: 'bio-rd RIS not configured' }); } const prefix = request.query.prefix || ''; const routerParam = request.query.router || 'default'; if (!prefix) { return reply.status(400).send({ error: 'prefix required' }); } try { const t0 = Date.now(); const routers = await risClient.getRouters(); const routerName = (routerParam === 'default' && routers.length > 0) ? routers[0] : routerParam; const [routes, longer] = await Promise.all([ risClient.lpm(routerName, prefix), risClient.getLonger(routerName, prefix), ]); return reply.status(200).send({ prefix, router: routerName, routes: routes || [], moreSpecifics: (longer || []).slice(0, 20), source: 'bio-rd-local', latencyMs: Date.now() - t0, }); } catch (e: any) { return reply.status(500).send({ error: e.message }); } } ); // GET /api/rib/routers fastify.get( '/rib/routers', async (_request: FastifyRequest, reply: FastifyReply) => { reply.header('Access-Control-Allow-Origin', '*'); reply.header('Cache-Control', 'no-store'); if (!risClient) { return reply.status(503).send({ error: 'bio-rd RIS not configured' }); } try { const routers = await risClient.getRouters(); return reply.status(200).send({ routers: routers || [], source: 'bio-rd-local' }); } catch (e: any) { return reply.status(500).send({ error: e.message }); } } ); // GET /api/rib/dump fastify.get<{ Querystring: RibDumpQuery }>( '/rib/dump', async (request: FastifyRequest<{ Querystring: RibDumpQuery }>, reply: FastifyReply) => { reply.header('Access-Control-Allow-Origin', '*'); reply.header('Cache-Control', 'no-store'); if (!risClient) { return reply.status(503).send({ error: 'bio-rd RIS not configured' }); } const router = request.query.router || ''; const asnFilter = request.query.asn ? parseInt(request.query.asn, 10) : undefined; const limit = Math.min(parseInt(request.query.limit || '100', 10), 1000); if (!router) { return reply.status(400).send({ error: 'router required' }); } try { const t0 = Date.now(); const allRoutes = await risClient.dumpRib(router, 'default', asnFilter); const routes = (allRoutes || []).slice(0, limit); return reply.status(200).send({ router, routes, total: (allRoutes || []).length, source: 'bio-rd-local', latencyMs: Date.now() - t0, }); } catch (e: any) { return reply.status(500).send({ error: e.message }); } } ); }