diff --git a/.gitignore b/.gitignore
index 6c501fd..bf051b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,4 @@ data/
.env
.env.*
!.env.example
-games/
+/games/
diff --git a/src/routes/games/[slug]/+page.server.ts b/src/routes/games/[slug]/+page.server.ts
new file mode 100644
index 0000000..0456851
--- /dev/null
+++ b/src/routes/games/[slug]/+page.server.ts
@@ -0,0 +1,91 @@
+import { error, fail } from '@sveltejs/kit';
+import type { Actions, PageServerLoad } from './$types';
+import { getDb } from '$lib/server/db';
+import { getSession } from '$lib/server/auth';
+import type { Game, GameFile, Screenshot, Tag } from '$lib/types';
+
+export const load: PageServerLoad = async ({ params, parent, cookies }) => {
+ const { loggedIn } = await parent();
+ const db = getDb();
+
+ const total = (db.prepare('SELECT COUNT(*) as n FROM games').get() as { n: number }).n;
+ console.log(`[games/${params.slug}] page server: slug="${params.slug}" total_games_in_db=${total}`);
+
+ const game = db.prepare('SELECT * FROM games WHERE slug = ?').get(params.slug) as
+ | Game
+ | undefined;
+ if (!game) {
+ const inSeries = db.prepare('SELECT slug, title FROM series WHERE slug = ?').get(params.slug) as { slug: string; title: string } | undefined;
+ console.log(
+ `[games/${params.slug}] 404 — not in games table. Total games in DB: ${total}. In series table: ${inSeries ? `yes ("${inSeries.title}")` : 'no'}`
+ );
+ error(404, 'Game not found');
+ }
+ if (game.library === 'private' && !loggedIn) error(403, 'Login required');
+
+ const files = db
+ .prepare('SELECT * FROM game_files WHERE game_id = ? ORDER BY platform, filename')
+ .all(game.id) as GameFile[];
+
+ const screenshots = db
+ .prepare('SELECT * FROM screenshots WHERE game_id = ? ORDER BY sort_order')
+ .all(game.id) as Screenshot[];
+
+ const tags = db
+ .prepare(
+ `SELECT t.id, t.name FROM tags t
+ JOIN game_tags gt ON gt.tag_id = t.id
+ WHERE gt.game_id = ?
+ ORDER BY t.name`
+ )
+ .all(game.id) as Tag[];
+
+ return { game, files, screenshots, tags };
+};
+
+export const actions: Actions = {
+ updateMeta: async ({ request, params, cookies }) => {
+ if (!getSession(cookies)) error(403, 'Login required');
+ const db = getDb();
+ const data = await request.formData();
+ const description = (data.get('description') as string) ?? '';
+ const genre = (data.get('genre') as string) ?? '';
+ db.prepare('UPDATE games SET description = ?, genre = ? WHERE slug = ?').run(
+ description,
+ genre,
+ params.slug
+ );
+ return { success: true };
+ },
+
+ addTag: async ({ request, params, cookies }) => {
+ if (!getSession(cookies)) error(403, 'Login required');
+ const db = getDb();
+ const data = await request.formData();
+ const name = ((data.get('tag') as string) ?? '').trim();
+ if (!name) return fail(400, { tagError: 'Tag name required' });
+
+ db.prepare('INSERT OR IGNORE INTO tags (name) VALUES (?)').run(name);
+ const tag = db.prepare('SELECT id FROM tags WHERE name = ?').get(name) as { id: number };
+ const game = db.prepare('SELECT id FROM games WHERE slug = ?').get(params.slug) as {
+ id: number;
+ };
+ db.prepare('INSERT OR IGNORE INTO game_tags (game_id, tag_id) VALUES (?, ?)').run(
+ game.id,
+ tag.id
+ );
+ return { success: true };
+ },
+
+ removeTag: async ({ request, params, cookies }) => {
+ if (!getSession(cookies)) error(403, 'Login required');
+ const db = getDb();
+ const data = await request.formData();
+ const tagId = Number(data.get('tagId'));
+ const game = db.prepare('SELECT id FROM games WHERE slug = ?').get(params.slug) as {
+ id: number;
+ };
+ db.prepare('DELETE FROM game_tags WHERE game_id = ? AND tag_id = ?').run(game.id, tagId);
+ return { success: true };
+ }
+};
diff --git a/src/routes/games/[slug]/+page.svelte b/src/routes/games/[slug]/+page.svelte
new file mode 100644
index 0000000..e7d62a2
--- /dev/null
+++ b/src/routes/games/[slug]/+page.svelte
@@ -0,0 +1,71 @@
+
+
+
{game.genre}
+ {/if} + +