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>
141 lines
8.4 KiB
Markdown
141 lines
8.4 KiB
Markdown
# 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).
|