Files
clawborn/server/stats-server.mjs
2026-03-17 17:53:14 +08:00

144 lines
3.5 KiB
JavaScript

import { createServer } from "node:http";
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const statsFilePath = path.resolve(__dirname, "../data/generation-stats.json");
const port = Number(process.env.PORT || 3001);
const defaultStats = {
total: 0,
byTemplate: {},
updatedAt: null,
};
let writeQueue = Promise.resolve();
async function ensureStatsFile() {
await fs.mkdir(path.dirname(statsFilePath), { recursive: true });
try {
await fs.access(statsFilePath);
} catch {
await fs.writeFile(statsFilePath, JSON.stringify(defaultStats, null, 2), "utf8");
}
}
async function readStats() {
await ensureStatsFile();
const raw = await fs.readFile(statsFilePath, "utf8");
try {
const parsed = JSON.parse(raw);
return {
total: Number(parsed.total || 0),
byTemplate: parsed.byTemplate && typeof parsed.byTemplate === "object" ? parsed.byTemplate : {},
updatedAt: parsed.updatedAt || null,
};
} catch {
return { ...defaultStats };
}
}
function writeStats(nextStats) {
writeQueue = writeQueue.then(() =>
fs.writeFile(statsFilePath, JSON.stringify(nextStats, null, 2), "utf8"),
);
return writeQueue;
}
function sendJson(res, statusCode, payload) {
res.writeHead(statusCode, {
"Content-Type": "application/json; charset=utf-8",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
});
res.end(JSON.stringify(payload));
}
function readRequestBody(req) {
return new Promise((resolve, reject) => {
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
if (!body) {
resolve({});
return;
}
try {
resolve(JSON.parse(body));
} catch (error) {
reject(error);
}
});
req.on("error", reject);
});
}
const server = createServer(async (req, res) => {
if (!req.url || !req.method) {
sendJson(res, 400, { error: "Invalid request." });
return;
}
if (req.method === "OPTIONS") {
sendJson(res, 204, {});
return;
}
const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
try {
if (req.method === "GET" && url.pathname === "/api/stats") {
const stats = await readStats();
sendJson(res, 200, stats);
return;
}
if (req.method === "POST" && url.pathname === "/api/generate-count") {
const body = await readRequestBody(req);
const templateId = typeof body.templateId === "string" && body.templateId.trim()
? body.templateId.trim()
: "unknown";
const stats = await readStats();
const nextStats = {
total: stats.total + 1,
byTemplate: {
...stats.byTemplate,
[templateId]: Number(stats.byTemplate[templateId] || 0) + 1,
},
updatedAt: new Date().toISOString(),
};
await writeStats(nextStats);
sendJson(res, 200, nextStats);
return;
}
if (req.method === "GET" && url.pathname === "/api/health") {
sendJson(res, 200, { ok: true });
return;
}
sendJson(res, 404, { error: "Not found." });
} catch (error) {
sendJson(res, 500, {
error: error instanceof Error ? error.message : "Internal server error.",
});
}
});
server.listen(port, async () => {
await ensureStatsFile();
console.log(`Stats server listening on http://localhost:${port}`);
});