/** * ScoreAIManage Server Entry Point * Production-ready server for Render deployment */ const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const compression = require('compression'); const rateLimit = require('express-rate-limit'); // Import PostgreSQL database const { getDatabase } = require('./lib/database'); let db = null; const initializeDatabase = async () => { try { db = getDatabase(); await db.connect(); console.log('🗄️ PostgreSQL database connected successfully'); } catch (error) { console.error('❌ Failed to connect to PostgreSQL database:', error.message); throw error; } }; const Logger = { info: (message, meta = {}) => console.log(`[INFO] ${message}`, meta), error: (message, error) => console.error(`[ERROR] ${message}`, error), warn: (message, meta = {}) => console.log(`[WARN] ${message}`, meta), }; // Load environment variables require('dotenv').config(); const app = express(); const PORT = process.env.PORT || 10000; // Security middleware app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], }, }, })); // CORS configuration app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['https://scoreaimanage.onrender.com'], credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], })); // Compression middleware app.use(compression()); // Rate limiting const limiter = rateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs message: { error: 'Too many requests from this IP, please try again later.', }, }); app.use('/api/', limiter); // Body parsing middleware app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Request logging app.use((req, res, next) => { Logger.info(`${req.method} ${req.path}`, { ip: req.ip, userAgent: req.get('User-Agent'), timestamp: new Date().toISOString(), }); next(); }); // Health check endpoint app.get('/api/health', async (req, res) => { try { // Check database connection const health = await db.healthCheck(); res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString(), version: require('./package.json').version, environment: process.env.NODE_ENV, database: health, uptime: process.uptime(), memory: process.memoryUsage(), }); } catch (error) { Logger.error('Health check failed', error); res.status(503).json({ status: 'unhealthy', timestamp: new Date().toISOString(), error: error.message, }); } }); // API routes registration with logging Logger.info('Registering API routes...'); app.use('/api/auth', (req, res, next) => { Logger.info(`[ROUTER] Auth route accessed: ${req.method} ${req.path}`); next(); }, require('./routes/auth')); app.use('/api/students', (req, res, next) => { Logger.info(`[ROUTER] Students route accessed: ${req.method} ${req.path}`); next(); }, require('./routes/students')); app.use('/api/classes', (req, res, next) => { Logger.info(`[ROUTER] Classes route accessed: ${req.method} ${req.path}`); next(); }, require('./routes/classes')); app.use('/api/subjects', (req, res, next) => { Logger.info(`[ROUTER] Subjects route accessed: ${req.method} ${req.path}`); next(); }, require('./routes/subjects')); app.use('/api/grades', (req, res, next) => { Logger.info(`[ROUTER] Grades route accessed: ${req.method} ${req.path}`); next(); }, require('./routes/grades')); app.use('/api/exams', (req, res, next) => { Logger.info(`[ROUTER] Exams route accessed: ${req.method} ${req.path}`); next(); }, require('./routes/exams')); Logger.info('All API routes registered successfully'); // API documentation endpoint with logging app.get('/api/docs', (req, res) => { Logger.info(`[DOCS] API documentation accessed`, { ip: req.ip, userAgent: req.get('User-Agent') }); res.json({ name: 'ScoreAIManage API', version: require('./package.json').version, description: '学生成绩管理系统 API', endpoints: { health: '/api/health', auth: '/api/auth/*', students: '/api/students/*', classes: '/api/classes/*', subjects: '/api/subjects/*', grades: '/api/grades/*', exams: '/api/exams/*', }, documentation: 'https://scoreaimanage.onrender.com/api/docs', }); }); // 404 handler with logging app.use('*', (req, res) => { Logger.warn(`[404] Route not found: ${req.method} ${req.originalUrl}`, { ip: req.ip, userAgent: req.get('User-Agent') }); res.status(404).json({ error: 'Not Found', message: `Route ${req.originalUrl} not found`, timestamp: new Date().toISOString(), }); }); // Global error handler app.use((error, req, res, next) => { Logger.error('Unhandled error', error); res.status(error.status || 500).json({ error: process.env.NODE_ENV === 'production' ? 'Internal Server Error' : error.message, timestamp: new Date().toISOString(), ...(process.env.NODE_ENV !== 'production' && { stack: error.stack }), }); }); // Graceful shutdown process.on('SIGTERM', async () => { Logger.info('SIGTERM received, shutting down gracefully'); try { if (db) await db.disconnect(); Logger.info('Database connections closed'); } catch (error) { Logger.error('Error closing database connections', error); } process.exit(0); }); process.on('SIGINT', async () => { Logger.info('SIGINT received, shutting down gracefully'); try { if (db) await db.disconnect(); Logger.info('Database connections closed'); } catch (error) { Logger.error('Error closing database connections', error); } process.exit(0); }); // Start server async function startServer() { try { // Initialize database Logger.info('Initializing database...'); await initializeDatabase(); // Start listening app.listen(PORT, () => { Logger.info(`🚀 ScoreAIManage server started successfully`, { port: PORT, environment: process.env.NODE_ENV, url: `https://scoreaimanage.onrender.com`, healthCheck: `https://scoreaimanage.onrender.com/api/health`, }); }); } catch (error) { Logger.error('Failed to start server', error); process.exit(1); } } // Start the server startServer();