144 lines
3.5 KiB
JavaScript
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}`);
|
|
});
|