require("dotenv").config(); const express = require("express"); const path = require("path"); const fs = require("fs"); // Added for fs operations const db = require("./src/config/database"); // SQLite db instance const helmet = require("helmet"); const session = require("express-session"); const passport = require("./src/config/passport"); const logger = require("./config/logger"); 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 async function initializeDatabase() { const dbPath = path.resolve(__dirname, "formies.sqlite"); const dbExists = fs.existsSync(dbPath); if (!dbExists) { logger.info("Database file not found, creating and initializing..."); try { // The 'db' instance from './src/config/database' should already create the file. // Now, run the init.sql script. const initSql = fs.readFileSync( path.resolve(__dirname, "init.sql"), "utf8" ); // SQLite driver's `exec` method can run multiple statements await new Promise((resolve, reject) => { db.exec(initSql, (err) => { if (err) { logger.error("Failed to initialize database:", err); return reject(err); } logger.info("Database initialized successfully."); resolve(); }); }); } catch (error) { logger.error("Error during database initialization:", error); process.exit(1); // Exit if DB initialization fails } } else { logger.info("Database file found."); } } // 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 SQLite 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); });