/** * @file Leaderboard routes for retrieving ranked player statistics. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Route handler requires many steps */ /* eslint-disable complexity -- Leaderboard handler has inherent complexity */ import { Hono } from "hono"; import { gameTitles } from "../data/titles.js"; import { prisma } from "../db/client.js"; import { logger } from "../services/logger.js"; import type { HonoEnvironment } from "../types/hono.js"; import type { GameState } from "@elysium/types"; const leaderboardRouter = new Hono(); const validCategories = new Set([ "totalGold", "bossesDefeated", "questsCompleted", "achievementsUnlocked", "prestigeCount", "transcendenceCount", "apotheosisCount", ]); const gameStateCategories = new Set([ "prestigeCount", "transcendenceCount", "apotheosisCount", ]); /** * Parses the showOnLeaderboards flag from a player's profile settings blob. * @param raw - The raw profile settings value from the database. * @returns True if the player should appear on leaderboards, false otherwise. */ const parseShowOnLeaderboards = (raw: unknown): boolean => { if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Runtime profile shape */ return (raw as Record).showOnLeaderboards !== false; } return true; }; /** * Resolves the display title name for a given title ID. * @param titleId - The player's active title ID. * @returns The human-readable title name, or empty string if no title. */ const resolveTitleName = (titleId: string | null): string => { if (titleId === null || titleId === "") { return ""; } return gameTitles.find((title) => { return title.id === titleId; })?.name ?? titleId; }; leaderboardRouter.get("/", async(context) => { try { const category = context.req.query("category") ?? "totalGold"; const limitRaw = Number(context.req.query("limit") ?? "100"); const limit = Math.min(Math.max(1, limitRaw), 100); if (!validCategories.has(category)) { return context.json({ error: "Invalid category" }, 400); } const [ players, gameStates ] = await Promise.all([ prisma.player.findMany(), gameStateCategories.has(category) ? prisma.gameState.findMany() : Promise.resolve([]), ]); const stateMap = new Map( gameStates.map((gs) => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Prisma returns JsonValue */ return [ gs.discordId, gs.state as unknown as GameState ]; }), ); const entries = players. filter((player) => { return parseShowOnLeaderboards(player.profileSettings); }). map((player) => { let value = 0; if (category === "totalGold") { value = player.lifetimeGoldEarned; } else if (category === "bossesDefeated") { value = player.lifetimeBossesDefeated; } else if (category === "questsCompleted") { value = player.lifetimeQuestsCompleted; } else if (category === "achievementsUnlocked") { value = player.lifetimeAchievementsUnlocked; } else { const state = stateMap.get(player.discordId); if (category === "prestigeCount") { value = state?.prestige.count ?? 0; } else if (category === "transcendenceCount") { value = state?.transcendence?.count ?? 0; } else if (category === "apotheosisCount") { value = state?.apotheosis?.count ?? 0; } } return { activeTitle: resolveTitleName(player.activeTitle), avatar: player.avatar ?? null, characterName: player.characterName, discordId: player.discordId, username: player.username, value: value, }; }). sort((a, b) => { return b.value - a.value; }). slice(0, limit). map((entry, index) => { return { ...entry, rank: index + 1 }; }); return context.json({ category, entries }); } catch (error) { void logger.error( "leaderboards", error instanceof Error ? error : new Error(String(error)), ); return context.json({ error: "Internal server error" }, 500); } }); export { leaderboardRouter };