fix(api): align /devices version scope, add Vary header, add validation + tests

This commit is contained in:
2026-07-13 05:07:51 +08:00
parent 0ad133492b
commit 5b107bddf7
2 changed files with 49 additions and 1 deletions
@@ -77,4 +77,40 @@ describe("buildPluginGameResponse — shape contract", () => {
expect(r.topEntries.length).toBe(2) expect(r.topEntries.length).toBe(2)
expect(r.topEntries[0].id).toBe("e1") // pinned first expect(r.topEntries[0].id).toBe("e1") // pinned first
}) })
it("handles all-null fps values gracefully", async () => {
const entries = [
{ id: "e1", hardwareSlug: "steamdeck-oled", fpsAvg: 60, fpsLow: null, fpsOnePercentLow: null, fpsHigh: null,
upscalerType: "none", frameGenMethod: "none", protonVersion: null, osVersion: null, tdpWatts: null,
settingsJson: null, upvotes: 0, isPinned: false, createdAt: new Date("2026-01-01"),
userName: null, userImage: null },
]
const r = await buildPluginGameResponse({ game: { id: "g1", steamAppId: 123, title: "X", slug: "x" }, entries, recent: entries })
expect(r.estFps).not.toBeNull()
expect(r.estFps!.avg).toBe(60)
expect(r.estFps!.low).toBeNull()
expect(r.estFps!.onePct).toBeNull()
expect(r.estFps!.high).toBeNull()
expect(r.estFps!.count).toBe(1)
})
it("trims recent entries separately from topEntries", async () => {
const top = [
{ id: "e1", hardwareSlug: "steamdeck-oled", fpsAvg: 60, fpsLow: 40, fpsOnePercentLow: 45, fpsHigh: 90,
upscalerType: "none", frameGenMethod: "none", protonVersion: "9", osVersion: "SteamOS 3", tdpWatts: 12,
settingsJson: null, upvotes: 5, isPinned: true, createdAt: new Date("2026-01-01"),
userName: "u", userImage: null },
]
const recent = [
{ id: "e2", hardwareSlug: "steamdeck-oled", fpsAvg: 80, fpsLow: 55, fpsOnePercentLow: 60, fpsHigh: 120,
upscalerType: "fsr", frameGenMethod: "none", protonVersion: "9", osVersion: "SteamOS 3", tdpWatts: 15,
settingsJson: null, upvotes: 2, isPinned: false, createdAt: new Date("2026-02-01"),
userName: "u2", userImage: null },
]
const r = await buildPluginGameResponse({ game: { id: "g1", steamAppId: 123, title: "X", slug: "x" }, entries: top, recent })
expect(r.topEntries).toHaveLength(1)
expect(r.topEntries[0].id).toBe("e1")
expect(r.recentEntries).toHaveLength(1)
expect(r.recentEntries[0].id).toBe("e2")
})
}) })
+13 -1
View File
@@ -207,6 +207,7 @@ export const pluginPublicRoutes = new Elysia({
.limit(query.limit ?? 3) .limit(query.limit ?? 3)
set.headers["Cache-Control"] = "public, max-age=60" set.headers["Cache-Control"] = "public, max-age=60"
set.headers["Vary"] = "search-params"
return await buildPluginGameResponse({ return await buildPluginGameResponse({
game, game,
entries: topRows as unknown as PluginEntryRow[], entries: topRows as unknown as PluginEntryRow[],
@@ -230,6 +231,10 @@ export const pluginPublicRoutes = new Elysia({
"/game/:steamAppId/devices", "/game/:steamAppId/devices",
async ({ params, set }) => { async ({ params, set }) => {
const steamAppId = Number(params.steamAppId) const steamAppId = Number(params.steamAppId)
if (!Number.isInteger(steamAppId) || steamAppId <= 0) {
set.status = 400
return { error: "Invalid steamAppId", devices: [] }
}
const [game] = await db const [game] = await db
.select({ id: games.id }) .select({ id: games.id })
.from(games) .from(games)
@@ -248,10 +253,17 @@ export const pluginPublicRoutes = new Elysia({
.from(performanceEntries) .from(performanceEntries)
.innerJoin(hardware, eq(performanceEntries.hardwareSlug, hardware.slug)) .innerJoin(hardware, eq(performanceEntries.hardwareSlug, hardware.slug))
.innerJoin(gameVersions, eq(performanceEntries.versionId, gameVersions.id)) .innerJoin(gameVersions, eq(performanceEntries.versionId, gameVersions.id))
.where(and(eq(gameVersions.gameId, game.id), eq(performanceEntries.isRemoved, false))) .where(
and(
eq(gameVersions.gameId, game.id),
eq(gameVersions.isLatest, true),
eq(performanceEntries.isRemoved, false),
),
)
.groupBy(performanceEntries.hardwareSlug, hardware.name) .groupBy(performanceEntries.hardwareSlug, hardware.name)
.orderBy(desc(sql`count(*)`)) .orderBy(desc(sql`count(*)`))
set.headers["Cache-Control"] = "public, max-age=60" set.headers["Cache-Control"] = "public, max-age=60"
set.headers["Vary"] = "search-params"
return { devices: rows } return { devices: rows }
}, },
{ {