chore: initial project scaffold

Set up Mangalord with a Rust/axum backend, SvelteKit frontend, Postgres,
and Docker Compose deployment. Establishes the architecture and TDD
patterns the project will extend:

- Hexagonal-ish backend layering (domain / repo / storage / api) with
  a pluggable Storage trait (LocalStorage today, S3 as a future impl).
- Initial migration: users, mangas, chapters, bookmarks.
- Vertical slice for mangas (list, search, create, get) with
  #[sqlx::test] integration coverage and storage unit tests.
- SvelteKit frontend using Svelte 5 runes, typed API client, Vitest
  unit tests and Playwright e2e with route mocking.
- CLAUDE.md documenting layering, TDD/git/SemVer workflow rules, and
  extension points (tags, fulltext search, OCR, S3, auth).
- Project-scoped .claude/settings.json with permission allowlist for
  the toolchain (git, cargo, npm/vite, docker, psql, gh, doc fetches).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-16 21:05:16 +02:00
commit 6c1d04aaf4
48 changed files with 1657 additions and 0 deletions

140
CLAUDE.md Normal file
View File

@@ -0,0 +1,140 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
Mangalord is a manga / comics reader. Users browse, search, read, bookmark, and upload manga. The same HTTP API is consumed by both the SvelteKit frontend and external bots/scripts — there is no separate "bot API" or "internal API." Deployment is a single server via Docker Compose.
## Stack
- **Backend**: Rust, axum 0.7, sqlx 0.8 (Postgres), tokio
- **Database**: Postgres 16
- **Frontend**: SvelteKit 2 with Svelte 5 runes, TypeScript, Vite
- **Storage**: `Storage` trait + `LocalStorage` impl. S3 and friends are planned implementations, not refactors.
- **Tests**: `cargo test` (unit + integration via `#[sqlx::test]`), Vitest (frontend unit), Playwright (E2E)
## Development workflow rules
These rules apply to every change. They are not negotiable per-task — if a change can't be made under them, raise the conflict before writing code.
### TDD
Tests describing the expected behaviour are written **before** the implementation, in the same commit or one immediately preceding. A commit that adds production code without a corresponding test (or that updates a test only after the fact to match what was built) does not meet this bar — split it.
See *TDD workflow* below for picking the right test level.
### Git
- One branch per logical change, prefixed by intent: `feat/<slug>`, `bugfix/<slug>`, `chore/<slug>`, `docs/<slug>`, `refactor/<slug>`, `test/<slug>`. Never commit directly to `main`.
- Commit messages follow Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:`). One concern per commit.
- Each branch lands via a reviewed PR (self-review counts when working solo; the diff must still be read end-to-end before merge). Squash-merge into `main` to keep history linear.
- Do not push `main` directly, do not force-push shared branches, and do not bypass hooks.
### Semantic versioning
The project version is tracked in [backend/Cargo.toml](backend/Cargo.toml) and [frontend/package.json](frontend/package.json) and bumped on every merge to `main`:
- `feat:` → bump **minor**
- `fix:` / `bugfix:` → bump **patch**
- breaking change (only valid before 1.0, otherwise needs a major bump after 1.0) → bump **minor** while under 1.0
- `chore:` / `docs:` / `test:` / `refactor:` with no behaviour change → no bump
While the project is pre-release the version stays **below `1.0.0`**. The `1.0.0` release happens **only at the user's explicit instruction** — do not bump to or past it autonomously.
Both manifests must stay in lockstep (same version string).
## TDD workflow
The project is developed test-first. For each change:
1. Write a failing test at the right level.
2. Implement the minimum to make it pass.
3. Refactor with tests green.
Pick the level deliberately:
- **Pure logic / mappers / validation** → unit test next to the code. In Rust: `#[cfg(test)] mod tests` in the same file. In the frontend: a sibling `*.test.ts`.
- **Anything that touches the DB** → integration test in [backend/tests/](backend/tests/), using `#[sqlx::test(migrations = "./migrations")]`. Each test gets a fresh, migrated database.
- **Cross-component user journeys** → Playwright in [frontend/e2e/](frontend/e2e/). Mock the network at the Playwright route level when the journey doesn't require a real backend.
Run these before claiming a change works:
```bash
(cd backend && cargo test)
(cd frontend && npm test)
(cd frontend && npm run test:e2e) # needs dev server, see playwright.config.ts
```
Backend integration tests require `DATABASE_URL` to point at a Postgres where the test user can `CREATEDB` (the `#[sqlx::test]` macro provisions a fresh database per test).
## Backend layout
Hexagonal-ish layering. Handlers depend on `repo` and `storage`, never the other way around. This is the seam new extensions plug into.
- [backend/src/domain/](backend/src/domain/) — pure data types, no I/O. Add new types here first.
- [backend/src/repo/](backend/src/repo/) — DB access functions taking `&PgPool`. Plain async fns rather than a repository struct; tests target them directly.
- [backend/src/storage/](backend/src/storage/) — `Storage` trait + `LocalStorage`. **Add `S3Storage` here when needed**, do not branch on backend type in handlers.
- [backend/src/api/](backend/src/api/) — axum handlers and route wiring. One module per resource.
- [backend/src/app.rs](backend/src/app.rs) — `AppState` and router assembly. Integration tests use the same `router(state)` function with a test `AppState`.
- [backend/src/error.rs](backend/src/error.rs) — `AppError` is the single error type returned by handlers. New variants map to status codes in `IntoResponse`.
When adding a new resource:
1. Add a migration `backend/migrations/NNNN_<name>.sql`.
2. Add the domain type in [backend/src/domain/](backend/src/domain/).
3. Add repo functions in [backend/src/repo/](backend/src/repo/).
4. Add handlers in `backend/src/api/<resource>.rs` and merge in [backend/src/api/mod.rs](backend/src/api/mod.rs).
5. Add integration tests in [backend/tests/](backend/tests/), reusing [backend/tests/common/mod.rs](backend/tests/common/mod.rs).
### Database access
Queries use `sqlx::query_as::<_, T>(...)` (runtime-checked) with `#[derive(FromRow)]` on the domain types, so the project builds without a live DB. If you want compile-time SQL checking later, switch to `sqlx::query!`/`query_as!` macros and commit a `.sqlx` directory via `cargo sqlx prepare`.
Migrations live in [backend/migrations/](backend/migrations/) and run automatically at startup via `sqlx::migrate!`.
### Storage trait
Do not call `tokio::fs` or any filesystem API from handlers. Go through `state.storage` (the `Storage` trait). Keys are `/`-separated paths; the local backend rejects `..` and empty segments. Suggested key layout:
- `mangas/{manga_id}/cover.{ext}`
- `mangas/{manga_id}/chapters/{chapter_id}/pages/{nnnn}.{ext}`
## Frontend layout
- [frontend/src/lib/api/](frontend/src/lib/api/) — typed API client. **All backend calls go through here**, not raw `fetch` in components. The base URL is `import.meta.env.VITE_API_BASE`, defaulting to `/api` (which Vite dev-proxies to the backend).
- [frontend/src/routes/](frontend/src/routes/) — SvelteKit routes.
- Use Svelte 5 runes (`$state`, `$derived`, `$effect`, `$props`). Do not use the legacy `let`-reactive syntax or `on:event=` directive form — prefer `onevent={...}`.
- The Node adapter is configured for production; `npm run build && node build` is what the Dockerfile runs.
## Common commands
```bash
# Full stack (compose), production-like
docker compose up --build
# Local dev: Postgres in compose, backend + frontend native
docker compose -f docker-compose.dev.yml up -d
(cd backend && cargo run)
(cd frontend && npm run dev)
# Backend tests
(cd backend && cargo test)
(cd backend && cargo test --test api_mangas) # single integration test file
(cd backend && cargo test list_is_empty_initially) # single test by name
# Frontend tests
(cd frontend && npm test) # vitest, all
(cd frontend && npx vitest run src/lib/api/mangas.test.ts) # single file
(cd frontend && npm run test:e2e) # playwright (auto-starts vite)
```
## Extension points
These are first-class slots in the architecture. When adding any of them, plug into the existing seam rather than building parallel infrastructure.
- **Tags / lists**: new tables joined to `mangas`. New `domain`, `repo`, and `api` modules; the existing manga endpoints do not need to change.
- **Full-text / fuzzy search**: enable `pg_trgm` in a migration and add a GIN index on `mangas.title`; swap the `WHERE` in `repo::manga::list` to use `%` operator or `tsvector`. The API shape (`?search=...`) does not change.
- **OCR / autotagging**: a background worker (a separate binary or a tokio task spawned in `app::build`) that reads pages from `storage::Storage` and writes tag rows. Do not couple OCR to upload handlers — it runs asynchronously.
- **S3 storage**: add `storage::S3Storage` implementing `Storage`. Branch in `app::build` based on a config field (e.g., `STORAGE_BACKEND=s3`). Handlers do not change.
- **Auth**: an axum middleware producing a `CurrentUser` extractor. Bots use an API token header, browser users a session cookie — both should populate the same extractor so handlers stay backend-agnostic. Until this lands, treat all endpoints as unauthenticated (this is acknowledged technical debt, not a final design).