This commit is contained in:
starified
2026-04-21 22:03:19 -04:00
parent 36e2d11f2e
commit 08bf320b57
4681 changed files with 566542 additions and 0 deletions

121
server/server.js Normal file
View File

@@ -0,0 +1,121 @@
const express = require("express");
const path = require("path");
const cors = require("cors");
const compression = require("compression");
const dotenv = require("dotenv");
const { pool, ensureDatabase } = require("./db");
const playersRoutes = require("./routes/players");
const rankingsRoutes = require("./routes/rankings");
const { sendError } = require("./utils/http");
const { createApiRateLimiters } = require("./middleware/rate-limit");
const {
createCorsMiddleware,
securityHeaders,
enforceHttps,
} = require("./middleware/security");
dotenv.config();
const app = express();
const PORT = Number(process.env.PORT || 3000);
const isDevelopment = process.env.NODE_ENV !== 'production';
const trustProxy = String(process.env.TRUST_PROXY || "false").toLowerCase() === "true";
app.set("trust proxy", trustProxy);
app.set("etag", "strong");
app.use(createCorsMiddleware());
app.use(securityHeaders);
app.use(enforceHttps);
app.use(
compression({
threshold: 1024,
level: 6,
})
);
app.use(
express.json({
limit: "32kb",
strict: true,
type: "application/json",
})
);
const [ipRateLimit, userRateLimit] = createApiRateLimiters({
ipMax: Number(process.env.RATE_LIMIT_IP_MAX || 60),
userMax: Number(process.env.RATE_LIMIT_USER_MAX || 60),
windowMs: Number(process.env.RATE_LIMIT_WINDOW_MS || 60 * 1000),
});
app.use("/api", ipRateLimit, userRateLimit);
app.use("/api", (_req, res, next) => {
res.setHeader("Cache-Control", "no-store");
next();
});
app.use(
express.static(path.join(__dirname, "..", "public"), {
etag: true,
lastModified: true,
maxAge: "1h",
setHeaders(res, filePath) {
const ext = path.extname(filePath).toLowerCase();
if (ext === ".html") {
res.setHeader("Cache-Control", "no-store");
return;
}
if (filePath.includes(`${path.sep}assets${path.sep}`)) {
res.setHeader("Cache-Control", "public, max-age=604800, immutable");
} else {
res.setHeader("Cache-Control", "public, max-age=3600");
}
},
})
);
app.use("/api", playersRoutes);
app.use("/api", rankingsRoutes);
app.get("/api/health", async (_req, res) => {
try {
await pool.query("SELECT 1 AS ok");
return res.json({ ok: true });
} catch (_error) {
return sendError(res, 500, "DB_UNAVAILABLE", "Database connection failed");
}
});
app.use((err, _req, res, _next) => {
if (err && /CORS origin blocked/i.test(String(err.message || ""))) {
return sendError(res, 403, "CORS_BLOCKED", "Origin is not allowed.");
}
const isProd = String(process.env.NODE_ENV || "").toLowerCase() === "production";
if (!isProd) {
console.error("Unhandled error:", err);
} else {
console.error("Unhandled error:", err?.name || "UnknownError");
}
return sendError(res, 500, "INTERNAL_ERROR", "Internal server error");
});
async function startServer() {
await ensureDatabase();
app.listen(PORT, () => {
console.log(`EaglerTiers API running on http://localhost:${PORT}`);
console.log(`Environment: ${isDevelopment ? 'development' : 'production'}`);
});
}
startServer().catch((error) => {
console.error("Failed to start API:", error?.message || error);
process.exit(1);
});