113 lines
2.9 KiB
JavaScript
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);
|
|
});
|
|
}
|