// Load environment-specific config const env = process.env.NODE_ENV || "dev"; const envFile = `.env.${env}`; require("dotenv").config({ path: envFile, }); const express = require("express"); const http = require("http"); const { Server } = require("socket.io"); const cors = require("cors"); const bodyParser = require("body-parser"); const path = require("path"); const helmet = require("helmet"); const { sequelize } = require("./models"); // Import from models/index.js to ensure associations are loaded const { cookieParser } = require("./middleware/csrf"); const logger = require("./utils/logger"); const morgan = require("morgan"); const authRoutes = require("./routes/auth"); const { router: alphaRoutes } = require("./routes/alpha"); const userRoutes = require("./routes/users"); const itemRoutes = require("./routes/items"); const rentalRoutes = require("./routes/rentals"); const messageRoutes = require("./routes/messages"); const forumRoutes = require("./routes/forum"); const stripeRoutes = require("./routes/stripe"); const stripeWebhookRoutes = require("./routes/stripeWebhooks"); const mapsRoutes = require("./routes/maps"); const conditionCheckRoutes = require("./routes/conditionChecks"); const feedbackRoutes = require("./routes/feedback"); const uploadRoutes = require("./routes/upload"); const healthRoutes = require("./routes/health"); const emailServices = require("./services/email"); const s3Service = require("./services/s3Service"); // Socket.io setup const { authenticateSocket } = require("./sockets/socketAuth"); const { initializeMessageSocket } = require("./sockets/messageSocket"); const app = express(); const server = http.createServer(app); // Initialize Socket.io with CORS const io = new Server(server, { cors: { origin: process.env.FRONTEND_URL || "http://localhost:3000", credentials: true, methods: ["GET", "POST"], }, }); // Apply socket authentication middleware io.use(authenticateSocket); // Initialize message socket handlers initializeMessageSocket(io); // Store io instance in app for use in routes app.set("io", io); // Import security middleware const { enforceHTTPS, securityHeaders, addRequestId, sanitizeError, } = require("./middleware/security"); const { generalLimiter } = require("./middleware/rateLimiter"); const errorLogger = require("./middleware/errorLogger"); const apiLogger = require("./middleware/apiLogger"); const { requireAlphaAccess } = require("./middleware/alphaAccess"); // Apply security middleware app.use(enforceHTTPS); app.use(addRequestId); app.use(securityHeaders); // Security headers with Helmet app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "https://cdn.jsdelivr.net"], fontSrc: ["'self'"], scriptSrc: ["'self'", "https://accounts.google.com"], imgSrc: ["'self'"], connectSrc: ["'self'"], frameSrc: ["'self'", "https://accounts.google.com"], }, }, }) ); // Cookie parser for CSRF app.use(cookieParser); // HTTP request logging app.use(morgan("combined", { stream: logger.stream })); // API request/response logging app.use("/api/", apiLogger); // CORS with security settings (must come BEFORE rate limiter to ensure headers on all responses) app.use( cors({ origin: process.env.FRONTEND_URL || "http://localhost:3000", credentials: true, optionsSuccessStatus: 200, exposedHeaders: ["X-CSRF-Token"], }) ); // General rate limiting for all routes app.use("/api/", generalLimiter); // Body parsing with size limits app.use( bodyParser.json({ limit: "1mb", verify: (req, res, buf) => { // Store raw body for webhook verification req.rawBody = buf; }, }) ); app.use( bodyParser.urlencoded({ extended: true, limit: "1mb", parameterLimit: 100, // Limit number of parameters }) ); // Serve static files from uploads directory with CORS headers app.use( "/uploads", helmet.crossOriginResourcePolicy({ policy: "cross-origin" }), express.static(path.join(__dirname, "uploads")) ); // Health check endpoints (no auth, no rate limiting) app.use("/health", healthRoutes); // Stripe webhooks (no auth, uses signature verification instead) app.use("/api/stripe/webhooks", stripeWebhookRoutes); // Root endpoint app.get("/", (req, res) => { res.json({ message: "Village Share API is running!" }); }); // Public routes (no alpha access required) app.use("/api/alpha", alphaRoutes); app.use("/api/auth", authRoutes); // Auth has its own alpha checks in registration // Protected routes (require alpha access) app.use("/api/users", requireAlphaAccess, userRoutes); app.use("/api/items", requireAlphaAccess, itemRoutes); app.use("/api/rentals", requireAlphaAccess, rentalRoutes); app.use("/api/messages", requireAlphaAccess, messageRoutes); app.use("/api/forum", requireAlphaAccess, forumRoutes); app.use("/api/stripe", requireAlphaAccess, stripeRoutes); app.use("/api/maps", requireAlphaAccess, mapsRoutes); app.use("/api/condition-checks", requireAlphaAccess, conditionCheckRoutes); app.use("/api/feedback", requireAlphaAccess, feedbackRoutes); app.use("/api/upload", requireAlphaAccess, uploadRoutes); // Error handling middleware (must be last) app.use(errorLogger); app.use(sanitizeError); const PORT = process.env.PORT || 5000; const { checkPendingMigrations } = require("./utils/checkMigrations"); sequelize .authenticate() .then(async () => { logger.info("Database connection established successfully"); // Check for pending migrations const pendingMigrations = await checkPendingMigrations(sequelize); if (pendingMigrations.length > 0) { logger.error( `Found ${pendingMigrations.length} pending migration(s). Please run 'npm run db:migrate'`, { pendingMigrations } ); process.exit(1); } logger.info("All migrations are up to date"); // Initialize email services and load templates try { await emailServices.initialize(); logger.info("Email services initialized successfully"); } catch (err) { logger.error("Failed to initialize email services", { error: err.message, stack: err.stack, }); // Fail fast - don't start server if email templates can't load if (env === "prod" || env === "production") { logger.error( "Cannot start server without email services in production" ); process.exit(1); } else { logger.warn( "Email services failed to initialize - continuing in dev mode" ); } } // Initialize S3 service for image uploads try { s3Service.initialize(); logger.info("S3 service initialized successfully"); } catch (err) { logger.error("Failed to initialize S3 service", { error: err.message, stack: err.stack, }); logger.error("Cannot start server without S3 service in production"); process.exit(1); } server.listen(PORT, () => { logger.info(`Server is running on port ${PORT}`, { port: PORT, environment: env, }); logger.info("Socket.io server initialized"); }); }) .catch((err) => { logger.error("Unable to connect to database", { error: err.message, stack: err.stack, }); process.exit(1); });