feat: add stats fetching, preset joins, and sync=1 param to game page

This commit is contained in:
2026-04-26 15:49:00 +08:00
parent 64cf13636c
commit 016d585e48
+74 -8
View File
@@ -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,7 +39,9 @@ async function createGameStub(steamAppId: number) {
if (res.ok) {
const data = (await res.json()) as Record<
string,
{ success: boolean; data: {
{
success: boolean
data: {
type?: string
name: string
developers?: string[]
@@ -46,11 +49,11 @@ async function createGameStub(steamAppId: number) {
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<number>`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 (
<GamePageClient
game={serializedGame}
@@ -187,6 +251,8 @@ export default async function GamePage({
comments: commentCount,
}}
platformSupport={platformSupport}
presets={serializedPresets}
gameId={game.id}
/>
)
}