arena/server/index.js

113 lines
2.9 KiB
JavaScript

import fastifyMiddie from "@fastify/middie";
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 { 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;
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(),
};
});
await app.register(visitorRoutes, { prefix: "/api/visitors" });
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 {
await app.register(fastifyMiddie);
const { createServer: createViteServer } = await import("vite");
const vite = await createViteServer({
root,
server: {
middlewareMode: true,
hmr: {
server: app.server,
},
},
appType: "spa",
});
app.use((request, response, next) => {
if (request.url?.startsWith("/api/")) {
next();
return;
}
vite.middlewares(request, response, next);
});
}
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 ? "Visitor 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);
});
}