import "server-only"; import { asc } from "drizzle-orm"; import { connection } from "next/server"; import { getDb } from "@/db/client"; import { catalogArtifacts, catalogCharacters, catalogWeapons } from "@/db/schema"; import { parseLunarisVersion, weaponStatsFromLunaris } from "@/lib/catalog-stats"; import { gcsimKey } from "@/lib/translator/keys"; import type { Catalog, CatalogItem } from "@/lib/translator/types"; const fallback: Catalog = { version: "fallback", characters: [ { id: "10000002", key: "kamisatoayaka", name: "Kamisato Ayaka", imageUrl: "", rarity: 5, weaponType: "WEAPON_SWORD_ONE_HAND", element: "Cryo", supported: true, baseStats: [] }, { id: "10000032", key: "bennett", name: "Bennett", imageUrl: "", rarity: 4, weaponType: "WEAPON_SWORD_ONE_HAND", element: "Pyro", supported: true, baseStats: [] }, { id: "10000089", key: "furina", name: "Furina", imageUrl: "", rarity: 5, weaponType: "WEAPON_SWORD_ONE_HAND", element: "Hydro", supported: true, baseStats: [] }, { id: "10000047", key: "kaedeharakazuha", name: "Kaedehara Kazuha", imageUrl: "", rarity: 5, weaponType: "WEAPON_SWORD_ONE_HAND", element: "Anemo", supported: true, baseStats: [] }, ], weapons: [ { id: "11401", key: "favoniussword", name: "Favonius Sword", imageUrl: "", rarity: 4, weaponType: "WEAPON_SWORD_ONE_HAND", supported: true, baseStats: [] }, { id: "11509", key: "mistsplitterreforged", name: "Mistsplitter Reforged", imageUrl: "", rarity: 5, weaponType: "WEAPON_SWORD_ONE_HAND", supported: true, baseStats: [] }, ], artifacts: [ { id: "15007", key: "noblesseoblige", name: "Noblesse Oblige", imageUrl: "", rarity: 5, supported: true, baseStats: [] }, { id: "15020", key: "blizzardstrayer", name: "Blizzard Strayer", imageUrl: "", rarity: 5, supported: true, baseStats: [] }, { id: "15032", key: "goldentroupe", name: "Golden Troupe", imageUrl: "", rarity: 5, supported: true, baseStats: [] }, ], }; function publicUrl(key: string) { const base = (process.env.S3_PUBLIC_URL ?? "https://buzz-cdn.astrxl.dev").replace(/\/+$/u, ""); return `${base}/${key.split("/").map(encodeURIComponent).join("/")}`; } async function getLunarisWeaponStats() { const root = "https://api.lunaris.moe/data"; try { const versionResponse = await fetch(`${root}/version.json`, { next: { revalidate: 3600 } }); if (!versionResponse.ok) throw new Error(`Version request returned ${versionResponse.status}`); const version = parseLunarisVersion(await versionResponse.json()); const weaponsResponse = await fetch(`${root}/${encodeURIComponent(version)}/weaponlist.json`, { next: { revalidate: 3600 } }); if (!weaponsResponse.ok) throw new Error(`Weapon request returned ${weaponsResponse.status}`); return weaponStatsFromLunaris(await weaponsResponse.json()); } catch (error) { console.error("Lunaris weapon stats unavailable", error); return new Map(); } } function withWeaponStats(catalog: Catalog, stats: Awaited>): Catalog { return { ...catalog, weapons: catalog.weapons.map((weapon) => ({ ...weapon, baseStats: stats.get(weapon.id) ?? [] })) }; } export async function getCatalog(): Promise { await connection(); const weaponStats = await getLunarisWeaponStats(); if (!process.env.DATABASE_URL) return withWeaponStats(fallback, weaponStats); try { const db = getDb(); const [characters, weapons, artifacts] = await Promise.all([ db.select().from(catalogCharacters).orderBy(asc(catalogCharacters.name)), db.select().from(catalogWeapons).orderBy(asc(catalogWeapons.name)), db.select().from(catalogArtifacts).orderBy(asc(catalogArtifacts.name)), ]); if (!characters.length || !weapons.length || !artifacts.length) return withWeaponStats(fallback, weaponStats); const map = (kind: "character" | "weapon" | "artifact", rows: typeof characters | typeof weapons | typeof artifacts): CatalogItem[] => rows.map((row) => { const character = "element" in row ? row : null; const key = gcsimKey(kind, row.name, character?.element, row.key); return { id: row.key, key, name: row.name, imageUrl: publicUrl(row.imageKey), rarity: row.rarity, weaponType: "weaponType" in row ? row.weaponType : null, element: character?.element ?? null, supported: Boolean(key), baseStats: character?.baseStats ?? [] }; }); return withWeaponStats({ version: "buzz-current", characters: map("character", characters), weapons: map("weapon", weapons), artifacts: map("artifact", artifacts) }, weaponStats); } catch (error) { console.error("Catalog unavailable; using fallback catalog", error); return withWeaponStats(fallback, weaponStats); } }