const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const morgan = require('morgan'); const compression = require('compression'); const rateLimit = require('express-rate-limit'); require('dotenv').config({ path: './config.env' }); const logger = require('./utils/logger'); const errorHandler = require('./middleware/errorHandler'); const requestValidator = require('./middleware/requestValidator'); const healthCheck = require('./middleware/healthCheck'); // Import routes const recommendationRoutes = require('./routes/recommendations'); const healthRoutes = require('./routes/health'); const app = express(); const PORT = process.env.PORT || 8010; const HOST = process.env.HOST || '0.0.0.0'; // Security middleware app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], }, }, })); // CORS configuration app.use(cors({ origin: process.env.CORS_ORIGIN || '*', credentials: process.env.CORS_CREDENTIALS === 'true', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'] })); // Compression middleware app.use(compression()); // Logging middleware app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } })); // Rate limiting const limiter = rateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100, // limit each IP to 100 requests per windowMs message: { error: 'Too many requests from this IP, please try again later.', retryAfter: Math.ceil((parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000) / 1000) }, standardHeaders: true, legacyHeaders: false, }); app.use(limiter); // Body parsing middleware app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Request validation middleware app.use(requestValidator); // Health check middleware app.use(healthCheck); // Routes app.use('/api/recommendations', recommendationRoutes); app.use('/health', healthRoutes); // Root endpoint app.get('/', (req, res) => { res.json({ message: 'Unison - Unified Tech Stack Recommendation Service', version: '1.0.0', status: 'operational', timestamp: new Date().toISOString(), endpoints: { health: '/health', recommendations: '/api/recommendations', unified: '/api/recommendations/unified' }, services: { techStackSelector: process.env.TECH_STACK_SELECTOR_URL || 'http://pipeline_tech_stack_selector:8002', templateManager: process.env.TEMPLATE_MANAGER_URL || 'http://pipeline_template_manager:8009' } }); }); // 404 handler app.use('*', (req, res) => { res.status(404).json({ error: 'Not Found', message: `Route ${req.originalUrl} not found`, availableEndpoints: [ 'GET /', 'GET /health', 'POST /api/recommendations/unified' ] }); }); // Error handling middleware (must be last) app.use(errorHandler); // Start server const server = app.listen(PORT, HOST, () => { logger.info(`🚀 Unison service started on ${HOST}:${PORT}`); logger.info(`📊 Environment: ${process.env.NODE_ENV || 'development'}`); logger.info(`🔗 Tech Stack Selector: ${process.env.TECH_STACK_SELECTOR_URL || 'http://pipeline_tech_stack_selector:8002'}`); logger.info(`🔗 Template Manager: ${process.env.TEMPLATE_MANAGER_URL || 'http://pipeline_template_manager:8009'}`); }); // Graceful shutdown process.on('SIGTERM', () => { logger.info('SIGTERM received, shutting down gracefully'); server.close(() => { logger.info('Process terminated'); process.exit(0); }); }); process.on('SIGINT', () => { logger.info('SIGINT received, shutting down gracefully'); server.close(() => { logger.info('Process terminated'); process.exit(0); }); }); module.exports = app;