import express, { Express, Request, Response, NextFunction } from 'express'; import cors from 'cors'; import compression from 'compression'; import { apiRateLimiter, strictRateLimiter } from './middleware/rate-limit'; import tokenRoutes from './routes/tokens'; import reportRoutes from './routes/report'; import adminRoutes from './routes/admin'; import configRoutes from './routes/config'; import bridgeRoutes from './routes/bridge'; import quoteRoutes from './routes/quote'; import routeTreeRoutes from './routes/routes'; import tokenMappingRoutes from './routes/token-mapping'; import heatmapRoutes from './routes/heatmap'; import arbitrageRoutes from './routes/arbitrage'; import aggregatorRouteMatrixRoutes from './routes/aggregator-routes'; import partnerPayloadRoutes from './routes/partner-payloads'; import { MultiChainIndexer } from '../indexer/chain-indexer'; import { getDatabasePool } from '../database/client'; import winston from 'winston'; // Setup logger const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), }), ], }); export class ApiServer { private app: Express; private port: number; private indexer: MultiChainIndexer; constructor() { this.app = express(); this.port = parseInt(process.env.PORT || '3000', 10); this.indexer = new MultiChainIndexer(); this.setupMiddleware(); this.setupRoutes(); this.setupErrorHandling(); } private setupMiddleware(): void { // CORS this.app.use(cors()); // Compression this.app.use(compression()); // Body parsing this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); // Rate limiting this.app.use('/api/v1', apiRateLimiter); // Request logging this.app.use((req: Request, res: Response, next: NextFunction) => { logger.info(`${req.method} ${req.path}`, { ip: req.ip, userAgent: req.get('user-agent'), }); next(); }); } private setupRoutes(): void { // Health check this.app.get('/health', async (req: Request, res: Response) => { try { // Check database connection const pool = getDatabasePool(); await pool.query('SELECT 1'); res.json({ status: 'healthy', timestamp: new Date().toISOString(), services: { database: 'connected', indexer: 'running', }, }); } catch (error) { res.status(503).json({ status: 'unhealthy', timestamp: new Date().toISOString(), error: error instanceof Error ? error.message : 'Unknown error', }); } }); // API routes this.app.use('/api/v1', tokenRoutes); this.app.use('/api/v1', configRoutes); this.app.use('/api/v1/report', reportRoutes); this.app.use('/api/v1/bridge', bridgeRoutes); this.app.use('/api/v1', routeTreeRoutes); this.app.use('/api/v1/token-mapping', tokenMappingRoutes); this.app.use('/api/v1', quoteRoutes); this.app.use('/api/v1', heatmapRoutes); this.app.use('/api/v1', arbitrageRoutes); this.app.use('/api/v1', aggregatorRouteMatrixRoutes); this.app.use('/api/v1', partnerPayloadRoutes); // Admin routes (stricter rate limit) this.app.use('/api/v1/admin', strictRateLimiter, adminRoutes); // Root this.app.get('/', (req: Request, res: Response) => { res.json({ name: 'Token Aggregation Service', version: '1.0.0', endpoints: { health: '/health', api: '/api/v1', }, }); }); } private setupErrorHandling(): void { // 404 handler this.app.use((req: Request, res: Response) => { res.status(404).json({ error: 'Not found' }); }); // Error handler // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Express error handler requires 4-arg signature this.app.use((err: Error, req: Request, res: Response, _next: NextFunction) => { logger.error('Unhandled error:', err); res.status(500).json({ error: 'Internal server error', message: process.env.NODE_ENV === 'development' ? err.message : undefined, }); }); } async start(): Promise { try { // Initialize indexer await this.indexer.initialize(); // Start indexing await this.indexer.startAll(); // Start server this.app.listen(this.port, () => { logger.info(`Token Aggregation Service listening on port ${this.port}`); logger.info(`Health check: http://localhost:${this.port}/health`); logger.info(`API: http://localhost:${this.port}/api/v1`); }); } catch (error) { logger.error('Failed to start server:', error); process.exit(1); } } async stop(): Promise { this.indexer.stopAll(); logger.info('Server stopped'); } }