import { NextResponse } from "next/server"; import { readdirSync, readFileSync, statSync } from "fs"; import { join } from "path"; import { auth } from "@/lib/auth"; import { memoAsync } from "@/lib/cache"; export const dynamic = "force-dynamic"; export const runtime = "nodejs"; const STATS_DIR = "/home/minecraft/server/world/stats"; const USERCACHE = "/home/minecraft/server/usercache.json"; const TICKS_PER_HOUR = 20 * 60 * 60; type Entry = { uuid: string; name: string; playtimeTicks: number; playtimeHours: number; lastPlayedMs: number; }; type UserCacheEntry = { name: string; uuid: string }; function loadNameMap(): Map { try { const arr = JSON.parse(readFileSync(USERCACHE, "utf8")) as UserCacheEntry[]; return new Map(arr.map((e) => [e.uuid, e.name])); } catch { return new Map(); } } function computePlaytime(): Entry[] { const names = loadNameMap(); let files: string[]; try { files = readdirSync(STATS_DIR).filter((f) => f.endsWith(".json")); } catch { return []; } const out: Entry[] = []; for (const f of files) { const uuid = f.replace(/\.json$/, ""); const path = join(STATS_DIR, f); try { const data = JSON.parse(readFileSync(path, "utf8")) as { stats?: { "minecraft:custom"?: Record }; }; const ticks = Number( data.stats?.["minecraft:custom"]?.["minecraft:play_time"] || 0 ); const mtime = statSync(path).mtimeMs; out.push({ uuid, name: names.get(uuid) || uuid.slice(0, 8), playtimeTicks: ticks, playtimeHours: Math.round((ticks / TICKS_PER_HOUR) * 100) / 100, lastPlayedMs: mtime, }); } catch { // Skip unreadable/corrupt stat files } } out.sort((a, b) => b.playtimeTicks - a.playtimeTicks); return out; } export async function GET() { const session = await auth(); if (!session) { return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); } try { const data = await memoAsync("players:playtime", 60_000, async () => computePlaytime() ); return NextResponse.json(data, { headers: { "Cache-Control": "private, max-age=60" }, }); } catch (e) { return NextResponse.json( { error: (e as Error).message }, { status: 500 } ); } }