require("dotenv").config(); const express = require("express"); const path = require("path"); const fs = require("fs"); // Added for fs operations const pool = require("./src/config/database"); // Changed to pg pool const helmet = require("helmet"); const session = require("express-session"); const passport = require("./src/config/passport"); const logger = require("./config/logger"); // Corrected logger path back to original const errorHandler = require("./middleware/errorHandler"); const { connectRedis, closeRedis } = require("./src/config/redis"); // Import routes const publicRoutes = require("./src/routes/public"); const authRoutes = require("./src/routes/auth"); const dashboardRoutes = require("./src/routes/dashboard"); const apiV1Routes = require("./src/routes/api_v1"); const app = express(); const PORT = process.env.PORT || 3000; // Function to initialize the database with PostgreSQL async function initializeDatabase() { try { // Check if a key table exists (e.g., users) to see if DB is initialized await pool.query("SELECT 1 FROM users LIMIT 1"); logger.info("Database tables appear to exist. Skipping initialization."); } catch (tableCheckError) { // Specific error code for undefined_table in PostgreSQL is '42P01' if (tableCheckError.code === "42P01") { logger.info( "Users table not found, attempting to initialize database..." ); try { const initSql = fs.readFileSync( path.resolve(__dirname, "init.sql"), "utf8" ); // Execute the entire init.sql script. // pg library can usually handle multi-statement queries if separated by semicolons. await pool.query(initSql); logger.info("Database initialized successfully from init.sql."); } catch (initError) { logger.error("Failed to initialize database with init.sql:", initError); process.exit(1); // Exit if DB initialization fails } } else { // Another error occurred during the table check logger.error("Error checking for users table:", tableCheckError); process.exit(1); // Exit on other DB errors during startup } } } // Initialize Redis connection and Database async function initializeApp() { // Initialize Redis first, but don't block on failure connectRedis().catch(() => { logger.warn( "Redis connection failed, continuing with in-memory rate limiting" ); }); try { await initializeDatabase(); // Initialize PostgreSQL database } catch (error) { logger.error("Failed to initialize database:", error); process.exit(1); // Exit if DB initialization fails } // Middleware app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], }, }, }) ); app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true, limit: "10mb" })); // Session configuration (for development only, use Redis in production) app.use( session({ secret: process.env.SESSION_SECRET || "fallback-secret-change-in-production", resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === "production", httpOnly: true, maxAge: 24 * 60 * 60 * 1000, // 24 hours }, }) ); // Initialize Passport app.use(passport.initialize()); app.use(passport.session()); // Set view engine app.set("view engine", "ejs"); // API Routes app.use("/api/auth", authRoutes); // API V1 Routes app.use("/api/v1", apiV1Routes); // User Dashboard Routes app.use("/dashboard", dashboardRoutes); // Existing routes (maintaining backward compatibility) app.use("/", publicRoutes); // Health check endpoint app.get("/health", (req, res) => { res.json({ status: "healthy", timestamp: new Date().toISOString(), version: "1.0.0", }); }); // Global error handler - should be the last middleware app.use(errorHandler); // 404 handler app.use((req, res) => { logger.warn( `404 - Endpoint not found: ${req.originalUrl} - Method: ${req.method} - IP: ${req.ip}` ); res.status(404).json({ error: { message: "Endpoint not found", code: "NOT_FOUND", }, }); }); // Start server app.listen(PORT, () => { logger.info(`Server running on http://localhost:${PORT}`); // Environment checks if (!process.env.JWT_SECRET) { logger.warn( "WARNING: JWT_SECRET not set. Authentication will not work properly." ); } if (process.env.NTFY_ENABLED === "true" && process.env.NTFY_TOPIC_URL) { logger.info( `Ntfy notifications enabled for topic: ${process.env.NTFY_TOPIC_URL}` ); } else { logger.info("Ntfy notifications disabled or topic not configured."); } // Start cleanup of expired sessions every hour setInterval( () => { const jwtService = require("./src/services/jwtService"); jwtService.cleanupExpiredSessions(); }, 60 * 60 * 1000 ); }); // Graceful shutdown process.on("SIGINT", async () => { logger.info("Received SIGINT, shutting down gracefully..."); await closeRedis(); process.exit(0); }); process.on("SIGTERM", async () => { logger.info("Received SIGTERM, shutting down gracefully..."); await closeRedis(); process.exit(0); }); } // Initialize the application initializeApp().catch((error) => { logger.error("Failed to initialize application:", error); process.exit(1); }); module.exports = app;