feat: updates page and lint fixes

This commit is contained in:
2026-05-01 10:15:13 +08:00
parent 7bcd580397
commit ca08024cb5
25 changed files with 188 additions and 314 deletions
+5 -5
View File
@@ -1,6 +1,6 @@
import { Elysia, t } from "elysia"
import { db } from "@/lib/db/index"
import { communitySuggestions, games, user } from "@/lib/db/schema"
import { communitySuggestions, games, user, suggestionStatusEnum } from "@/lib/db/schema"
import { eq, and, desc } from "drizzle-orm"
import { requireAuth, requireContributorOrAdmin } from "@/lib/auth/guard"
@@ -109,7 +109,7 @@ export const communitySuggestionRoutes = new Elysia({
return { error: "You already have a pending suggestion for this field" }
}
const currentValue = String((game as any)[fieldName] ?? "")
const currentValue = String((game as Record<string, unknown>)[fieldName] ?? "")
const [suggestion] = await db
.insert(communitySuggestions)
@@ -149,9 +149,9 @@ export const communitySuggestionRoutes = new Elysia({
async ({ params, query }) => {
const status = query.status // optional filter
let conditions = [eq(communitySuggestions.gameId, params.gameId)]
const conditions = [eq(communitySuggestions.gameId, params.gameId)]
if (status) {
conditions.push(eq(communitySuggestions.status, status as any))
conditions.push(eq(communitySuggestions.status, status as typeof suggestionStatusEnum.enumValues[number]))
}
const suggestions = await db
@@ -263,7 +263,7 @@ export const communitySuggestionRoutes = new Elysia({
// If approved, apply the change to the game
if (status === "approved") {
const updateData: Record<string, any> = {}
const updateData: Record<string, string> = {}
updateData[suggestion.fieldName] = suggestion.proposedValue
await db
+5 -5
View File
@@ -7,7 +7,7 @@ import {
gamePlatformSupport,
hardware,
} from "@/lib/db/schema"
import { ilike, or, sql, eq, and, desc, asc, inArray, gte, lte } from "drizzle-orm"
import { ilike, or, sql, eq, and, desc, asc, inArray, gte, lte, type SQL } from "drizzle-orm"
import { fuzzySearchTerm } from "@/lib/db/search"
const MAX_OFFSET = 10000
@@ -92,11 +92,11 @@ export const gamesListingRoutes = new Elysia({ prefix: "/games/listing" }).get(
// ── FPS range filter (games with benchmarks in this range) ────
if (minFps || maxFps) {
const fpsConditions = [
const fpsConditions: (SQL | undefined)[] = [
eq(performanceEntries.isRemoved, false),
minFps ? gte(performanceEntries.fpsAvg, Number(minFps)) : undefined,
maxFps ? lte(performanceEntries.fpsAvg, Number(maxFps)) : undefined,
].filter(Boolean) as any[]
].filter((c): c is SQL => c !== undefined)
const fpsSubquery = db
.select({ gameId: gameVersions.gameId })
@@ -127,7 +127,7 @@ export const gamesListingRoutes = new Elysia({ prefix: "/games/listing" }).get(
// ── Proton / Native filter ────────────────────────────────────
if (protonNative && ["proton", "native", "both"].includes(protonNative)) {
const protonConditions: any[] = []
const protonConditions: SQL[] = []
if (protonNative === "proton" || protonNative === "both") {
protonConditions.push(eq(gamePlatformSupport.protonStatus, "proton"))
@@ -148,7 +148,7 @@ export const gamesListingRoutes = new Elysia({ prefix: "/games/listing" }).get(
// ── Anti-cheat status filter ──────────────────────────────────
const validAcStatuses = ["supported", "unsupported", "unknown", "none"]
if (antiCheatStatus && antiCheatStatus !== "any" && validAcStatuses.includes(antiCheatStatus)) {
const acConditions: any[] = [
const acConditions: SQL[] = [
eq(gamePlatformSupport.antiCheatRelevant, true),
eq(gamePlatformSupport.antiCheatStatus, antiCheatStatus as "none" | "supported" | "unsupported" | "unknown"),
]
+3 -42
View File
@@ -190,46 +190,7 @@ export const gameVersionsRoutes = new Elysia({ prefix: "/games/:gameId/versions"
// ── Game Sync Routes ────────────────────────────────────────────────
const MAX_BULK_SYNC = 1000
const SYNC_CONCURRENCY = 5 // Number of parallel syncs
const SYNC_BATCH_DELAY_MS = 2000 // Delay between batches to respect rate limits (2 seconds)
/**
* Process syncs sequentially with delay to avoid Steam rate limiting.
* Steam API has aggressive rate limiting - parallel requests get blocked quickly.
*/
async function syncSequentially(
gamesToSync: { id: string; steamAppId: number | null }[]
): Promise<{ synced: number; failed: number; results: Map<string, { success: boolean; error?: string }> }> {
let synced = 0
let failed = 0
const results = new Map<string, { success: boolean; error?: string }>()
for (let i = 0; i < gamesToSync.length; i++) {
const game = gamesToSync[i]
if (!game.steamAppId) continue
try {
const result = await syncSteamGame(game.steamAppId, { forceRetry: true })
if (result.success) {
synced++
results.set(game.id, { success: true })
} else {
failed++
results.set(game.id, { success: false, error: result.error })
}
} catch (e) {
failed++
results.set(game.id, { success: false, error: String(e) })
}
// Delay between syncs to avoid rate limiting
if (i < gamesToSync.length - 1) {
await new Promise((resolve) => setTimeout(resolve, SYNC_BATCH_DELAY_MS))
}
}
return { synced, failed, results }
}
export const gameSyncRoutes = new Elysia({ prefix: "/games" })
// Bulk sync with streaming progress (defined before /:gameId/sync to avoid route conflict)
@@ -285,7 +246,8 @@ export const gameSyncRoutes = new Elysia({ prefix: "/games" })
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
const send = (data: any) => {
type ProgressEvent = { type: string; current?: number; total: number; synced: number; failed: number; currentGame?: string | null }
const send = (data: ProgressEvent) => {
controller.enqueue(encoder.encode(JSON.stringify(data) + "\n"))
}
@@ -294,7 +256,6 @@ export const gameSyncRoutes = new Elysia({ prefix: "/games" })
let synced = 0
let failed = 0
const batchSize = SYNC_CONCURRENCY
// Process syncs sequentially with delay to avoid rate limiting
for (let i = 0; i < gamesToSync.length; i++) {
@@ -305,7 +266,7 @@ export const gameSyncRoutes = new Elysia({ prefix: "/games" })
const result = await syncSteamGame(game.steamAppId, { forceRetry: true })
if (result.success) synced++
else failed++
} catch (e) {
} catch {
failed++
}
+7 -7
View File
@@ -1,6 +1,6 @@
import { Elysia } from "elysia";
import { db } from "@/lib/db/index";
import { games, gamePlatformSupport, performanceEntries, gameVersions } from "@/lib/db/schema";
import { games, gamePlatformSupport, performanceEntries, gameVersions, playabilityStatusEnum } from "@/lib/db/schema";
import { eq, and, avg, count, sql } from "drizzle-orm";
import { requireContributorOrAdmin } from "@/lib/auth/guard";
@@ -95,7 +95,7 @@ export async function recalculatePlayability(gameId: string): Promise<{
await db
.update(gamePlatformSupport)
.set({
playabilityStatus: status as any,
playabilityStatus: status as typeof playabilityStatusEnum.enumValues[number],
playabilityCalculatedAt: new Date(),
})
.where(
@@ -111,13 +111,13 @@ export async function recalculatePlayability(gameId: string): Promise<{
// Update aggregate game-level playability (worst of all devices)
const priority = { unplayable: 0, needs_tweaks: 1, playable: 2, great: 3, unknown: 4 };
let worstStatus: string = "unknown";
let worstStatus: typeof playabilityStatusEnum.enumValues[number] = "unknown";
for (const r of results) {
if (
priority[r.playabilityStatus as keyof typeof priority] <
priority[worstStatus as keyof typeof priority]
) {
worstStatus = r.playabilityStatus;
worstStatus = r.playabilityStatus as typeof playabilityStatusEnum.enumValues[number];
}
}
@@ -132,7 +132,7 @@ export async function recalculatePlayability(gameId: string): Promise<{
await db
.update(games)
.set({
playabilityStatus: worstStatus as any,
playabilityStatus: worstStatus as typeof playabilityStatusEnum.enumValues[number],
playabilityCalculatedAt: new Date(),
})
.where(eq(games.id, gameId));
@@ -174,7 +174,7 @@ export const playabilityRoutes = new Elysia({ prefix: "/playability" })
if (hardwareSlug) {
await db
.update(gamePlatformSupport)
.set({ playabilityStatus: status as any, playabilityOverride: true })
.set({ playabilityStatus: status as typeof playabilityStatusEnum.enumValues[number], playabilityOverride: true })
.where(
and(
eq(gamePlatformSupport.gameId, params.gameId),
@@ -184,7 +184,7 @@ export const playabilityRoutes = new Elysia({ prefix: "/playability" })
} else {
await db
.update(games)
.set({ playabilityStatus: status as any, playabilityOverride: true })
.set({ playabilityStatus: status as typeof playabilityStatusEnum.enumValues[number], playabilityOverride: true })
.where(eq(games.id, params.gameId));
}
+2 -2
View File
@@ -6,7 +6,7 @@ import { eq } from "drizzle-orm"
// In-memory cache for reviews (key -> { data, expires })
const reviewCache = new Map<
string,
{ data: any; expires: number }
{ data: SteamReviewResponse; expires: number }
>()
const CACHE_TTL = 60 * 60 * 1000 // 1 hour
@@ -100,7 +100,7 @@ export const steamReviewRoutes = new Elysia({ prefix: "/steam-reviews" })
reviewCache.set(cacheKey, { data: slicedData, expires: Date.now() + CACHE_TTL })
return slicedData
} catch (error) {
} catch {
set.status = 502
return { error: "Steam review API unavailable" }
}