/** * @file Apotheosis route handling the apotheosis reset mechanic. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Route handler requires many steps */ /* eslint-disable max-statements -- Route handler requires many statements */ /* eslint-disable stylistic/max-len -- Description string cannot be shortened */ import { Hono } from "hono"; import { prisma } from "../db/client.js"; import { authMiddleware } from "../middleware/auth.js"; import { buildPostApotheosisState, isEligibleForApotheosis, } from "../services/apotheosis.js"; import { logger } from "../services/logger.js"; import { grantApotheosisRole, postMilestoneWebhook, } from "../services/webhook.js"; import type { HonoEnvironment } from "../types/hono.js"; import type { GameState } from "@elysium/types"; const apotheosisRouter = new Hono(); apotheosisRouter.use("*", authMiddleware); apotheosisRouter.post("/", async(context) => { try { const discordId = context.get("discordId"); const record = await prisma.gameState.findUnique({ where: { discordId } }); if (!record) { return context.json({ error: "No save found" }, 404); } const rawState: unknown = record.state; /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Prisma returns JsonValue; cast to GameState */ const state = rawState as GameState; if (!isEligibleForApotheosis(state)) { return context.json( { error: "Not eligible for Apotheosis — purchase all Transcendence upgrades first", }, 400, ); } // Capture current-run stats before the nuclear reset // eslint-disable-next-line capitalized-comments -- v8 ignore /* v8 ignore next 9 -- @preserve */ const runBossesDefeated = state.bosses.filter((b) => { return b.status === "defeated"; }).length; const runQuestsCompleted = state.quests.filter((q) => { return q.status === "completed"; }).length; const runAdventurersRecruited = state.adventurers.reduce((sum, a) => { return sum + a.count; }, 0); // eslint-disable-next-line capitalized-comments -- v8 ignore /* v8 ignore next 3 -- @preserve */ const runAchievementsUnlocked = state.achievements.filter((a) => { return a.unlockedAt !== null; }).length; const { updatedState, updatedApotheosisData } = buildPostApotheosisState( state, state.player.characterName, ); const now = Date.now(); await prisma.gameState.update({ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Prisma requires object */ data: { state: updatedState as object, updatedAt: now }, where: { discordId }, }); await prisma.player.update({ data: { characterName: state.player.characterName, lastSavedAt: now, lifetimeAchievementsUnlocked: { increment: runAchievementsUnlocked }, lifetimeAdventurersRecruited: { increment: runAdventurersRecruited }, lifetimeBossesDefeated: { increment: runBossesDefeated }, lifetimeClicks: { increment: state.player.totalClicks }, // Accumulate into lifetime totals lifetimeGoldEarned: { increment: state.player.totalGoldEarned }, lifetimeQuestsCompleted: { increment: runQuestsCompleted }, totalClicks: 0, // Reset current-run counters totalGoldEarned: 0, }, where: { discordId }, }); const apotheosisCount = updatedApotheosisData.count; void logger.metric("apotheosis", 1, { apotheosisCount, discordId }); void grantApotheosisRole(discordId); void postMilestoneWebhook(discordId, "apotheosis", { apotheosis: updatedApotheosisData.count, prestige: updatedState.prestige.count, // eslint-disable-next-line capitalized-comments -- v8 ignore /* v8 ignore next -- @preserve */ transcendence: updatedState.transcendence?.count ?? 0, }); return context.json({ apotheosisCount: updatedApotheosisData.count }); } catch (error) { void logger.error( "apotheosis", error instanceof Error ? error : new Error(String(error)), ); return context.json({ error: "Internal server error" }, 500); } }); export { apotheosisRouter };