Files

150 lines
4.0 KiB
JavaScript

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;