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); }); }