157 lines
3.7 KiB
JavaScript
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);
|
|
}
|
|
}
|