import { getConfig } from "./config.js"; import { getDb } from "./db.js"; const DEFAULT_DAILY_COLLECTION_NAME = "daily_death_stats"; const SPECIES_KEYS = ["human", "orc", "skeleton", "slime", "wolf", "bear"]; let dailyIndexesReady; export async function deathStatsRoutes(fastify) { fastify.get("/today", async () => { return getTodayDeathStats(); }); fastify.post("/today", async (request) => { const payload = parseJsonBody(request.body); const deathsBySpecies = normalizeDeathCounts(payload.deathsBySpecies); const totalDeaths = totalCount(deathsBySpecies); const now = new Date(); const date = dayKey(now); const collection = await getDailyCollection(); await ensureDailyDeathStatsIndex(collection); if (totalDeaths === 0) { const today = await collection.findOne({ _id: date }); return { saved: false, today: formatDailyStats(today, date), }; } await collection.updateOne( { _id: date }, { $setOnInsert: { _id: date, date, createdAt: now, }, $set: { updatedAt: now, }, $inc: { battles: 1, totalDeaths, ...speciesIncrements(deathsBySpecies), }, }, { upsert: true }, ); const today = await collection.findOne({ _id: date }); return { saved: true, today: formatDailyStats(today, date), }; }); } async function getTodayDeathStats() { const collection = await getDailyCollection(); await ensureDailyDeathStatsIndex(collection); const date = dayKey(new Date()); const today = await collection.findOne({ _id: date }); return formatDailyStats(today, date); } async function getDailyCollection() { const db = await getDb(); return db.collection( getConfig().MONGODB_DAILY_DEATH_COLLECTION || DEFAULT_DAILY_COLLECTION_NAME, ); } async function ensureDailyDeathStatsIndex(collection) { if (!dailyIndexesReady) { dailyIndexesReady = collection.createIndex({ updatedAt: -1 }); } return dailyIndexesReady; } function parseJsonBody(body) { if (!body) { return {}; } if (typeof body === "object") { return body; } try { return JSON.parse(body); } catch { return {}; } } function normalizeDeathCounts(value = {}) { return SPECIES_KEYS.reduce((counts, species) => { counts[species] = Math.max(0, Math.round(Number(value?.[species]) || 0)); return counts; }, {}); } function speciesIncrements(deathsBySpecies) { return SPECIES_KEYS.reduce((increments, species) => { increments[`deathsBySpecies.${species}`] = deathsBySpecies[species] ?? 0; return increments; }, {}); } function formatDailyStats(document, date) { const deathsBySpecies = normalizeDeathCounts(document?.deathsBySpecies); return { date, battles: Math.max(0, Math.round(Number(document?.battles) || 0)), deathsBySpecies, totalDeaths: Math.max( totalCount(deathsBySpecies), Math.round(Number(document?.totalDeaths) || 0), ), updatedAt: document?.updatedAt?.toISOString?.() ?? null, }; } function totalCount(deathsBySpecies) { return SPECIES_KEYS.reduce((sum, species) => sum + (deathsBySpecies[species] ?? 0), 0); } function dayKey(date) { const timeZone = getConfig().DEATH_STATS_TIME_ZONE || "Asia/Seoul"; try { const parts = new Intl.DateTimeFormat("en-US", { day: "2-digit", month: "2-digit", timeZone, year: "numeric", }) .formatToParts(date) .reduce((result, part) => { result[part.type] = part.value; return result; }, {}); return `${parts.year}-${parts.month}-${parts.day}`; } catch { return date.toISOString().slice(0, 10); } }