feat: non-Steam game editing API, createdBy tracking, and source badges in search

This commit is contained in:
2026-04-29 00:16:43 +08:00
parent c6d4e7b3d1
commit b8173bd8f8
6 changed files with 1812 additions and 1 deletions
+10
View File
@@ -267,6 +267,16 @@ function SearchResultCard({
<h3 className="text-sm sm:text-base font-semibold text-text group-hover:text-primary transition-colors duration-200 truncate"> <h3 className="text-sm sm:text-base font-semibold text-text group-hover:text-primary transition-colors duration-200 truncate">
{result.title} {result.title}
</h3> </h3>
{!isLocal && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-text/5 border border-border text-text/35 capitalize shrink-0">
Steam
</span>
)}
{isLocal && result.source !== "steam" && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-amber-500/10 border border-amber-500/20 text-amber-400 capitalize shrink-0">
{result.source}
</span>
)}
{result.isRawPerformer && ( {result.isRawPerformer && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-green-500/10 border border-green-500/20 text-green-400 text-[10px] font-semibold shrink-0"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-green-500/10 border border-green-500/20 text-green-400 text-[10px] font-semibold shrink-0">
RAW PERFORMER RAW PERFORMER
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "games" ADD COLUMN "created_by" text;
File diff suppressed because it is too large Load Diff
+7
View File
@@ -92,6 +92,13 @@
"when": 1777389996351, "when": 1777389996351,
"tag": "0012_clever_thunderbolt_ross", "tag": "0012_clever_thunderbolt_ross",
"breakpoints": true "breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1777392960312,
"tag": "0013_supreme_purple_man",
"breakpoints": true
} }
] ]
} }
+67 -1
View File
@@ -1,7 +1,7 @@
import { Elysia, t } from "elysia" import { Elysia, t } from "elysia"
import { db } from "@/lib/db/index" import { db } from "@/lib/db/index"
import { games, gameVersions, gamePlatformSupport } from "@/lib/db/schema" import { games, gameVersions, gamePlatformSupport } from "@/lib/db/schema"
import { ilike } from "drizzle-orm" import { ilike, eq } from "drizzle-orm"
import { requireRole } from "@/lib/auth/guard" import { requireRole } from "@/lib/auth/guard"
export const gamesManualRoutes = new Elysia({ prefix: "/games" }) export const gamesManualRoutes = new Elysia({ prefix: "/games" })
@@ -45,6 +45,7 @@ export const gamesManualRoutes = new Elysia({ prefix: "/games" })
storeUrl: body.storeUrl || null, storeUrl: body.storeUrl || null,
genres: body.genres || null, genres: body.genres || null,
releaseDate: body.releaseDate || null, releaseDate: body.releaseDate || null,
createdBy: guard.user.id,
}) })
.returning() .returning()
@@ -93,3 +94,68 @@ export const gamesManualRoutes = new Elysia({ prefix: "/games" })
}), }),
} }
) )
.put(
"/:gameId/manual",
async ({ request, params, body, set }) => {
const guard = await requireRole(request.headers, ["user", "contributor", "admin"])
if (!guard.ok) {
set.status = guard.status
return { error: guard.error }
}
const [game] = await db
.select()
.from(games)
.where(eq(games.id, params.gameId))
.limit(1)
if (!game) {
set.status = 404
return { error: "Game not found" }
}
if (game.source === "steam") {
set.status = 403
return { error: "Steam games are synced automatically and cannot be manually edited" }
}
// Only creator or admin can edit
if (game.createdBy && game.createdBy !== guard.user.id && guard.user.role !== "admin") {
set.status = 403
return { error: "Only the creator or an admin can edit this game" }
}
const [updated] = await db
.update(games)
.set({
title: body.title ?? game.title,
developer: body.developer ?? game.developer,
publisher: body.publisher ?? game.publisher,
description: body.description ?? game.description,
headerImage: body.headerImage ?? game.headerImage,
capsuleImage: body.capsuleImage ?? game.capsuleImage,
storeUrl: body.storeUrl ?? game.storeUrl,
genres: body.genres ?? game.genres,
releaseDate: body.releaseDate ?? game.releaseDate,
updatedAt: new Date(),
})
.where(eq(games.id, params.gameId))
.returning()
return { game: updated }
},
{
params: t.Object({ gameId: t.String() }),
body: t.Object({
title: t.Optional(t.String()),
developer: t.Optional(t.String()),
publisher: t.Optional(t.String()),
description: t.Optional(t.String()),
headerImage: t.Optional(t.String()),
capsuleImage: t.Optional(t.String()),
storeUrl: t.Optional(t.String()),
genres: t.Optional(t.Array(t.String())),
releaseDate: t.Optional(t.String()),
}),
}
)
+1
View File
@@ -62,6 +62,7 @@ export const games = pgTable(
}>(), }>(),
lastSync: timestamp("last_sync"), lastSync: timestamp("last_sync"),
syncStatus: text("sync_status").default("pending"), syncStatus: text("sync_status").default("pending"),
createdBy: text("created_by"),
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(),
}, },