Files
chitai/AGENTS.md
patrick 55e00ba960 feat: import a Calibre library
Reads metadata.db and copies the books into a library — from a zip uploaded on
the library settings page, or from a path with `litestar calibre-import`. The
source is never touched, and re-running only picks up what is new.

Also names the formats mimetypes does not know: a Calibre library is full of
MOBI and AZW3, and a null content type used to fail the book endpoint.
2026-08-17 13:38:44 -04:00

5.5 KiB

Chitai

Self-hosted eBook library manager. Users organise eBook files into libraries, which contain books (one book = one metadata record + one or more files on disk). Books carry authors, publishers, tags, series and identifiers, can be grouped into per-user bookshelves, and are read in-browser through built-in EPUB and PDF readers with reading-progress tracking. The catalogue is also exposed as an OPDS feed for e-reader apps, and progress syncs with KOReader devices via a KOSync-compatible endpoint.

Layout

Path What
backend/ Litestar REST API + PostgreSQL. See backend/AGENTS.md.
frontend/ SvelteKit SSR web app. See frontend/AGENTS.md.
frontend/src/lib/vendor/ Vendored foliate-js (the EPUB engine), copied by frontend/scripts/vendor-foliate.sh.
frontend/static/pdfjs/ Vendored pdf.js viewer, used by the PDF reader in an iframe.
docker-compose.yml Production stack: db (postgres:17), backend, frontend.
docs/screenshots/ Images used by README.md.
shell.nix Root dev shell; composes the two sub-shells.

Development environment

nix-shell from the repo root is the intended entry point. It pulls in both backend/shell.nix and frontend/shell.nix, whose shellHooks do real work as a side effect:

  • backenduv venv + uv sync, then initdb / pg_ctl start into backend/.postgres/ (socket dir, not TCP), createdb chitai, and finally applies migrations with alchemy --config chitai.database.config.config upgrade --no-prompt. exitHook stops PostgreSQL on shell exit.
  • frontendpnpm install.

So entering the shell gives you a running database with an up-to-date schema; you do not need to start Postgres yourself. Both hooks cd around, which can be surprising in scripts.

Without Nix you need: Python 3.13 + uv, Node 24 + pnpm, and PostgreSQL 17 with the pg_trgm extension available.

Configuration

All backend settings are read by backend/src/chitai/config.py (Settings, pydantic-settings) with the CHITAI_ prefix from the repo-root .env. .env.prod-example is the template; copy it to .env for a fresh deployment.

.env is gitignored and contains a real CHITAI_TOKEN_SECRET — do not print, copy or commit it.

The frontend reads exactly one variable, VITE_BACKEND_API_URL (frontend/src/lib/server/config.ts, defaulting to http://localhost:8000).

How a request flows

browser
  └─ SvelteKit SSR node server
       ├─ hooks.server.ts       reads `authToken` cookie, validates via GET /access/me,
       │                        populates locals.user / locals.api, redirects to /login otherwise
       ├─ $lib/api/*.remote.ts  remote functions (query/command/form) → locals.api (ApiClient)
       └─ routes/api/[...path]  catch-all proxy, for browser-direct fetches (reader file streams)
            └─ Litestar backend
                 └─ controllers/ → services/ → SQLAlchemy models → PostgreSQL

The JWT the frontend holds in the authToken cookie is the same bearer token the backend issues from POST /access/login. The frontend never stores credentials beyond that cookie.

Commands

Backend (from backend/):

uv run litestar --app-dir src/chitai/ run --reload   # dev server on :8000
pytest tests/                                        # needs Docker (pytest-databases)
ruff format src/
alchemy --config chitai.database.config.config make-migrations
alchemy --config chitai.database.config.config upgrade

# Import a Calibre library. Copies files; --dry-run reports without writing.
litestar --app-dir src/chitai/ calibre-import <path> --library <slug>

Frontend (from frontend/):

pnpm dev       # vite dev server on :5173
pnpm build     # adapter-node output in build/
pnpm check     # svelte-check — run before finishing
pnpm lint      # prettier --check + eslint
pnpm format    # prettier --write

API docs are served by the running backend at http://localhost:8000/schema/ (Swagger) and /schema/openapi.json.

Cross-cutting rules

  • Keep the two schema layers in sync. Backend request/response shapes live in backend/src/chitai/schemas/ (Pydantic); the frontend mirrors them as Zod schemas in frontend/src/lib/schema/. Changing one without the other produces runtime validation failures, not type errors.
  • Regenerate the OpenAPI types after changing API shapes. frontend/src/lib/schema/openapi/schema.d.ts is generated from the backend's OpenAPI document with openapi-typescript (a devDependency; there is no package.json script for it, so it is run manually against a live backend).
  • Migrations are mandatory. The app runs with create_all=False, so a model change without a matching Alembic revision will not reach the database.
  • Commit messages follow type: summaryfeat:, fix:, refactor:, chore:.
  • The three README.md files are user-facing. Agent-facing knowledge belongs in the AGENTS.md files.