From 17214a36259f7ae090a1307b7670d7fdab830f1c Mon Sep 17 00:00:00 2001 From: gunshiz Date: Sat, 29 Aug 2026 18:52:01 +0000 Subject: [PATCH] feat(guides): organize schemas and export teams --- app/api/teams/[teamId]/image/route.tsx | 124 + components/admin/create-guide-flow.tsx | 2 +- components/admin/guide-overview-form.tsx | 2 +- components/admin/structured-dashboard.tsx | 2 +- components/public/structured-directory.tsx | 2 +- components/public/structured-guide.tsx | 11 +- db/schema.ts | 75 +- drizzle/0004_mixed_felicia_hardy.sql | 77 + drizzle/meta/0004_snapshot.json | 3439 ++++++++++++++++++++ drizzle/meta/_journal.json | 7 + lib/catalog/lunaris.test.ts | 12 +- lib/catalog/lunaris.ts | 4 +- lib/guides/queries.ts | 40 + 13 files changed, 3751 insertions(+), 46 deletions(-) create mode 100644 app/api/teams/[teamId]/image/route.tsx create mode 100644 drizzle/0004_mixed_felicia_hardy.sql create mode 100644 drizzle/meta/0004_snapshot.json diff --git a/app/api/teams/[teamId]/image/route.tsx b/app/api/teams/[teamId]/image/route.tsx new file mode 100644 index 0000000..eea533b --- /dev/null +++ b/app/api/teams/[teamId]/image/route.tsx @@ -0,0 +1,124 @@ +/* eslint-disable @next/next/no-img-element */ +import { ImageResponse } from "next/og"; + +import { getPublicTeamExport } from "@/lib/guides/queries"; + +const anuphanFont = fetch( + "https://raw.githubusercontent.com/google/fonts/main/ofl/anuphan/Anuphan%5Bwght%5D.ttf", +) + .then((response) => (response.ok ? response.arrayBuffer() : null)) + .catch(() => null); + +function amount(value: number): string { + return new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(value); +} + +export async function GET( + _request: Request, + context: RouteContext<"/api/teams/[teamId]/image">, +) { + const { teamId } = await context.params; + const team = await getPublicTeamExport(teamId); + if (!team) return new Response("Team not found", { status: 404 }); + + const total = team.members.reduce( + (sum, member) => sum + Number(member.damage), + 0, + ); + const seconds = Number(team.rotationSeconds); + const dps = seconds > 0 ? total / seconds : 0; + const font = await anuphanFont; + + return new ImageResponse( +
+
+
+ {team.guideName} +
+
+ {team.title} · {seconds.toLocaleString(undefined, { maximumFractionDigits: 2 })}s rotation +
+
+ +
+ Damage Share +
+
+ {team.members.map((member) => { + const damage = Number(member.damage); + const percent = total > 0 ? (damage / total) * 100 : 0; + return ( +
+ +
+
+
{member.character.name}
+
{amount(damage)}
+
+
+
+
+
+
+ {percent.toFixed(1)}% +
+
+
+
+ ); + })} +
+ +
+
+ DPS: {amount(dps)} + | + DPR: {amount(total)} +
+
+ {team.combo} +
+
+ BUZZ SHEET +
+
, + { + width: 550, + height: 776, + fonts: font + ? [{ name: "Anuphan", data: font, weight: 400, style: "normal" }] + : undefined, + headers: { + "Cache-Control": "no-store", + "Content-Disposition": `attachment; filename="buzz-team-${team.id}.png"`, + }, + }, + ); +} diff --git a/components/admin/create-guide-flow.tsx b/components/admin/create-guide-flow.tsx index 9f657ce..b81b970 100644 --- a/components/admin/create-guide-flow.tsx +++ b/components/admin/create-guide-flow.tsx @@ -40,7 +40,7 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char Guide Image {cover ? ( -
+
{`Guide
) : null} diff --git a/components/admin/guide-overview-form.tsx b/components/admin/guide-overview-form.tsx index b2dc6fa..cb2519c 100644 --- a/components/admin/guide-overview-form.tsx +++ b/components/admin/guide-overview-form.tsx @@ -21,5 +21,5 @@ export function GuideOverviewForm({ guide }: { guide: { id: string; version: num initialVersion: guide.version, persist: (next, expectedVersion) => saveGuideOverview(guide.id, { ...next, expectedVersion }), }); - return Overviewภาพและข้อความนี้แสดงบนหน้าแรกและส่วนแนะนำของ GuideGuide Image
{`Guide
setCover(asset)} />
Overview