const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); const compression = require('compression'); const morgan = require('morgan'); require('express-async-errors'); // Handle async errors const config = require('./config/config.json'); const { swaggerUi, swaggerSpec } = require('./config/swagger'); // Import configurations const { initializeDatabase } = require('./config/database'); const { setupRelationships } = require('./models'); const { redisClient } = require('./config/redis'); // Import middleware const { errorHandler, notFoundHandler } = require('./middleware/errorHandler'); // Import routes const authRoutes = require('./routes/authRoutes'); const schoolRoutes = require('./routes/schoolRoutes'); const classRoutes = require('./routes/classRoutes'); const academicYearRoutes = require('./routes/academicYearRoutes'); const subjectRoutes = require('./routes/subjectRoutes'); const userRoutes = require('./routes/userRoutes'); const studentRoutes = require('./routes/studentRoutes'); const teacherRoutes = require('./routes/teacherRoutes'); const roomRoutes = require('./routes/roomRoutes'); const attendanceRoutes = require('./routes/attendanceRoutes'); const gradeRoutes = require('./routes/gradeRoutes'); const subscriptionRoutes = require('./routes/subscriptionRoutes'); const trainingRoutes = require('./routes/trainingRoutes'); const parentTaskRoutes = require('./routes/parentTaskRoutes'); const chapterRoutes = require('./routes/chapterRoutes'); const gameRoutes = require('./routes/gameRoutes'); const lessonRoutes = require('./routes/lessonRoutes'); const chapterLessonRoutes = require('./routes/chapterLessonRoutes'); const vocabRoutes = require('./routes/vocabRoutes'); const grammarRoutes = require('./routes/grammarRoutes'); const storyRoutes = require('./routes/storyRoutes'); const learningContentRoutes = require('./routes/learningContentRoutes'); /** * Initialize Express Application */ const app = express(); /** * Security Middleware */ app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], // Allow inline scripts for Swagger UI styleSrc: ["'self'", "'unsafe-inline'"], // Allow inline styles for Swagger UI imgSrc: ["'self'", "data:", "https:", "validator.swagger.io"], // Allow Swagger validator connectSrc: ["'self'"], fontSrc: ["'self'"], objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], }, }, })); app.use(cors({ origin: config.cors.origin, credentials: true, })); /** * Body Parsing Middleware */ app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); /** * Static Files Middleware */ app.use(express.static('public')); /** * Compression Middleware */ app.use(compression()); /** * Logging Middleware */ if (config.server.env === 'development') { app.use(morgan('dev')); } else { app.use(morgan('combined')); } /** * Health Check Endpoint */ app.get('/health', async (req, res) => { try { // Check database connection await require('./config/database').testConnection(); // Check Redis connection await redisClient.ping(); res.json({ success: true, status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), environment: config.server.env, services: { database: 'connected', redis: 'connected', }, }); } catch (error) { res.status(503).json({ success: false, status: 'unhealthy', error: error.message, }); } }); /** * API Info Endpoint */ app.get('/api', (req, res) => { res.json({ success: true, name: 'Sena School Management API', version: '1.0.0', description: 'API for managing 200 schools with Redis caching and BullMQ job processing', endpoints: { auth: '/api/auth', schools: '/api/schools', classes: '/api/classes', academicYears: '/api/academic-years', subjects: '/api/subjects', users: '/api/users', students: '/api/students', teachers: '/api/teachers', rooms: '/api/rooms', attendance: '/api/attendance', grades: '/api/grades', subscriptions: '/api/subscriptions', training: '/api/training', parentTasks: '/api/parent-tasks', chapters: '/api/chapters', lessons: '/api/lessons', games: '/api/games', vocab: '/api/vocab', }, documentation: '/api-docs', }); }); /** * Swagger API Documentation */ app.use('/api-docs', swaggerUi.serve); app.get('/api-docs', swaggerUi.setup(swaggerSpec, { customCss: '.swagger-ui .topbar { display: none }', customSiteTitle: 'SENA API Documentation', customfavIcon: '/favicon.ico', swaggerOptions: { persistAuthorization: true, displayRequestDuration: true, filter: true, syntaxHighlight: { theme: 'monokai', }, }, })); /** * Swagger JSON endpoint */ app.get('/api-docs.json', (req, res) => { res.setHeader('Content-Type', 'application/json'); res.send(swaggerSpec); }); /** * API Routes */ app.use('/api/auth', authRoutes); app.use('/api/schools', schoolRoutes); app.use('/api/classes', classRoutes); app.use('/api/academic-years', academicYearRoutes); app.use('/api/subjects', subjectRoutes); app.use('/api/users', userRoutes); app.use('/api/students', studentRoutes); app.use('/api/teachers', teacherRoutes); app.use('/api/rooms', roomRoutes); app.use('/api/attendance', attendanceRoutes); app.use('/api/grades', gradeRoutes); app.use('/api/subscriptions', subscriptionRoutes); app.use('/api/training', trainingRoutes); app.use('/api/parent-tasks', parentTaskRoutes); app.use('/api/chapters', chapterRoutes); app.use('/api/chapters', chapterLessonRoutes); // Nested route: /api/chapters/:id/lessons app.use('/api/games', gameRoutes); app.use('/api/lessons', lessonRoutes); app.use('/api/vocab', vocabRoutes); app.use('/api/grammar', grammarRoutes); app.use('/api/stories', storyRoutes); app.use('/api/learning-content', learningContentRoutes); /** * Queue Status Endpoint */ app.get('/api/queues/status', async (req, res) => { try { const { getQueueMetrics, QueueNames } = require('./config/bullmq'); const metrics = await Promise.all( Object.values(QueueNames).map(queueName => getQueueMetrics(queueName)) ); res.json({ success: true, data: metrics, }); } catch (error) { res.status(500).json({ success: false, error: error.message, }); } }); /** * Cache Stats Endpoint */ app.get('/api/cache/stats', async (req, res) => { try { const info = await redisClient.info('stats'); const dbSize = await redisClient.dbsize(); res.json({ success: true, data: { dbSize, stats: info, }, }); } catch (error) { res.status(500).json({ success: false, error: error.message, }); } }); /** * 404 Handler */ app.use(notFoundHandler); /** * Global Error Handler */ app.use(errorHandler); /** * Initialize Application */ const initializeApp = async () => { try { // Initialize database connection await initializeDatabase(); // Setup model relationships setupRelationships(); console.log('✅ Application initialized successfully'); } catch (error) { console.error('❌ Application initialization failed:', error.message); throw error; } }; module.exports = { app, initializeApp };