286 lines
7.4 KiB
JavaScript
286 lines
7.4 KiB
JavaScript
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 };
|