arena/server/deathStats.js

157 lines
3.7 KiB
JavaScript

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