@@ -2,9 +2,11 @@ import Fastify from 'fastify';
22import cors from '@fastify/cors' ;
33import rateLimit from '@fastify/rate-limit' ;
44import helmet from '@fastify/helmet' ;
5+ import fastifyStatic from '@fastify/static' ;
56import { readFile } from 'node:fs/promises' ;
6- import { basename , resolve } from 'node:path' ;
7- import { loadData , initializeDataOnStartup , getCachedData , searchChains , getChainById , getAllChains , getAllRelations , getRelationsById , getEndpointsById , getAllEndpoints , getAllKeywords , validateChainData , traverseRelations } from './dataService.js' ;
7+ import { basename , resolve , dirname , join } from 'node:path' ;
8+ import { fileURLToPath as toFilePath } from 'node:url' ;
9+ import { loadData , initializeDataOnStartup , getCachedData , searchChains , getChainById , getAllChains , getAllRelations , getRelationsById , getEndpointsById , getAllEndpoints , getAllKeywords , validateChainData , traverseRelations , countChainsByTag } from './dataService.js' ;
810import { getMonitoringResults , getMonitoringStatus , startRpcHealthCheck } from './rpcMonitor.js' ;
911import {
1012 PORT , HOST , BODY_LIMIT , MAX_PARAM_LENGTH ,
@@ -51,11 +53,22 @@ export async function buildApp(options = {}) {
5153 directives : {
5254 defaultSrc : [ "'self'" ] ,
5355 scriptSrc : [ "'self'" ] ,
54- styleSrc : [ "'self'" ]
56+ styleSrc : [ "'self'" ] ,
57+ fontSrc : [ "'self'" ] ,
58+ connectSrc : [ "'self'" ] ,
59+ imgSrc : [ "'self'" , "data:" ]
5560 }
5661 }
5762 } ) ;
5863
64+ // Serve public/ directory for the 3D visualization UI
65+ const __dir = dirname ( toFilePath ( import . meta. url ) ) ;
66+ await fastify . register ( fastifyStatic , {
67+ root : join ( __dir , 'public' ) ,
68+ prefix : '/ui/' ,
69+ decorateReply : false
70+ } ) ;
71+
5972 // Security: Rate limiting
6073 await fastify . register ( rateLimit , {
6174 max : RATE_LIMIT_MAX ,
@@ -75,7 +88,7 @@ export async function buildApp(options = {}) {
7588 /**
7689 * Health check endpoint
7790 */
78- fastify . get ( '/health' , async ( request , reply ) => {
91+ fastify . get ( '/health' , async ( ) => {
7992 const cachedData = getCachedData ( ) ;
8093 return {
8194 status : 'ok' ,
@@ -157,7 +170,7 @@ export async function buildApp(options = {}) {
157170 /**
158171 * Get all chain relations
159172 */
160- fastify . get ( '/relations' , async ( request , reply ) => {
173+ fastify . get ( '/relations' , async ( ) => {
161174 const relations = getAllRelations ( ) ;
162175
163176 return relations ;
@@ -205,7 +218,7 @@ export async function buildApp(options = {}) {
205218 /**
206219 * Get all endpoints
207220 */
208- fastify . get ( '/endpoints' , async ( request , reply ) => {
221+ fastify . get ( '/endpoints' , async ( ) => {
209222 const endpoints = getAllEndpoints ( ) ;
210223
211224 return {
@@ -234,7 +247,7 @@ export async function buildApp(options = {}) {
234247 /**
235248 * Get raw data sources
236249 */
237- fastify . get ( '/sources' , async ( request , reply ) => {
250+ fastify . get ( '/sources' , async ( ) => {
238251 const cachedData = getCachedData ( ) ;
239252 return {
240253 lastUpdated : cachedData . lastUpdated ,
@@ -281,7 +294,7 @@ export async function buildApp(options = {}) {
281294 /**
282295 * Get SLIP-0044 coin types as JSON
283296 */
284- fastify . get ( '/slip44' , async ( request , reply ) => {
297+ fastify . get ( '/slip44' , async ( _request , reply ) => {
285298 const cachedData = getCachedData ( ) ;
286299
287300 if ( ! cachedData . slip44 ) {
@@ -366,7 +379,7 @@ export async function buildApp(options = {}) {
366379 /**
367380 * Get RPC monitoring results
368381 */
369- fastify . get ( '/rpc-monitor' , async ( request , reply ) => {
382+ fastify . get ( '/rpc-monitor' , async ( ) => {
370383 const results = getMonitoringResults ( ) ;
371384 const status = getMonitoringStatus ( ) ;
372385
@@ -409,15 +422,11 @@ export async function buildApp(options = {}) {
409422 /**
410423 * Get aggregate stats
411424 */
412- fastify . get ( '/stats' , async ( request , reply ) => {
425+ fastify . get ( '/stats' , async ( ) => {
413426 const chains = getAllChains ( ) ;
414427 const monitorResults = getMonitoringResults ( ) ;
415428
416- const totalChains = chains . length ;
417- const totalMainnets = chains . filter ( c => ! c . tags ?. includes ( 'Testnet' ) && ! c . tags ?. includes ( 'L2' ) && ! c . tags ?. includes ( 'Beacon' ) ) . length ;
418- const totalTestnets = chains . filter ( c => c . tags ?. includes ( 'Testnet' ) ) . length ;
419- const totalL2s = chains . filter ( c => c . tags ?. includes ( 'L2' ) ) . length ;
420- const totalBeacons = chains . filter ( c => c . tags ?. includes ( 'Beacon' ) ) . length ;
429+ const { totalChains, totalMainnets, totalTestnets, totalL2s, totalBeacons } = countChainsByTag ( chains ) ;
421430
422431 const rpcWorking = monitorResults . workingEndpoints ;
423432 const rpcFailed = monitorResults . failedEndpoints || 0 ;
@@ -487,10 +496,9 @@ export async function buildApp(options = {}) {
487496/**
488497 * Parse and validate an integer parameter
489498 * @param {string } param - Parameter value to parse
490- * @param {string } paramName - Name of the parameter for error message
491499 * @returns {number|null } Parsed integer or null if invalid
492500 */
493- function parseIntParam ( param , paramName = 'ID' ) {
501+ function parseIntParam ( param ) {
494502 const parsed = Number . parseInt ( param , 10 ) ;
495503 return Number . isNaN ( parsed ) ? null : parsed ;
496504}
@@ -507,12 +515,10 @@ function sendError(reply, code, message) {
507515
508516// Only run the server if this file is executed directly (CLI mode)
509517// This allows the file to be imported for testing without starting the server
510- import { fileURLToPath } from 'node:url' ;
511-
512- const __filename = fileURLToPath ( import . meta. url ) ;
518+ const __filename = toFilePath ( import . meta. url ) ;
513519
514520// Check if this file is being run directly
515- const isMainModule = process . argv [ 1 ] === __filename || process . argv [ 1 ] === fileURLToPath ( import . meta . url ) ;
521+ const isMainModule = process . argv [ 1 ] === __filename ;
516522
517523if ( isMainModule ) {
518524 const start = async ( ) => {
0 commit comments