feat: replace fsrVersion with upscalerType/upscalerVersion, add battery & custom system columns

This commit is contained in:
2026-04-27 07:18:43 +08:00
parent ea6407cb4f
commit 91de833417
19 changed files with 1051 additions and 655 deletions
+13 -6
View File
@@ -34,7 +34,8 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
fpsAvg: performanceEntries.fpsAvg,
fpsLow: performanceEntries.fpsLow,
fpsHigh: performanceEntries.fpsHigh,
fsrVersion: performanceEntries.fsrVersion,
upscalerType: performanceEntries.upscalerType,
upscalerVersion: performanceEntries.upscalerVersion,
frameGenMethod: performanceEntries.frameGenMethod,
protonVersion: performanceEntries.protonVersion,
osVersion: performanceEntries.osVersion,
@@ -112,10 +113,13 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
const isRawPerformer = entries.some(
(e) =>
(e.fpsAvg ?? 0) >= 60 &&
e.fsrVersion === "none" &&
e.upscalerType === "none" &&
e.frameGenMethod === "none",
)
// ── 3b. Poor Performance check ─────────────────────────────────
const isPoorPerformance = entries.some((e) => (e.fpsAvg ?? 0) < 30)
// ── 4. Boxplot per device ─────────────────────────────────────
const boxplotMap = new Map<
string,
@@ -185,7 +189,7 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
{ hardwareSlug: string; sum: number; count: number }
>()
for (const e of entries) {
const key = `${e.fsrVersion}|${e.frameGenMethod}|${e.hardwareSlug}`
const key = `${e.upscalerType}|${e.upscalerVersion ?? ''}|${e.frameGenMethod}|${e.hardwareSlug}`
const existing = upscalerMap.get(key) || {
hardwareSlug: e.hardwareSlug,
sum: 0,
@@ -198,9 +202,10 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
const upscalerStats = Array.from(upscalerMap.entries()).map(
([key, data]) => {
const [fsrVersion, frameGenMethod] = key.split("|")
const [upscalerType, upscalerVersion, frameGenMethod] = key.split("|")
return {
fsrVersion,
upscalerType,
upscalerVersion: upscalerVersion || null,
frameGenMethod,
hardwareSlug: data.hardwareSlug,
avgFps: Math.round((data.sum / data.count) * 10) / 10,
@@ -220,8 +225,9 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
fpsHigh: e.fpsHigh!,
isRawPerformer:
(e.fpsAvg ?? 0) >= 60 &&
e.fsrVersion === "none" &&
e.upscalerType === "none" &&
e.frameGenMethod === "none",
isPoorPerformer: (e.fpsAvg ?? 0) < 30,
}))
// ── 8. Device breakdown ───────────────────────────────────────
@@ -261,6 +267,7 @@ export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
versionCount,
},
isRawPerformer,
isPoorPerformance,
boxplot,
historical,
upscalerStats,
+11 -10
View File
@@ -120,7 +120,8 @@ export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
fpsAvg: performanceEntries.fpsAvg,
fpsLow: performanceEntries.fpsLow,
fpsHigh: performanceEntries.fpsHigh,
fsrVersion: performanceEntries.fsrVersion,
upscalerType: performanceEntries.upscalerType,
upscalerVersion: performanceEntries.upscalerVersion,
frameGenMethod: performanceEntries.frameGenMethod,
protonVersion: performanceEntries.protonVersion,
osVersion: performanceEntries.osVersion,
@@ -152,7 +153,7 @@ export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
topGames: [],
genreBreakdown: [],
protonBreakdown: [],
fsrBreakdown: [],
upscalerBreakdown: [],
}
}
@@ -250,17 +251,17 @@ export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
.sort((a, b) => b[1] - a[1])
.map(([version, count]) => ({ version, count }))
// ── FSR breakdown ───────────────────────────────────
const fsrMap = new Map<string, { count: number; avgFps: number }>()
// ── Upscaler breakdown ───────────────────────────────────
const upscalerMap = new Map<string, { count: number; avgFps: number }>()
for (const e of entries) {
const key = e.fsrVersion ?? "none"
if (!fsrMap.has(key)) fsrMap.set(key, { count: 0, avgFps: 0 })
const f = fsrMap.get(key)!
const key = e.upscalerType ?? "none"
if (!upscalerMap.has(key)) upscalerMap.set(key, { count: 0, avgFps: 0 })
const f = upscalerMap.get(key)!
f.count++
f.avgFps += e.fpsAvg ?? 0
}
const fsrBreakdown = [...fsrMap.entries()].map(([version, data]) => ({
version,
const upscalerBreakdown = [...upscalerMap.entries()].map(([type, data]) => ({
upscalerType: type,
count: data.count,
avgFps: Math.round((data.avgFps / data.count) * 10) / 10,
}))
@@ -276,7 +277,7 @@ export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
topGames,
genreBreakdown,
protonBreakdown,
fsrBreakdown,
upscalerBreakdown,
}
},
{
+15 -5
View File
@@ -106,7 +106,10 @@ export const performanceSubmitRoutes = new Elysia({ prefix: "/performance" })
fpsHigh: body.fpsHigh ?? null,
protonVersion: body.protonVersion ?? null,
osVersion: body.osVersion ?? null,
fsrVersion: body.fsrVersion ?? "none",
upscalerType: body.upscalerType ?? "none",
upscalerVersion: body.upscalerVersion ?? null,
estimatedBatteryMin: body.estimatedBatteryMin ?? null,
customSystem: body.customSystem ?? false,
frameGenMethod: body.frameGenMethod ?? "none",
loadTimeSsd: body.loadTimeSsd ?? null,
loadTimeSd: body.loadTimeSd ?? null,
@@ -131,19 +134,26 @@ export const performanceSubmitRoutes = new Elysia({ prefix: "/performance" })
fpsHigh: t.Optional(t.Union([t.Number(), t.Null()])),
protonVersion: t.Optional(t.Union([t.String(), t.Null()])),
osVersion: t.Optional(t.Union([t.String(), t.Null()])),
fsrVersion: t.Optional(
upscalerType: t.Optional(
t.Union([
t.Literal("none"),
t.Literal("fsr1"),
t.Literal("fsr2"),
t.Literal("fsr3"),
t.Literal("fsr"),
t.Literal("dlss"),
t.Literal("xess"),
t.Literal("lsfg"),
t.Literal("other"),
]),
),
upscalerVersion: t.Optional(t.Union([t.String(), t.Null()])),
estimatedBatteryMin: t.Optional(t.Union([t.Number(), t.Null()])),
customSystem: t.Optional(t.Boolean()),
frameGenMethod: t.Optional(
t.Union([
t.Literal("none"),
t.Literal("fsr_fg"),
t.Literal("dlss_fg"),
t.Literal("lsfg"),
t.Literal("other"),
]),
),
loadTimeSsd: t.Optional(t.Union([t.Number(), t.Null()])),
+13 -7
View File
@@ -12,7 +12,7 @@ export const performanceRoutes = createCrudRoutes(performanceEntries, {
auth: { read: "public", write: "user", delete: "admin" },
softDelete: true,
search: { fields: ["userNotes"] },
filter: { fields: ["hardwareSlug", "fsrVersion", "frameGenMethod"] },
filter: { fields: ["hardwareSlug", "upscalerType", "upscalerVersion", "frameGenMethod"] },
})
// ── Verify endpoint (admin/mod) ───────────────────────────────────
@@ -183,7 +183,8 @@ export const performanceVerifyRoutes = new Elysia({
fpsAvg: performanceEntries.fpsAvg,
fpsLow: performanceEntries.fpsLow,
fpsHigh: performanceEntries.fpsHigh,
fsrVersion: performanceEntries.fsrVersion,
upscalerType: performanceEntries.upscalerType,
upscalerVersion: performanceEntries.upscalerVersion,
frameGenMethod: performanceEntries.frameGenMethod,
settingsJson: performanceEntries.settingsJson,
userNotes: performanceEntries.userNotes,
@@ -227,10 +228,11 @@ export const performanceVerifyRoutes = new Elysia({
.get(
"/stats",
async ({ query, set }) => {
const { gameId, hardwareSlug, fsrVersion } = query as {
const { gameId, hardwareSlug, upscalerType, upscalerVersion } = query as {
gameId?: string
hardwareSlug?: string
fsrVersion?: string
upscalerType?: string
upscalerVersion?: string
}
if (!gameId) {
@@ -246,8 +248,11 @@ export const performanceVerifyRoutes = new Elysia({
if (hardwareSlug) {
conditions.push(eq(performanceEntries.hardwareSlug, hardwareSlug))
}
if (fsrVersion) {
conditions.push(eq(performanceEntries.fsrVersion, fsrVersion as any)) // eslint-disable-line @typescript-eslint/no-explicit-any
if (upscalerType) {
conditions.push(eq(performanceEntries.upscalerType, upscalerType as any)) // eslint-disable-line @typescript-eslint/no-explicit-any
}
if (upscalerVersion) {
conditions.push(eq(performanceEntries.upscalerVersion, upscalerVersion))
}
// Join through gameVersions to get to games
@@ -272,7 +277,8 @@ export const performanceVerifyRoutes = new Elysia({
query: t.Object({
gameId: t.String(),
hardwareSlug: t.Optional(t.String()),
fsrVersion: t.Optional(t.String()),
upscalerType: t.Optional(t.String()),
upscalerVersion: t.Optional(t.String()),
}),
},
)
+6 -2
View File
@@ -146,8 +146,9 @@ export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
countMap.get(c.gameId)!.comments = c.count
}
// ── 2b. Raw Performer + best FPS ────────────────────────────────
// ── 2b. Raw Performer + Poor Performance + best FPS ────────────
const rawPerformerMap = new Map<string, boolean>()
const poorPerformerMap = new Map<string, boolean>()
const bestFpsMap = new Map<string, number>()
if (localGameIds.length > 0) {
@@ -157,9 +158,10 @@ export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
bestFps: sql<number>`MAX(${performanceEntries.fpsAvg})::real`,
isRawPerformer: sql<boolean>`BOOL_OR(
${performanceEntries.fpsAvg} >= 60
AND ${performanceEntries.fsrVersion} = 'none'
AND ${performanceEntries.upscalerType} = 'none'
AND ${performanceEntries.frameGenMethod} = 'none'
)`,
isPoorPerformance: sql<boolean>`BOOL_OR(${performanceEntries.fpsAvg} < 30)`,
})
.from(performanceEntries)
.innerJoin(
@@ -177,6 +179,7 @@ export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
for (const row of perfStats) {
bestFpsMap.set(row.gameId, row.bestFps)
rawPerformerMap.set(row.gameId, row.isRawPerformer)
poorPerformerMap.set(row.gameId, row.isPoorPerformance)
}
}
@@ -273,6 +276,7 @@ export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
}
: null,
isRawPerformer: rawPerformerMap.get(g.id) ?? false,
isPoorPerformance: poorPerformerMap.get(g.id) ?? false,
bestFps: bestFpsMap.get(g.id) ?? null,
latestVersion: latestVersionMap.get(g.id) ?? null,
})
+8 -6
View File
@@ -1,7 +1,7 @@
import { Elysia, t } from "elysia"
import { auth } from "@/lib/auth"
import { db } from "@/lib/db/index"
import { user, performanceEntries, games, gameVersions, hardware, account } from "@/lib/db/schema"
import { user, performanceEntries, games, gameVersions, hardware, account, passkey } from "@/lib/db/schema"
import { eq, sql, and, desc } from "drizzle-orm"
import { hashPassword } from "better-auth/crypto"
@@ -155,10 +155,11 @@ export const userRoutes = new Elysia({ prefix: "/user" })
.from(account)
.where(eq(account.userId, session.user.id))
// Count passkeys
const passkeys = await auth.api.listPasskeys({
headers: request.headers,
})
// Count passkeys via direct DB query (avoids auth.api.listPasskeys hanging)
const passkeys = await db
.select({ id: passkey.id })
.from(passkey)
.where(eq(passkey.userId, session.user.id))
// Check if user has a password (from accounts where providerId is "credential")
const hasPassword = accounts.some((a) => a.providerId === "credential")
@@ -256,7 +257,8 @@ export const userRoutes = new Elysia({ prefix: "/user" })
fpsHigh: performanceEntries.fpsHigh,
hardwareSlug: performanceEntries.hardwareSlug,
hardwareName: hardware.name,
fsrVersion: performanceEntries.fsrVersion,
upscalerType: performanceEntries.upscalerType,
upscalerVersion: performanceEntries.upscalerVersion,
frameGenMethod: performanceEntries.frameGenMethod,
verifiedAt: performanceEntries.verifiedAt,
createdAt: performanceEntries.createdAt,
+18 -7
View File
@@ -13,17 +13,21 @@ import { gameVersions } from "./gameVersions"
import { hardware } from "./hardware"
import { user } from "./auth"
export const fsrVersionEnum = pgEnum("fsr_version", [
export const upscalerTypeEnum = pgEnum("upscaler_type", [
"none",
"fsr1",
"fsr2",
"fsr3",
"fsr",
"dlss",
"xess",
"lsfg",
"other",
])
export const frameGenMethodEnum = pgEnum("frame_gen_method", [
"none",
"fsr_fg",
"dlss_fg",
"lsfg",
"other",
])
export type GameSettingCategory = {
@@ -56,8 +60,9 @@ export const performanceEntries = pgTable(
protonVersion: text("proton_version"),
osVersion: text("os_version"),
// Upscaler tracking (replaces isFsrEnabled boolean)
fsrVersion: fsrVersionEnum("fsr_version").default("none").notNull(),
// Upscaler tracking
upscalerType: upscalerTypeEnum("upscaler_type").default("none").notNull(),
upscalerVersion: text("upscaler_version"),
frameGenMethod: frameGenMethodEnum("frame_gen_method")
.default("none")
.notNull(),
@@ -73,6 +78,12 @@ export const performanceEntries = pgTable(
settingsJson: jsonb("settings_json").$type<GameSettingCategory[]>(),
userNotes: text("user_notes"),
// Battery estimate (minutes)
estimatedBatteryMin: integer("estimated_battery_min"),
// Custom system flag
customSystem: boolean("custom_system").default(false).notNull(),
// Moderation
isRemoved: boolean("is_removed").default(false).notNull(),
removedReason: text("removed_reason"),
@@ -91,7 +102,7 @@ export const performanceEntries = pgTable(
updatedAt: timestamp("updated_at").defaultNow().notNull(),
},
(table) => [
index("perf_hardware_fsr_idx").on(table.hardwareSlug, table.fsrVersion),
index("perf_hardware_upscaler_idx").on(table.hardwareSlug, table.upscalerType),
index("perf_version_idx").on(table.versionId),
index("perf_user_idx").on(table.userId),
],