# 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 `shellHook`s do real work as a side effect:

- **backend** — `uv 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.
- **frontend** — `pnpm 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/`):

```bash
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/`):

```bash
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.
- **CI gates formatting, linting and tests.** `.gitea/workflows/ci.yml` runs on every push and pull
  request: `ruff format --check`, `ruff check`, `pytest`, and `prettier --check` all **block**;
  `eslint` and `pnpm check` report without failing until their pre-existing counts reach zero. A
  `v*` tag additionally builds and publishes both container images — see
  `docs/ci-release-pipeline.md`.
- **Commit messages** follow `type: summary` — `feat:`, `fix:`, `refactor:`, `chore:`.
- The three `README.md` files are user-facing. Agent-facing knowledge belongs in the `AGENTS.md`
  files.
