Files
smom-dbis-138/services/token-aggregation/src/api/server.ts

174 lines
5.1 KiB
TypeScript
Raw Normal View History

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<void> {
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<void> {
this.indexer.stopAll();
logger.info('Server stopped');
}
}