133 lines
3.5 KiB
JavaScript
133 lines
3.5 KiB
JavaScript
import fastifyStatic from "@fastify/static";
|
|
import Fastify from "fastify";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { getConfig } from "./config.js";
|
|
import { closeMongoConnection, getMongoClient, hasMongoConfig } from "./db.js";
|
|
import { deathStatsRoutes } from "./deathStats.js";
|
|
import { visitorRoutes } from "./visitors.js";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const distPath = path.join(root, "dist");
|
|
const isProduction = process.env.NODE_ENV === "production" || process.argv.includes("--production");
|
|
const appConfig = getConfig();
|
|
const port = appConfig.SERVER_PORT;
|
|
const host = appConfig.SERVER_HOST;
|
|
let viteDevServer;
|
|
|
|
const app = Fastify({
|
|
bodyLimit: 16 * 1024,
|
|
});
|
|
|
|
app.addContentTypeParser("*", { parseAs: "string" }, (request, body, done) => {
|
|
done(null, body);
|
|
});
|
|
|
|
app.get("/api/health", async () => {
|
|
return {
|
|
ok: true,
|
|
dbConfigured: hasMongoConfig(),
|
|
};
|
|
});
|
|
|
|
if (!isProduction) {
|
|
const { createServer: createViteServer } = await import("vite");
|
|
viteDevServer = await createViteServer({
|
|
root,
|
|
server: {
|
|
middlewareMode: true,
|
|
hmr: {
|
|
server: app.server,
|
|
},
|
|
},
|
|
appType: "spa",
|
|
});
|
|
}
|
|
|
|
await app.register(visitorRoutes, { prefix: "/api/visitors" });
|
|
await app.register(deathStatsRoutes, { prefix: "/api/death-stats" });
|
|
|
|
if (isProduction) {
|
|
await app.register(fastifyStatic, {
|
|
root: distPath,
|
|
prefix: "/",
|
|
});
|
|
|
|
app.setNotFoundHandler((request, reply) => {
|
|
const acceptsHtml = String(request.headers.accept || "").includes("text/html");
|
|
|
|
if (request.method !== "GET" || request.url.startsWith("/api/") || !acceptsHtml) {
|
|
reply.code(404).send({ error: "not_found" });
|
|
return;
|
|
}
|
|
|
|
reply.sendFile("index.html");
|
|
});
|
|
} else {
|
|
app.setNotFoundHandler((request, reply) => {
|
|
if (request.url.startsWith("/api/")) {
|
|
reply.code(404).send({ error: "not_found" });
|
|
return;
|
|
}
|
|
|
|
reply.hijack();
|
|
viteDevServer.middlewares(request.raw, reply.raw, (error) => {
|
|
if (error) {
|
|
viteDevServer.ssrFixStacktrace(error);
|
|
console.error(error);
|
|
|
|
if (!reply.raw.headersSent) {
|
|
reply.raw.statusCode = 500;
|
|
reply.raw.end(error.stack);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!reply.raw.headersSent && !reply.raw.writableEnded) {
|
|
reply.raw.statusCode = 404;
|
|
reply.raw.end("Not found");
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
app.setErrorHandler((error, request, reply) => {
|
|
const isMissingMongoConfig = error.message.includes("MongoDB configuration");
|
|
const status = isMissingMongoConfig ? 503 : 500;
|
|
|
|
console.error(error);
|
|
reply.code(status).send({
|
|
error: isMissingMongoConfig ? "mongodb_not_configured" : "internal_server_error",
|
|
message: isProduction ? "Arena tracking is unavailable." : error.message,
|
|
});
|
|
});
|
|
|
|
await app.listen({ port, host });
|
|
console.log(`Arena Picker listening on http://localhost:${port}`);
|
|
|
|
if (hasMongoConfig()) {
|
|
getMongoClient()
|
|
.then(() => {
|
|
console.log("MongoDB connection pool is ready.");
|
|
})
|
|
.catch((error) => {
|
|
console.error("MongoDB connection failed. Visitor API will retry on request.", error);
|
|
});
|
|
}
|
|
|
|
["SIGINT", "SIGTERM"].forEach((signal) => {
|
|
process.on(signal, () => {
|
|
shutdown(signal);
|
|
});
|
|
});
|
|
|
|
function shutdown(signal) {
|
|
console.log(`${signal} received. Closing server.`);
|
|
app.close()
|
|
.then(closeMongoConnection)
|
|
.finally(() => {
|
|
process.exit(0);
|
|
});
|
|
}
|