feat: non-Steam game editing API, createdBy tracking, and source badges in search
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "games" ADD COLUMN "created_by" text;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -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(),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user