scoreaimanage / server.js
PenceZao's picture
合并角色系统并修复登录跳转
1338bd3
/**
* 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();