Files
sena_db_api_layer/app.js
silverpro89 b34bd7a4e3
All checks were successful
Deploy to Production / deploy (push) Successful in 20s
update clear cache
2026-01-28 19:54:59 +07:00

302 lines
8.1 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 gameTypeRoutes = require('./routes/gameTypeRoutes');
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');
const uploadRoutes = require('./routes/uploadRoutes');
/**
* 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 with CORS
*/
app.use((req, res, next) => {
// Apply CORS headers for static files (images, media, etc.)
if (req.path.match(/\.(jpg|jpeg|png|gif|svg|webp|ico|mp3|mp4|wav|pdf)$/i)) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
}
next();
});
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',
gameTypes: '/api/game-types',
vocab: '/api/vocab',
upload: '/api/upload',
},
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/game-types', gameTypeRoutes);
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);
app.use('/api/upload', uploadRoutes);
/**
* 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 };