Support uploading structured JSON to create courses, classes, and students without wiping existing data, and merge user name fields while adding gender and registration details from source sheets.
139 lines
5.6 KiB
JavaScript
139 lines
5.6 KiB
JavaScript
// /app.js — Main Express Application Entry Point
|
|
'use strict';
|
|
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
const helmet = require('helmet');
|
|
const rateLimit = require('express-rate-limit');
|
|
const mongoose = require('mongoose');
|
|
|
|
const config = require('./config/config');
|
|
const connectDB = require('./config/db');
|
|
const logger = require('./utils/logger');
|
|
const notFoundHandler = require('./middlewares/notFoundHandler');
|
|
const globalErrorHandler = require('./middlewares/globalErrorHandler');
|
|
|
|
// ── Route imports ─────────────────────────────────────────────────────────────
|
|
const authRoutes = require('./components/auth/authRoutes');
|
|
const userRoutes = require('./components/users/userRoutes');
|
|
const professorRoutes = require('./components/professors/professorRoutes');
|
|
const courseRoutes = require('./components/courses/courseRoutes');
|
|
const classRoutes = require('./components/classes/classRoutes');
|
|
const sessionRoutes = require('./components/sessions/sessionRoutes');
|
|
const paymentRoutes = require('./components/payments/paymentRoutes');
|
|
const roleRoutes = require('./components/roles/roleRoutes');
|
|
const notificationRoutes = require('./components/notifications/notificationRoutes');
|
|
const certificateRoutes = require('./components/certificates/certificateRoutes');
|
|
const fileRoutes = require('./components/files/fileRoutes');
|
|
const dashboardRoutes = require('./components/dashboard/dashboardRoutes');
|
|
const activityLogRoutes = require('./components/activityLogs/activityLogRoutes');
|
|
const contactInquiryRoutes = require('./components/contactInquiries/contactInquiryRoutes');
|
|
const activityLogger = require('./middlewares/activityLogger');
|
|
|
|
const app = express();
|
|
|
|
// ── Security & Middleware ─────────────────────────────────────────────────────
|
|
app.use(helmet());
|
|
app.use(cors({
|
|
origin: config.CORS_ORIGIN,
|
|
credentials: true
|
|
}));
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
app.use(activityLogger);
|
|
|
|
// Global rate limiter
|
|
const limiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 300,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
message: { status: 'error', message: 'Too many requests. Please try again later.' }
|
|
});
|
|
app.use('/api', limiter);
|
|
|
|
// ── Routes ────────────────────────────────────────────────────────────────────
|
|
app.get('/api/health', (req, res) => {
|
|
const dbStates = {
|
|
0: 'disconnected',
|
|
1: 'connected',
|
|
2: 'connecting',
|
|
3: 'disconnecting',
|
|
99: 'uninitialized'
|
|
};
|
|
|
|
const dbStateCode = mongoose.connection.readyState;
|
|
const dbStatus = dbStates[dbStateCode] || 'unknown';
|
|
const isHealthy = dbStateCode === 1;
|
|
|
|
res.status(isHealthy ? 200 : 503).json({
|
|
status: isHealthy ? 'healthy' : 'unhealthy',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime(),
|
|
services: {
|
|
database: dbStatus
|
|
},
|
|
environment: config.NODE_ENV
|
|
});
|
|
});
|
|
|
|
const seedDatabase = require('./seed');
|
|
const seedRoutes = require('./components/seed/seedRoutes');
|
|
const dataImportRoutes = require('./components/dataImport/dataImportRoutes');
|
|
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/users', userRoutes);
|
|
app.use('/api/professors', professorRoutes);
|
|
app.use('/api/courses', courseRoutes);
|
|
app.use('/api/classes', classRoutes);
|
|
app.use('/api/sessions', sessionRoutes);
|
|
app.use('/api/payments', paymentRoutes);
|
|
app.use('/api/roles', roleRoutes);
|
|
app.use('/api/notifications', notificationRoutes);
|
|
app.use('/api/certificates', certificateRoutes);
|
|
app.use('/api/files', fileRoutes);
|
|
app.use('/api/dashboard', dashboardRoutes);
|
|
app.use('/api/activity-logs', activityLogRoutes);
|
|
app.use('/api/contact-inquiries', contactInquiryRoutes);
|
|
app.use('/api/seed', seedRoutes);
|
|
app.use('/api/data-import', dataImportRoutes);
|
|
|
|
// ── Error Handlers ────────────────────────────────────────────────────────────
|
|
app.use(notFoundHandler);
|
|
app.use(globalErrorHandler);
|
|
|
|
// ── Server startup ────────────────────────────────────────────────────────────
|
|
const PORT = config.PORT || 3000;
|
|
|
|
const startServer = async () => {
|
|
await connectDB();
|
|
|
|
if (config.NODE_ENV === 'production') {
|
|
logger.info('Running production bootstrap (roles + SuperAdmin from env)');
|
|
await seedDatabase({ disconnectOnComplete: false });
|
|
}
|
|
|
|
const registerEventListeners = require('./events/eventListeners');
|
|
const { startPaymentReminderJob } = require('./jobs/paymentReminderJob');
|
|
const { startNotificationRetryJob } = require('./jobs/notificationRetryJob');
|
|
const { startTempBucketCleanupJob } = require('./jobs/tempBucketCleanupJob');
|
|
const { startClassReminderJob } = require('./jobs/classReminderJob');
|
|
|
|
registerEventListeners();
|
|
startPaymentReminderJob();
|
|
startNotificationRetryJob();
|
|
startTempBucketCleanupJob();
|
|
startClassReminderJob();
|
|
|
|
app.listen(PORT, () => {
|
|
logger.info(`Gameno API listening on http://localhost:${PORT} [${config.NODE_ENV}]`);
|
|
});
|
|
};
|
|
|
|
startServer().catch(err => {
|
|
logger.error('Failed to start server:', err);
|
|
process.exit(1);
|
|
});
|
|
|
|
module.exports = app;
|