Files
elysium/apps/api/src/routes/prestige.ts
T
hikari acda4c2fc4 fix: resolve pre-existing TypeScript strictness build errors
- Add @types/node to API devDependencies
- Create HonoEnv type and apply to all routers + auth middleware for
  proper context.get/set("discordId") typing
- Use conditional spreads for exactOptionalPropertyTypes dailyChallenges
  in GameContext, tick engine, and prestige route
- Use conditional spread for optional signature in SaveRequest calls
- Add non-null assertions in shuffle/template index for noUncheckedIndexedAccess
- Cast GameState to never for Prisma InputJsonValue fields
- Exclude vite.config.ts from web tsconfig (it runs in Node context)
2026-03-06 23:56:55 -08:00

145 lines
4.3 KiB
TypeScript

import type { BuyPrestigeUpgradeRequest, GameState, PrestigeRequest } from "@elysium/types";
import { Hono } from "hono";
import type { HonoEnv } from "../types/hono.js";
import { prisma } from "../db/client.js";
import { authMiddleware } from "../middleware/auth.js";
import { DEFAULT_PRESTIGE_UPGRADES } from "../data/prestigeUpgrades.js";
import { updateChallengeProgress } from "../services/dailyChallenges.js";
import {
buildPostPrestigeState,
computeRunestoneMultipliers,
isEligibleForPrestige,
} from "../services/prestige.js";
export const prestigeRouter = new Hono<HonoEnv>();
prestigeRouter.use("*", authMiddleware);
prestigeRouter.post("/", async (context) => {
const discordId = context.get("discordId") as string;
const body = await context.req.json<PrestigeRequest>();
const characterName = body.characterName?.trim();
if (!characterName) {
return context.json({ error: "characterName is required" }, 400);
}
const record = await prisma.gameState.findUnique({ where: { discordId } });
if (!record) {
return context.json({ error: "No save found" }, 404);
}
const state = record.state as unknown as GameState;
if (!isEligibleForPrestige(state)) {
return context.json(
{ error: "Not eligible for prestige — collect 1,000,000 total gold first" },
400,
);
}
// Update daily prestige challenge progress before resetting the run
let updatedDailyChallenges = state.dailyChallenges;
let challengeCrystals = 0;
if (updatedDailyChallenges) {
const result = updateChallengeProgress(updatedDailyChallenges, "prestige", 1);
updatedDailyChallenges = result.updatedChallenges;
challengeCrystals = result.crystalsAwarded;
}
const { newState, newPrestigeData, runestonesEarned, milestoneRunestones } = buildPostPrestigeState(
state,
characterName,
);
// Preserve daily challenges across the prestige reset and apply any crystal rewards
const finalState: GameState = {
...newState,
...(updatedDailyChallenges !== undefined ? { dailyChallenges: updatedDailyChallenges } : {}),
resources: {
...newState.resources,
crystals: newState.resources.crystals + challengeCrystals,
},
};
const now = Date.now();
await prisma.gameState.update({
where: { discordId },
data: { state: finalState as object, updatedAt: now },
});
await prisma.player.update({
where: { discordId },
data: {
characterName,
totalGoldEarned: 0,
totalClicks: 0,
lastSavedAt: now,
},
});
return context.json({
runestones: runestonesEarned,
newPrestigeCount: newPrestigeData.count,
milestoneRunestones,
});
});
prestigeRouter.post("/buy-upgrade", async (context) => {
const discordId = context.get("discordId") as string;
const body = await context.req.json<BuyPrestigeUpgradeRequest>();
const { upgradeId } = body;
if (!upgradeId) {
return context.json({ error: "upgradeId is required" }, 400);
}
const upgrade = DEFAULT_PRESTIGE_UPGRADES.find((u) => u.id === upgradeId);
if (!upgrade) {
return context.json({ error: "Unknown prestige upgrade" }, 404);
}
const record = await prisma.gameState.findUnique({ where: { discordId } });
if (!record) {
return context.json({ error: "No save found" }, 404);
}
const state = record.state as unknown as GameState;
const { purchasedUpgradeIds, runestones } = state.prestige;
if (purchasedUpgradeIds.includes(upgradeId)) {
return context.json({ error: "Upgrade already purchased" }, 400);
}
if (runestones < upgrade.runestonesCost) {
return context.json({ error: "Not enough runestones" }, 400);
}
const newRunestones = runestones - upgrade.runestonesCost;
const newPurchasedUpgradeIds = [...purchasedUpgradeIds, upgradeId];
const newState: GameState = {
...state,
prestige: {
...state.prestige,
runestones: newRunestones,
purchasedUpgradeIds: newPurchasedUpgradeIds,
...computeRunestoneMultipliers(newPurchasedUpgradeIds),
},
};
await prisma.gameState.update({
where: { discordId },
data: { state: newState as object, updatedAt: Date.now() },
});
const multipliers = computeRunestoneMultipliers(newPurchasedUpgradeIds);
return context.json({
runestonesRemaining: newRunestones,
purchasedUpgradeIds: newPurchasedUpgradeIds,
...multipliers,
});
});