require("dotenv").config(); const express = require("express"); const cors = require("cors"); const helmet = require("helmet"); const morgan = require("morgan"); const rateLimit = require("express-rate-limit"); const { query } = require("./db"); const { cacheMiddleware, invalidationMiddleware, getCacheStats, startCacheCleanup, } = require("./middleware/cache"); // Import routes const authRoutes = require("./routes/auth"); const songsRoutes = require("./routes/songs"); const listsRoutes = require("./routes/lists"); const profilesRoutes = require("./routes/profiles"); const adminRoutes = require("./routes/admin"); const app = express(); const PORT = process.env.PORT || 8080; // Start cache cleanup (every 60 seconds) startCacheCleanup(60000); // Security middleware app.use( helmet({ contentSecurityPolicy: false, // Disable for development }), ); // CORS configuration const allowedOrigins = [ "http://localhost:5100", "http://localhost:3000", "https://houseofprayer.ddns.net", "http://houseofprayer.ddns.net", ]; app.use( cors({ origin: (origin, callback) => { // Allow requests with no origin (like mobile apps or curl requests) if (!origin) return callback(null, true); if (allowedOrigins.includes(origin)) { callback(null, true); } else { // For development, allow all origins callback(null, true); } }, credentials: true, methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization", "If-None-Match"], exposedHeaders: ["ETag", "X-Cache", "Cache-Control"], }), ); // Explicit OPTIONS handler for preflight requests app.options("*", cors()); // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 1000, // Generous limit for development message: { error: "Too many requests, please try again later." }, }); app.use("/api/", limiter); // Body parsing app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true, limit: "10mb" })); // Response caching middleware (applies to GET requests) app.use("/api/", cacheMiddleware()); // Cache invalidation middleware (handles POST, PUT, DELETE) app.use("/api/", invalidationMiddleware); // Logging (compact format for production-like environment) app.use(morgan("dev")); // Health check app.get("/health", (req, res) => { res.json({ status: "ok", timestamp: new Date().toISOString() }); }); // Cache stats endpoint for monitoring app.get("/api/cache-stats", (req, res) => { res.json({ success: true, cache: getCacheStats(), timestamp: new Date().toISOString(), }); }); // Stats endpoint for dashboard app.get("/api/stats", async (req, res) => { try { const [songsResult, profilesResult, listsResult] = await Promise.all([ query("SELECT COUNT(*) as count FROM songs"), query("SELECT COUNT(*) as count FROM profiles"), query("SELECT COUNT(*) as count FROM plans"), ]); res.json({ success: true, stats: { songs: parseInt(songsResult.rows[0].count), profiles: parseInt(profilesResult.rows[0].count), lists: parseInt(listsResult.rows[0].count), }, }); } catch (err) { console.error("Stats error:", err); res.status(500).json({ success: false, message: "Failed to fetch stats" }); } }); // API Routes app.use("/api/auth", authRoutes); app.use("/api/songs", songsRoutes); app.use("/api/lists", listsRoutes); app.use("/api/profiles", profilesRoutes); app.use("/api/admin", adminRoutes); // 404 handler for API routes app.use((req, res) => { res.status(404).json({ error: "Not found" }); }); // Error handler app.use((err, req, res, next) => { console.error("Server error:", err); res.status(500).json({ error: "Internal server error" }); }); // Start server app.listen(PORT, () => { console.log("🚀 Server running on http://localhost:" + PORT); console.log("📊 Health check: http://localhost:" + PORT + "/health"); }); module.exports = app;