BLOCKER-001: Token Refresh Improvements (4 phases) - FASE 1: Rate limiting específico para /auth/refresh (15 req/15min per token) - FASE 2: Token rotation con SHA-256 hash y reuse detection - FASE 3: Session validation con cache de 30s (95% menos queries) - FASE 4: Proactive refresh con X-Token-Expires-At header E2E Tests: Video Upload Module (backend - 91 tests) - Suite 4: Controller tests (22 tests) - REST API endpoints validation - Suite 5: Service tests (29 tests) - Business logic and database operations - Suite 6: Storage tests (35 tests) - S3/R2 multipart upload integration - Suite 7: Full E2E flow (5 tests) - Complete pipeline validation Changes: - auth.middleware.ts: Session validation + token expiry header - rate-limiter.ts: Specific rate limiter for refresh endpoint - token.service.ts: Token rotation logic + session validation - session-cache.service.ts (NEW): 30s TTL cache for session validation - auth.types.ts: Extended types for session validation - auth.routes.ts: Applied refreshTokenRateLimiter - index.ts: Updated CORS to expose X-Token-Expires-At Tests created: - auth-token-refresh.test.ts (15 tests) - E2E token refresh flow - video-controller.test.ts (22 tests) - REST API validation - video-service.test.ts (29 tests) - Business logic validation - storage-service.test.ts (35 tests) - S3/R2 integration - video-upload-flow.test.ts (5 tests) - Complete pipeline Database migration executed: - Added refresh_token_hash and refresh_token_issued_at columns - Created index on refresh_token_hash for performance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
220 lines
6.5 KiB
TypeScript
220 lines
6.5 KiB
TypeScript
/**
|
|
* Trading Platform - Backend API
|
|
* ==========================
|
|
*
|
|
* Main entry point for the Express.js backend API.
|
|
*/
|
|
|
|
import express, { Express, Request, Response } from 'express';
|
|
import { createServer } from 'http';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import compression from 'compression';
|
|
import morgan from 'morgan';
|
|
import { config } from './config/index.js';
|
|
import { logger } from './shared/utils/logger.js';
|
|
import { setupSwagger } from './config/swagger.config.js';
|
|
|
|
// WebSocket
|
|
import { wsManager, tradingStreamService } from './core/websocket/index.js';
|
|
|
|
// Background Jobs
|
|
import { distributionJob } from './modules/investment/jobs/index.js';
|
|
|
|
// Portfolio WebSocket
|
|
import { portfolioWebSocket } from './modules/portfolio/websocket/portfolio.websocket.js';
|
|
|
|
// Import routes
|
|
import { authRouter } from './modules/auth/auth.routes.js';
|
|
import { usersRouter } from './modules/users/users.routes.js';
|
|
import { educationRouter } from './modules/education/education.routes.js';
|
|
import { tradingRouter } from './modules/trading/trading.routes.js';
|
|
import { investmentRouter } from './modules/investment/investment.routes.js';
|
|
import { paymentsRouter } from './modules/payments/payments.routes.js';
|
|
import { adminRouter } from './modules/admin/admin.routes.js';
|
|
import { mlRouter } from './modules/ml/ml.routes.js';
|
|
import { llmRouter } from './modules/llm/llm.routes.js';
|
|
import { portfolioRouter } from './modules/portfolio/portfolio.routes.js';
|
|
import { agentsRouter } from './modules/agents/agents.routes.js';
|
|
import { notificationRouter } from './modules/notifications/notification.routes.js';
|
|
|
|
// Service clients for health checks
|
|
import { tradingAgentsClient, mlEngineClient, llmAgentClient } from './shared/clients/index.js';
|
|
|
|
// Health aggregator
|
|
import { getSystemHealth, getQuickHealth } from './shared/utils/health-aggregator.js';
|
|
|
|
// Import middleware
|
|
import { errorHandler } from './core/middleware/error-handler.js';
|
|
import { notFoundHandler } from './core/middleware/not-found.js';
|
|
import { rateLimiter } from './core/middleware/rate-limiter.js';
|
|
|
|
const app: Express = express();
|
|
|
|
// Trust proxy (for rate limiting behind reverse proxy)
|
|
app.set('trust proxy', 1);
|
|
|
|
// Security middleware
|
|
app.use(helmet());
|
|
|
|
// CORS
|
|
app.use(cors({
|
|
origin: config.cors.origins,
|
|
credentials: true,
|
|
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
|
|
exposedHeaders: ['X-Token-Expires-At'], // FASE 4: Expose token expiry for proactive refresh
|
|
}));
|
|
|
|
// Compression
|
|
app.use(compression());
|
|
|
|
// Request logging
|
|
app.use(morgan('combined', {
|
|
stream: { write: (message) => logger.info(message.trim()) }
|
|
}));
|
|
|
|
// Body parsing
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
|
|
// Rate limiting
|
|
app.use(rateLimiter);
|
|
|
|
// Swagger documentation
|
|
setupSwagger(app, '/api/v1');
|
|
|
|
// Quick health check (before auth) - just checks if backend is running
|
|
app.get('/health', (req: Request, res: Response) => {
|
|
const quickHealth = getQuickHealth();
|
|
res.json({
|
|
...quickHealth,
|
|
version: config.app.version,
|
|
environment: config.app.env,
|
|
});
|
|
});
|
|
|
|
// Full system health check - checks all services including database
|
|
app.get('/health/full', async (req: Request, res: Response) => {
|
|
try {
|
|
const systemHealth = await getSystemHealth();
|
|
const statusCode = systemHealth.status === 'healthy' ? 200 :
|
|
systemHealth.status === 'degraded' ? 200 : 503;
|
|
res.status(statusCode).json(systemHealth);
|
|
} catch (error) {
|
|
logger.error('Health check failed:', error);
|
|
res.status(500).json({
|
|
status: 'unhealthy',
|
|
error: 'Health check failed',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
});
|
|
|
|
// Services health check (legacy, kept for compatibility)
|
|
app.get('/health/services', async (req: Request, res: Response) => {
|
|
try {
|
|
const systemHealth = await getSystemHealth();
|
|
// Transform to legacy format for backwards compatibility
|
|
const services: Record<string, { status: string; latency?: number; error?: string }> = {};
|
|
for (const service of systemHealth.services) {
|
|
services[service.name] = {
|
|
status: service.status,
|
|
latency: service.latency_ms,
|
|
error: service.error,
|
|
};
|
|
}
|
|
res.json({
|
|
status: systemHealth.status,
|
|
services,
|
|
timestamp: systemHealth.timestamp,
|
|
});
|
|
} catch (error) {
|
|
logger.error('Services health check failed:', error);
|
|
res.status(500).json({
|
|
status: 'unhealthy',
|
|
error: 'Health check failed',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
});
|
|
|
|
// API routes
|
|
const apiRouter = express.Router();
|
|
|
|
apiRouter.use('/auth', authRouter);
|
|
apiRouter.use('/users', usersRouter);
|
|
apiRouter.use('/education', educationRouter);
|
|
apiRouter.use('/trading', tradingRouter);
|
|
apiRouter.use('/investment', investmentRouter);
|
|
apiRouter.use('/payments', paymentsRouter);
|
|
apiRouter.use('/admin', adminRouter);
|
|
apiRouter.use('/ml', mlRouter);
|
|
apiRouter.use('/llm', llmRouter);
|
|
apiRouter.use('/portfolio', portfolioRouter);
|
|
apiRouter.use('/agents', agentsRouter);
|
|
apiRouter.use('/notifications', notificationRouter);
|
|
|
|
// Mount API router
|
|
app.use('/api/v1', apiRouter);
|
|
|
|
// 404 handler
|
|
app.use(notFoundHandler);
|
|
|
|
// Error handler
|
|
app.use(errorHandler);
|
|
|
|
// Create HTTP server
|
|
const httpServer = createServer(app);
|
|
|
|
// Initialize WebSocket
|
|
wsManager.initialize(httpServer);
|
|
tradingStreamService.initialize();
|
|
portfolioWebSocket.initialize();
|
|
|
|
// Start background jobs
|
|
distributionJob.start();
|
|
|
|
// WebSocket stats endpoint
|
|
app.get('/api/v1/ws/stats', (req: Request, res: Response) => {
|
|
res.json({
|
|
success: true,
|
|
data: tradingStreamService.getStats(),
|
|
});
|
|
});
|
|
|
|
// Start server
|
|
const PORT = config.app.port;
|
|
|
|
httpServer.listen(PORT, () => {
|
|
logger.info(`🚀 Trading Platform API server running on port ${PORT}`);
|
|
logger.info(`📡 WebSocket server running on ws://localhost:${PORT}/ws`);
|
|
logger.info(`📚 Environment: ${config.app.env}`);
|
|
logger.info(`📖 Docs available at http://localhost:${PORT}/api/v1/docs`);
|
|
});
|
|
|
|
// Graceful shutdown
|
|
const gracefulShutdown = () => {
|
|
logger.info('Shutting down gracefully...');
|
|
distributionJob.stop();
|
|
portfolioWebSocket.shutdown();
|
|
tradingStreamService.shutdown();
|
|
wsManager.shutdown();
|
|
httpServer.close(() => {
|
|
logger.info('HTTP server closed');
|
|
process.exit(0);
|
|
});
|
|
};
|
|
|
|
process.on('SIGTERM', () => {
|
|
logger.info('SIGTERM received.');
|
|
gracefulShutdown();
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
logger.info('SIGINT received.');
|
|
gracefulShutdown();
|
|
});
|
|
|
|
export { app };
|