mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
138 lines
6.3 KiB
JavaScript
138 lines
6.3 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* File Name: server.js
|
|
* Type of Program: Application Entry Point
|
|
* Description: Bootstraps the Express application.
|
|
* Registers global middleware (CORS, rate-limit, cookie-parser,
|
|
* session, Passport) and mounts all route groups.
|
|
* Syncs Sequelize models to the database on startup.
|
|
*
|
|
* Author: rgrgogu
|
|
* Date Created: Oct. 6, 2025
|
|
***********************************************************************************************************************************************************************
|
|
* HOW TO RUN:
|
|
* 1. cp .env.example .env (fill in your values)
|
|
* 2. npm install
|
|
* 3. npm run dev (development)
|
|
* npm start (production)
|
|
***********************************************************************************************************************************************************************/
|
|
require('dotenv').config();
|
|
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
const cookieParser = require('cookie-parser');
|
|
const session = require('express-session');
|
|
const passport = require('./config/passport.config');
|
|
const sequelize = require('./config/db.config');
|
|
|
|
// ── Middleware ──────────────────────────────────────────────────────────────────
|
|
const { globalLimiter } = require('./middleware/rateLimiter.middleware');
|
|
const { csrfErrorHandler } = require('./middleware/csrf.middleware');
|
|
|
|
// ── Routes ─────────────────────────────────────────────────────────────────────
|
|
const authRoutes = require('./routes/auth.routes');
|
|
const clientRoutes = require('./routes/client/client.routes');
|
|
const staffRoutes = require('./routes/staff/staff.routes');
|
|
const adminRoutes = require('./routes/admin/admin.routes');
|
|
const courseRoutes = require('./routes/admin/courses.routes');
|
|
|
|
// ── Models (ensure associations are loaded) ────────────────────────────────────
|
|
require('./models/users/users.mdl');
|
|
require('./models/users/user_sessions.mdl');
|
|
require('./models/users/user_groups.mdl');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
// Global Middleware Stack
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
app.set('trust proxy', 1); // Required for rate-limiter behind proxies/load balancers
|
|
|
|
const allowedOrigins = (process.env.ALLOWED_ORIGINS || process.env.APP_URL || '*').split(',');
|
|
|
|
app.use(cors({
|
|
origin: (origin, callback) => {
|
|
if (!origin || allowedOrigins.includes(origin)) return callback(null, true);
|
|
callback(new Error(`CORS: origin ${origin} not allowed`));
|
|
},
|
|
credentials: true,
|
|
preflightContinue: false, // cors() handles OPTIONS and stops — don't pass to next()
|
|
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
allowedHeaders: [
|
|
'Content-Type',
|
|
'Authorization',
|
|
'Accept',
|
|
'Origin',
|
|
'X-Requested-With',
|
|
'csrf-token',
|
|
'x-csrf-token',
|
|
'CSRF-Token',
|
|
'X-CSRF-Token',
|
|
],
|
|
optionsSuccessStatus: 204,
|
|
maxAge: 86400,
|
|
}));
|
|
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
app.use(cookieParser());
|
|
|
|
// Session — used only by csurf; JWT handles auth state
|
|
app.use(session({
|
|
secret: process.env.SESSION_SECRET || 'change-me',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, sameSite: 'strict' },
|
|
}));
|
|
|
|
app.use(passport.initialize());
|
|
|
|
// Global rate limiter (1000 req / 15 min)
|
|
app.use(globalLimiter);
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
// Routes
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/client', clientRoutes);
|
|
app.use('/api/staff', staffRoutes);
|
|
app.use('/api/admin', adminRoutes);
|
|
app.use('/api/admin/courses', courseRoutes);
|
|
|
|
// Health check
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({ status: 'ok', timestamp: new Date().toISOString(), env: process.env.NODE_ENV });
|
|
});
|
|
|
|
// 404 handler
|
|
app.use((req, res) => {
|
|
res.status(404).json({ status: 'error', message: 'Route not found.' });
|
|
});
|
|
|
|
// CSRF error handler (must be after routes)
|
|
app.use(csrfErrorHandler);
|
|
|
|
// Global error handler
|
|
app.use((err, req, res, next) => {
|
|
console.error('[UNHANDLED ERROR]', err);
|
|
res.status(500).json({ status: 'error', message: 'Internal server error.' });
|
|
});
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
// Database Sync + Listen
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
(async () => {
|
|
try {
|
|
await sequelize.authenticate();
|
|
console.log('✅ Database connected.');
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`🚀 STARR Server running on http://localhost:${PORT}`);
|
|
console.log(` Environment : ${process.env.NODE_ENV}`);
|
|
});
|
|
} catch (err) {
|
|
console.error('❌ Failed to start server:', err);
|
|
process.exit(1);
|
|
}
|
|
})();
|