fix gitignore
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,4 @@ data/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
games/
|
||||
/games/
|
||||
|
||||
91
src/routes/games/[slug]/+page.server.ts
Normal file
91
src/routes/games/[slug]/+page.server.ts
Normal file
@@ -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 };
|
||||
}
|
||||
};
|
||||
71
src/routes/games/[slug]/+page.svelte
Normal file
71
src/routes/games/[slug]/+page.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import type { Game, GameFile, Tag } from '$lib/types';
|
||||
import DownloadButton from '$lib/components/DownloadButton.svelte';
|
||||
import TagEditor from '$lib/components/TagEditor.svelte';
|
||||
import MetaEditor from '$lib/components/MetaEditor.svelte';
|
||||
import ScreenshotGallery from '$lib/components/ScreenshotGallery.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const game = $derived(data.game as Game);
|
||||
const files = $derived(data.files as GameFile[]);
|
||||
const tags = $derived(data.tags as Tag[]);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{game.title} — Game Grid</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<!-- Hero image -->
|
||||
{#if game.has_wide || game.has_cover}
|
||||
<div class="mb-8 rounded-xl overflow-hidden bg-gray-800 aspect-video max-h-80">
|
||||
<img
|
||||
src="/api/cover/{game.slug}?wide={(game.has_wide ? '1' : '0')}"
|
||||
alt={game.title}
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-8">
|
||||
<!-- Cover art (portrait) -->
|
||||
{#if game.has_cover}
|
||||
<div class="flex-none w-40 md:w-48">
|
||||
<div class="aspect-[2/3] rounded-lg overflow-hidden bg-gray-800 shadow-xl">
|
||||
<img src="/api/cover/{game.slug}" alt={game.title} class="w-full h-full object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">{game.title}</h1>
|
||||
|
||||
{#if game.genre}
|
||||
<p class="text-purple-400 text-sm font-medium mb-4">{game.genre}</p>
|
||||
{/if}
|
||||
|
||||
<TagEditor tags={tags} gameSlug={game.slug} loggedIn={data.loggedIn} />
|
||||
|
||||
<div class="mt-6">
|
||||
<DownloadButton {files} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<MetaEditor {game} loggedIn={data.loggedIn} />
|
||||
</div>
|
||||
|
||||
{#if data.screenshots.length > 0}
|
||||
<div class="mt-8">
|
||||
<h2 class="text-lg font-semibold text-gray-200 mb-3">Screenshots</h2>
|
||||
<ScreenshotGallery screenshots={data.screenshots} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-8">
|
||||
<a href="/" class="text-sm text-gray-500 hover:text-gray-300 transition-colors">← Back to library</a>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user