180 lines
5.6 KiB
JavaScript
180 lines
5.6 KiB
JavaScript
require('dotenv').config();
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
const morgan = require('morgan');
|
|
const swaggerUi = require('swagger-ui-express');
|
|
const swaggerSpec = require('./config/swagger');
|
|
const logger = require('./config/logger');
|
|
const { errorHandler, notFoundHandler } = require('./middleware/errorHandler');
|
|
const { testConnection, getDatabaseStats } = require('./config/db');
|
|
const { validateEnvironment } = require('./tests/validate-env');
|
|
const { isRedisConnected } = require('./config/redis');
|
|
|
|
// Security middleware
|
|
const { helmetConfig, customSecurityHeaders, getCorsOptions } = require('./middleware/security');
|
|
const { sanitizeAll } = require('./middleware/sanitization');
|
|
const { apiLimiter, docsLimiter } = require('./middleware/rateLimiter');
|
|
|
|
// Validate environment configuration on startup
|
|
console.log('\n🔧 Validating environment configuration...');
|
|
const isEnvValid = validateEnvironment();
|
|
if (!isEnvValid) {
|
|
console.error('❌ Environment validation failed. Please fix errors and restart.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const app = express();
|
|
|
|
// Configuration
|
|
const config = require('./config/config');
|
|
const PORT = config.server.port;
|
|
const API_PREFIX = config.server.apiPrefix;
|
|
const NODE_ENV = config.server.nodeEnv;
|
|
|
|
// Trust proxy - important for rate limiting and getting real client IP
|
|
app.set('trust proxy', 1);
|
|
|
|
// Security middleware - order matters!
|
|
// 1. Helmet for security headers
|
|
app.use(helmetConfig);
|
|
|
|
// 2. Custom security headers
|
|
app.use(customSecurityHeaders);
|
|
|
|
// 3. CORS configuration
|
|
app.use(cors(getCorsOptions()));
|
|
|
|
// 4. Body parser middleware
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
|
|
// 5. Input sanitization (NoSQL injection, XSS, HPP)
|
|
app.use(sanitizeAll);
|
|
|
|
// 6. Logging middleware
|
|
if (NODE_ENV === 'development') {
|
|
app.use(morgan('dev', { stream: logger.stream }));
|
|
} else {
|
|
app.use(morgan('combined', { stream: logger.stream }));
|
|
}
|
|
|
|
// 7. Log all requests in development
|
|
if (NODE_ENV === 'development') {
|
|
app.use((req, res, next) => {
|
|
logger.info(`${req.method} ${req.originalUrl}`, {
|
|
ip: req.ip,
|
|
userAgent: req.get('user-agent')
|
|
});
|
|
next();
|
|
});
|
|
}
|
|
|
|
// 8. Global rate limiting for all API routes
|
|
app.use(API_PREFIX, apiLimiter);
|
|
|
|
// API Documentation - with rate limiting
|
|
app.use('/api-docs', docsLimiter, swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
|
|
customCss: '.swagger-ui .topbar { display: none }',
|
|
customSiteTitle: 'Interview Quiz API Documentation',
|
|
customfavIcon: '/favicon.ico'
|
|
}));
|
|
|
|
// Swagger JSON endpoint - with rate limiting
|
|
app.get('/api-docs.json', docsLimiter, (req, res) => {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.send(swaggerSpec);
|
|
});
|
|
|
|
// Health check endpoint
|
|
app.get('/health', async (req, res) => {
|
|
const dbStats = await getDatabaseStats();
|
|
|
|
res.status(200).json({
|
|
status: 'OK',
|
|
message: 'Interview Quiz API is running',
|
|
timestamp: new Date().toISOString(),
|
|
environment: NODE_ENV,
|
|
database: dbStats
|
|
});
|
|
});
|
|
|
|
// API routes
|
|
const authRoutes = require('./routes/auth.routes');
|
|
const guestRoutes = require('./routes/guest.routes');
|
|
const categoryRoutes = require('./routes/category.routes');
|
|
const questionRoutes = require('./routes/question.routes');
|
|
const adminRoutes = require('./routes/admin.routes');
|
|
const quizRoutes = require('./routes/quiz.routes');
|
|
const userRoutes = require('./routes/user.routes');
|
|
|
|
app.use(`${API_PREFIX}/auth`, authRoutes);
|
|
app.use(`${API_PREFIX}/guest`, guestRoutes);
|
|
app.use(`${API_PREFIX}/categories`, categoryRoutes);
|
|
app.use(`${API_PREFIX}/questions`, questionRoutes);
|
|
app.use(`${API_PREFIX}/admin`, adminRoutes);
|
|
app.use(`${API_PREFIX}/quiz`, quizRoutes);
|
|
app.use(`${API_PREFIX}/users`, userRoutes);
|
|
|
|
// Root endpoint
|
|
app.get('/', (req, res) => {
|
|
res.json({
|
|
message: 'Welcome to Interview Quiz API',
|
|
version: '2.0.0',
|
|
documentation: '/api-docs'
|
|
});
|
|
});
|
|
|
|
// 404 handler - must be after all routes
|
|
app.use(notFoundHandler);
|
|
|
|
// Global error handler - must be last
|
|
app.use(errorHandler);
|
|
|
|
// Start server
|
|
app.listen(PORT, async () => {
|
|
logger.info('Server starting up...');
|
|
|
|
const redisStatus = isRedisConnected() ? '✅ Connected' : '⚠️ Not Connected (Optional)';
|
|
|
|
console.log(`
|
|
╔════════════════════════════════════════╗
|
|
║ Interview Quiz API - MySQL Edition ║
|
|
╚════════════════════════════════════════╝
|
|
|
|
🚀 Server running on port ${PORT}
|
|
🌍 Environment: ${NODE_ENV}
|
|
🔗 API Endpoint: http://localhost:${PORT}${API_PREFIX}
|
|
📊 Health Check: http://localhost:${PORT}/health
|
|
📚 API Docs: http://localhost:${PORT}/api-docs
|
|
📝 Logs: backend/logs/
|
|
💾 Cache (Redis): ${redisStatus}
|
|
`);
|
|
|
|
logger.info(`Server started successfully on port ${PORT}`);
|
|
|
|
// Test database connection on startup
|
|
console.log('🔌 Testing database connection...');
|
|
const connected = await testConnection();
|
|
if (!connected) {
|
|
console.warn('⚠️ Warning: Database connection failed. Server is running but database operations will fail.');
|
|
}
|
|
|
|
// Log Redis status
|
|
if (isRedisConnected()) {
|
|
console.log('💾 Redis cache connected and ready');
|
|
logger.info('Redis cache connected');
|
|
} else {
|
|
console.log('⚠️ Redis not connected - caching disabled (optional feature)');
|
|
logger.warn('Redis not connected - caching disabled');
|
|
}
|
|
});
|
|
|
|
// Handle unhandled promise rejections
|
|
process.on('unhandledRejection', (err) => {
|
|
console.error('Unhandled Promise Rejection:', err);
|
|
// Close server & exit process
|
|
process.exit(1);
|
|
});
|
|
|
|
module.exports = app;
|