Adds BACKEND_ADMIN_TOKEN env var (set in Doppler) as a required Bearer token for every admin route. Also fixes PocketBase filter injection in notification queries and wires BACKEND_ADMIN_TOKEN through docker-compose to both backend and ui services. Includes CLAUDE.md for AI assistant guidance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.9 KiB
3.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
Docker (via just — the primary way to run services)
All services use Doppler for secrets injection. The just commands handle this automatically.
just up # Start all services in background
just up-fg # Start all services, stream logs
just down # Stop all services
just down-volumes # Full reset (destructive — removes all volumes)
just build # Rebuild all Docker images
just build-svc backend # Rebuild a specific service
just restart # Stop + rebuild + start
just logs # Tail all logs
just log backend # Tail a specific service
just shell backend # Open shell in running container
just init # One-shot init: MinIO buckets, PocketBase collections, Postgres
Backend (Go)
cd backend
go vet ./...
go test -short -race -count=1 -timeout=60s ./...
go test -short -race -count=1 -run TestFoo ./internal/somepackage/
go build ./cmd/backend
go build ./cmd/runner
Frontend (SvelteKit)
cd ui
npm run dev # Dev server at localhost:5173
npm run build # Production build
npm run check # svelte-check (type-check)
npm run paraglide # Regenerate i18n messages (run after editing messages/*.json)
Architecture
Three services communicate via PocketBase records and a Redis/Valkey task queue:
Backend (backend/cmd/backend) — HTTP REST API. Handles reads, enqueues tasks to Redis via Asynq, returns presigned MinIO URLs. Minimal processing; delegates heavy work to the runner.
Runner (backend/cmd/runner) — Asynq task worker. Processes scraping, TTS audio generation, AI text/image generation. Reads/writes PocketBase and MinIO directly.
UI (ui/) — SvelteKit 2 + Svelte 5 SSR app. Consumes the backend API. Uses Paraglide JS for i18n (5 locales).
Data layer
| Service | Role |
|---|---|
| PocketBase (SQLite) | Auth, structured records (books, chapters, tasks, subscriptions) |
| MinIO (S3-compatible) | Object storage — chapter text, audio files, images |
| Meilisearch | Full-text search (runner indexes, backend reads) |
| Redis/Valkey | Asynq task queue + presigned URL cache |
Key backend packages
internal/backend/— HTTP handlers and server setupinternal/runner/— Task processor implementationsinternal/storage/— Unified MinIO + PocketBase interface (all data access goes through here)internal/orchestrator/— Task orchestration across servicesinternal/taskqueue/— Enqueue helpers (backend side)internal/asynqqueue/— Asynq queue setup (runner side)internal/config/— Environment variable loading (Doppler-injected at runtime, no .env files)internal/presigncache/— Redis cache for MinIO presigned URLs
UI routing conventions (SvelteKit)
+page.svelte/+page.server.ts— Page + server-side load+layout.svelte/+layout.server.ts— Layoutsroutes/api/— API routes (+server.ts)lib/audio.svelte.ts— Client-side audio playback store (Svelte 5 runes)
Key Conventions
- Svelte 5 runes only — use
$state,$derived,$effect; do not use Svelte 4 stores or reactive statements. - Modern Go idioms — structured logging via
log/slog, OpenTelemetry tracing throughout. - No direct MinIO/PocketBase client calls outside the
internal/storage/package. - Secrets via Doppler — never use
.envfiles. All secrets are injected by Doppler CLI. - CI/CD is Gitea Actions (
.gitea/workflows/), not GitHub Actions. Usegitea.ref_name/gitea.shavariables. - Git hooks in
.githooks/— enable withjust setup. - i18n: translation files live in
ui/messages/{en,es,fr,de,pt}.json; runnpm run paraglideafter editing them. - Error tracking: GlitchTip with per-service DSNs (backend id/2, runner id/3, UI id/1) stored in Doppler.