add agents.md and login and tag bugfix
This commit is contained in:
54
AGENTS.md
Normal file
54
AGENTS.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# AGENTS.md — Game Grid
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **SvelteKit 2** with **Svelte 5** (runes mode: `$state`, `$derived`, `$props`)
|
||||||
|
- **TypeScript** (strict), **Tailwind CSS 3**
|
||||||
|
- **SQLite** via `better-sqlite3` (native module — needs Python/make/g++)
|
||||||
|
- **bcrypt** (also native), **archiver** for on-the-fly .app zipping
|
||||||
|
- **Docker** for deployment with multi-stage build and bind-mounted game volumes
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # dev server (vite dev)
|
||||||
|
npm run build # production build (vite build)
|
||||||
|
npm run check # typecheck: runs svelte-kit sync then svelte-check
|
||||||
|
npm run preview # preview production build
|
||||||
|
```
|
||||||
|
|
||||||
|
No test, lint, or format scripts exist. No CI.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Two-tier library**: `GAMES_PATH/public/` (no auth) and `GAMES_PATH/private/` (login required).
|
||||||
|
|
||||||
|
**Scanner runs on every layout load** (`src/routes/+layout.server.ts:6`). On first request the scanner walks the file tree, detecting games, series, platforms, cover art, and screenshots — populating the SQLite DB. Database is auto-created and auto-migrated on first access (`src/lib/server/db.ts`).
|
||||||
|
|
||||||
|
**Auth**: single shared password (`APP_PASSWORD` env var), bcrypt-hashed at first use, session cookie is HMAC-signed with `SESSION_SECRET`.
|
||||||
|
|
||||||
|
**CSRF checkOrigin is disabled** globally in `svelte.config.js` — intentional for local network access.
|
||||||
|
|
||||||
|
**Native modules** (`better-sqlite3`, `bcrypt`) require Python 3, `make`, and `g++` for `npm install`. Docker build stage installs these explicitly.
|
||||||
|
|
||||||
|
## Important files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `src/lib/server/db.ts` | DB singleton, schema + migration |
|
||||||
|
| `src/lib/server/scanner.ts` | Filesystem scanner, game/series detection |
|
||||||
|
| `src/lib/server/auth.ts` | Session signing, password verification |
|
||||||
|
| `src/lib/platform.ts` | Platform detection from file extensions |
|
||||||
|
| `src/routes/+layout.server.ts` | Runs scanner & auth check on every request |
|
||||||
|
| `src/routes/games/[slug]/+page.server.ts` | Game detail, meta editing, tag actions |
|
||||||
|
| `src/routes/api/download/[fileId]/+server.ts` | File download, on-the-fly .app zipping |
|
||||||
|
| `svelte.config.js` | adapter-node, CSRF disabled |
|
||||||
|
| `docker-compose.yml` | Volume mounts, env vars, port mapping |
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **Scanner is slow on first load** — it walks every file in both libraries. Plan for this: refreshing while a scan is running will queue another scan.
|
||||||
|
- **No hot reload for `$env/dynamic/private`** — env var changes require a dev server restart.
|
||||||
|
- **`.svelte-kit/tsconfig.json` is generated** by `svelte-kit sync` and needed for TypeScript to resolve `$lib` etc. The `check` command runs sync first, but in an IDE you may need to run `npm run dev` once to generate it.
|
||||||
|
- **Game files are served directly off disk** — paths come from the DB and map to real filesystem paths under `GAMES_PATH`. If bind mounts change, re-scan (hit the app or `POST /api/scan`) to update the DB.
|
||||||
|
- **`.app` bundles** (macOS) are zipped on-the-fly for download — this is CPU-intensive for large bundles.
|
||||||
@@ -16,7 +16,11 @@
|
|||||||
<span class="inline-flex items-center gap-1 bg-gray-700 text-gray-200 text-sm px-3 py-1 rounded-full">
|
<span class="inline-flex items-center gap-1 bg-gray-700 text-gray-200 text-sm px-3 py-1 rounded-full">
|
||||||
{tag.name}
|
{tag.name}
|
||||||
{#if loggedIn}
|
{#if loggedIn}
|
||||||
<form method="POST" action="?/removeTag" use:enhance class="contents">
|
<form method="POST" action="?/removeTag" use:enhance={() => {
|
||||||
|
return async ({ result, update }) => {
|
||||||
|
if (result.type === 'success') await update();
|
||||||
|
};
|
||||||
|
}} class="contents">
|
||||||
<input type="hidden" name="tagId" value={tag.id} />
|
<input type="hidden" name="tagId" value={tag.id} />
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -31,7 +35,14 @@
|
|||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if loggedIn}
|
{#if loggedIn}
|
||||||
<form method="POST" action="?/addTag" use:enhance class="flex items-center gap-1" onsubmit={() => { newTag = ''; }}>
|
<form method="POST" action="?/addTag" use:enhance={() => {
|
||||||
|
return async ({ result, update }) => {
|
||||||
|
if (result.type === 'success') {
|
||||||
|
newTag = '';
|
||||||
|
await update();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}} class="flex items-center gap-1">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="tag"
|
name="tag"
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import type { Actions } from './$types';
|
|||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: ({ cookies }) => {
|
default: ({ cookies }) => {
|
||||||
cookies.delete('session', { path: '/' });
|
cookies.delete('session', {
|
||||||
|
path: '/',
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'strict',
|
||||||
|
secure: process.env.SECURE_COOKIES === 'true'
|
||||||
|
});
|
||||||
redirect(303, '/');
|
redirect(303, '/');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user