150 lines
4.0 KiB
JavaScript
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;
|