uploaded
This commit is contained in:
121
server/server.js
Normal file
121
server/server.js
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user