# Bookshelf server PocketBase backend for the Bookshelf app — see `../docs/SPEC.md` for the authoritative data model and API rules. This directory contains everything needed to provision and run it. ## Layout ``` server/ pocketbase PocketBase v0.40.2 binary (gitignored — platform-specific) pb_data/ SQLite databases + uploaded files (gitignored — runtime state) pb_migrations/ Schema history, auto-captured — a fresh instance replays these on first boot and ends up with the full schema pb_hooks/ JS hook that closes a PocketBase quirk (see below) setup-schema.sh Idempotent schema/rules provisioning (curl + jq only) create-user.sh Superuser-driven account creation (no public sign-up) deploy/ systemd unit, Docker, backups, remote-access guidance ``` ## Quickstart (local dev) PocketBase must already be running (see `deploy/README.md` for how to run it as a proper service; for a quick local check you can just run the binary directly: `./pocketbase serve`). ```sh # 1. Provision the schema + API rules (safe to re-run). ./setup-schema.sh http://127.0.0.1:8090 # 2. Create app accounts — there is no self-registration. ./create-user.sh you@example.com "a strong password" "Your Name" ``` Both scripts also read `PB_URL`/`PB_EMAIL`/`PB_PASS` from the environment, or fall back to `.dev-credentials` (gitignored, dev-instance-only — see `.dev-credentials` in this directory if present) if no args are given. ## Schema summary Three collections — `bookcases`, `shelves`, `books` — each requiring authentication for every action (list/view/create/update/delete). Deletion in the app is always a soft `deleted` flag (tombstone), never an actual record delete, so sync can propagate it — see `SyncEngine` in the Android app and `docs/SPEC.md`'s Sync design section. The built-in `users` collection is locked down: `createRule = null` means **only a superuser can create an account** (via `create-user.sh`); regular users can view any user (needed to resolve the `added_by` relation) and update only their own record. ## The pb_hooks quirk PocketBase's declarative list/search rule acts as a row-level SQL filter, not a hard gate: an unauthenticated request against a collection whose `listRule` requires auth still gets **`200 OK` with an empty result**, rather than an error — because the rule can't be cleanly separated into "deny the whole request" vs. "just don't return matching rows" (see [pocketbase/pocketbase#6492](https://github.com/pocketbase/pocketbase/discussions/6492)). No private data ever leaks this way, but a `200` is still the wrong signal for "you're not allowed here." `pb_hooks/main.pb.js` adds a small `onRecordsListRequest` hook that turns that into a `403` for the three app collections. It's plain JS, auto-loaded by the stock `pocketbase` binary — no recompilation, no framework — so it ships and deploys exactly like `pb_migrations/`. ## Verifying a deployment ```sh # Anonymous requests must be rejected (4xx) for all three collections: curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8090/api/collections/books/records # -> 403 # Self-registration must be rejected: curl -s -X POST http://127.0.0.1:8090/api/collections/users/records \ -H 'Content-Type: application/json' -d '{"email":"x@x.com","password":"password123","passwordConfirm":"password123"}' # -> 403 "Only superusers can perform this action." ``` See `deploy/README.md` for running the server long-term, reaching it from outside your home network, and backups.