From 016d585e4891717d5aac6745756785f2774bed8f Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Sun, 26 Apr 2026 15:49:00 +0800 Subject: [PATCH] feat: add stats fetching, preset joins, and sync=1 param to game page --- app/game/[id]/page.tsx | 96 +++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/app/game/[id]/page.tsx b/app/game/[id]/page.tsx index 2cd1395..b241a09 100644 --- a/app/game/[id]/page.tsx +++ b/app/game/[id]/page.tsx @@ -8,6 +8,7 @@ import { communityPresets, gameComments, gamePlatformSupport, + hardware, } from "@/lib/db/schema" import { eq, sql } from "drizzle-orm" import { isSyncStale, syncSteamGame } from "@/lib/steam/sync" @@ -38,19 +39,21 @@ async function createGameStub(steamAppId: number) { if (res.ok) { const data = (await res.json()) as Record< string, - { success: boolean; data: { - type?: string - name: string - developers?: string[] - publishers?: string[] - genres?: { description: string }[] - header_image?: string - short_description?: string - } } + { + success: boolean + data: { + type?: string + name: string + developers?: string[] + publishers?: string[] + genres?: { description: string }[] + header_image?: string + short_description?: string + } + } > const entry = data[String(steamAppId)] if (entry?.success && entry.data) { - // Reject non-games (DLCs, soundtracks, demos, etc.) if (entry.data.type && entry.data.type !== "game") { notFound() } @@ -87,11 +90,15 @@ async function createGameStub(steamAppId: number) { export default async function GamePage({ params, + searchParams, }: { params: Promise<{ id: string }> + searchParams: Promise<{ sync?: string }> }) { const { id } = await params + const { sync } = await searchParams const isNumeric = /^\d+$/.test(id) + const forceSync = sync === "1" // ── Resolve game ──────────────────────────────────────────────── let game @@ -111,7 +118,6 @@ export default async function GamePage({ game = rows[0] } - // Auto-create stub for missing Steam games if (!game && isNumeric) { try { game = await createGameStub(Number(id)) @@ -124,8 +130,8 @@ export default async function GamePage({ notFound() } - // ── Fetch related counts ──────────────────────────────────────── - const [benchmarkCount, presetCount, commentCount, platformSupport] = + // ── Fetch related data in parallel ────────────────────────────── + const [benchmarkCount, presetCount, commentCount, platformSupport, presetRows] = await Promise.all([ db .select({ count: sql`count(*)::int` }) @@ -151,10 +157,44 @@ export default async function GamePage({ .from(gamePlatformSupport) .where(eq(gamePlatformSupport.gameId, game.id)) .then((r) => r), + db + .select({ + id: communityPresets.id, + name: communityPresets.name, + description: communityPresets.description, + hardwareSlug: communityPresets.hardwareSlug, + hardwareName: hardware.name, + upvotes: communityPresets.upvotes, + settingsJson: communityPresets.settingsJson, + createdAt: communityPresets.createdAt, + fpsAvg: performanceEntries.fpsAvg, + fpsLow: performanceEntries.fpsLow, + fpsHigh: performanceEntries.fpsHigh, + fsrVersion: performanceEntries.fsrVersion, + frameGenMethod: performanceEntries.frameGenMethod, + protonVersion: performanceEntries.protonVersion, + osVersion: performanceEntries.osVersion, + }) + .from(communityPresets) + .leftJoin( + performanceEntries, + eq(communityPresets.performanceEntryId, performanceEntries.id), + ) + .innerJoin( + hardware, + eq(communityPresets.hardwareSlug, hardware.slug), + ) + .where(eq(communityPresets.gameId, game.id)) + .orderBy(sql`${communityPresets.upvotes} DESC`), ]) - // ── Stale-While-Revalidate: schedule background sync ──────────── - if (game.source === "steam" && game.steamAppId && isSyncStale(game.lastSync)) { + // ── Sync logic: force or stale-while-revalidate ───────────────── + const shouldSync = + game.source === "steam" && + game.steamAppId && + (forceSync || isSyncStale(game.lastSync)) + + if (shouldSync) { after(async () => { await syncSteamGame(game.steamAppId!) }) @@ -178,6 +218,30 @@ export default async function GamePage({ createdAt: game.createdAt.toISOString(), } + const serializedPresets = presetRows.map((p) => ({ + id: p.id, + name: p.name, + description: p.description, + hardwareSlug: p.hardwareSlug, + hardwareName: p.hardwareName, + upvotes: p.upvotes, + settingsCount: Array.isArray(p.settingsJson) + ? p.settingsJson.reduce( + (sum: number, cat: { settings: unknown[] }) => + sum + cat.settings.length, + 0, + ) + : 0, + fpsAvg: p.fpsAvg, + fpsLow: p.fpsLow, + fpsHigh: p.fpsHigh, + fsrVersion: p.fsrVersion, + frameGenMethod: p.frameGenMethod, + protonVersion: p.protonVersion, + osVersion: p.osVersion, + createdAt: p.createdAt.toISOString(), + })) + return ( ) }