const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); const compression = require('compression'); const mongoSanitize = require('express-mongo-sanitize'); const hpp = require('hpp'); const { errorHandler } = require('./middleware/errorHandler'); const { requestLogger } = require('./middleware/requestLogger'); const { authMiddleware, roleCheck } = require('./middleware/auth'); const { validateRequest } = require('./middleware/validation'); const { correlationIdMiddleware } = require('./middleware/correlationId'); const { metricsMiddleware } = require('./middleware/metrics'); const { rateLimiterRedis } = require('./utils/rateLimiter'); const { cache } = require('./utils/cache'); const routes = require('./routes'); const swaggerUi = require('swagger-ui-express'); const swaggerDocument = require('./swagger.json'); const logger = require('./utils/logger'); const { AppError } = require('./utils/errors'); const app = express(); app.use(helmet({ contentSecurityPolicy: { useDefaults: true, directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:', 'https:'], connectSrc: ["'self'"], frameSrc: ["'none'"], objectSrc: ["'none'"] } }, crossOriginEmbedderPolicy: true, crossOriginOpenerPolicy: true, crossOriginResourcePolicy: { policy: 'same-origin' }, dnsPrefetchControl: { allow: false }, frameguard: { action: 'deny' }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, ieNoOpen: true, noSniff: true, referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, xssFilter: true, permittedCrossDomainPolicies: { permittedPolicies: 'none' } })); app.use(cors({ origin: async (origin, callback) => { try { const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || []; if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { throw new AppError('Not allowed by CORS', 403); } } catch (error) { callback(error); } }, methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Correlation-ID'], credentials: true, maxAge: parseInt(process.env.CORS_MAX_AGE) || 86400 })); app.use(compression()); app.use(express.json({ limit: '10kb' })); app.use(express.urlencoded({ extended: true, limit: '10kb' })); app.use(mongoSanitize()); app.use(hpp()); app.use(correlationIdMiddleware); app.use(metricsMiddleware); app.use(rateLimiterRedis); app.use(requestLogger); app.use(cache); app.get('/health', async (req, res) => { try { const healthData = { status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime(), memoryUsage: process.memoryUsage(), version: process.env.npm_package_version }; res.status(200).json(healthData); } catch (error) { logger.error('Health check failed:', { error: error.message, stack: error.stack }); res.status(503).json({ status: 'error', message: 'Service unavailable' }); } }); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, { explorer: true, customCss: '.swagger-ui .topbar { display: none }', swaggerOptions: { persistAuthorization: true, docExpansion: 'none', filter: true } })); app.use('/api', authMiddleware, validateRequest, roleCheck, routes); app.use('*', (req, res) => { res.status(404).json({ status: 'error', message: 'Resource not found', path: req.originalUrl }); }); app.use(errorHandler); process.on('unhandledRejection', (err) => { logger.error('Unhandled Rejection:', { error: err.message, stack: err.stack }); if (process.env.NODE_ENV === 'production') { process.exit(1); } }); process.on('uncaughtException', (err) => { logger.error('Uncaught Exception:', { error: err.message, stack: err.stack }); if (process.env.NODE_ENV === 'production') { process.exit(1); } }); module.exports = app;