# Architecture

Tech stack, auth architecture, data model, and key architectural rationale for YotoShelf.

## Runtime stack

| Concern | Technology | Notes |
|---|---|---|
| HTTP server | Go + chi + huma | chi for routing; huma for OpenAPI generation and type-safe handlers |
| Frontend | Astro + Svelte 5 | Astro static MPA; interactive islands use Svelte 5 |
| Database | SQLite (modernc.org/sqlite) | Pure Go — no CGo, works in distroless |
| Session management | gorilla/sessions CookieStore | HMAC-signed cookies; Yoto tokens stored in SQLite, not cookies |
| Media processing | ffmpeg | Local audio transcoding for preview playback |
| Container | Multi-stage OCI | Node (frontend) → Go (binary) → Alpine (runtime + ffmpeg) |

## Auth architecture

YotoShelf has two separate auth concerns:

1. **App authentication** — who is logged in to YotoShelf. Supports both local user accounts (admin-created, argon2id passwords) and OIDC (optional, for self-hosters with an existing IdP).
2. **Yoto account linking** — OAuth2 PKCE flow to link a Yoto account, storing tokens encrypted in SQLite.

The Go backend is a Backend-for-Frontend (BFF): it handles all auth and serves the static frontend. The frontend never handles tokens directly. Go middleware checks the session cookie and redirects to `/login` for unauthenticated requests.

### Dual OAuth2 flows

| Concern | App login | Yoto account link |
|---|---|---|
| Protocol | Local password or OIDC (Auth Code) | OAuth2 Auth Code + PKCE |
| Token storage | Session cookie (user ID only) | SQLite `yoto_accounts` table (encrypted at rest) |
| Token lifetime | Session = configurable (default 7 days) | Access token = hours; refresh token = long-lived |
| Refresh | Re-auth or OIDC refresh | Background refresh; Yoto tokens are single-use |

### Token refresh serialisation

One `yoto.Client` instance per linked Yoto account, shared across all background workers via a `sync.RWMutex`-protected map. The client's internal mutex serialises refresh operations. Yoto refresh tokens are single-use — the new token is persisted atomically in a database transaction before the old one is discarded.

## Data model overview

SQLite is the single source of truth. `card.yaml` is a derived export, written after every SQLite change. The web UI never reads directly from `card.yaml`; a filesystem watcher imports external edits as a power-user escape hatch.

### Key tables

| Table | Purpose |
|---|---|
| `users` | Authenticated users. `auth_type`: `local` or `oidc`. Role: `admin` / `user` / `viewer`. |
| `yoto_accounts` | Linked Yoto accounts per user. Tokens encrypted at rest. Multiple accounts per user supported. |
| `collections` | Primary organising unit. Cards belong to collections. Each collection has member roles: owner, contributor, viewer. |
| `cards` | MYO cards. Each card has a home collection; can be linked to others. Status: `draft` / `ready`. |
| `card_tracks` | Audio tracks per card. Includes `metadata_embedded` boolean. |
| `publish_records` | Per-card, per-Yoto-account publish state. Cascade-deletes with card. |
| `share_links` | Time-limited label share links. HMAC token in path for aggressive rate limiting. |
| `jobs` | SQLite-backed job queue. Survives restarts. Per-track retry with order preservation. |

### Collections model

Collections are the primary organising concept. "All Cards" is a virtual UI filter, not a real collection. Every card must be created in an explicit collection.

A card has one home collection (ownership/deletion anchor) and can be linked to additional collections. Removing from the home collection deletes the card; removing from a linked collection removes the link only.

## Go package layout

| Package | Purpose |
|---|---|
| `cmd/` | Executable entry points: `serve`, `gen-openapi`, etc. |
| `internal/api` | HTTP handlers, huma registration, middleware |
| `internal/auth` | Session store, local auth, OIDC, rate limiter |
| `internal/card` | Card domain logic, path manager, YAML export |
| `internal/db` | SQLite open, sqlc-generated queries |
| `internal/config` | Environment variable config (CLI flags > env vars > defaults) |
| `internal/job` | SQLite-backed job queue and worker pool |

## Frontend architecture

Astro generates a static multi-page application embedded into the Go binary at build time via `//go:embed all:frontend/dist`. The Go server serves static files with long-lived cache headers for hashed `/_astro/*` assets.

Interactive components (card editor, collection browser, publish matrix) are Svelte 5 islands hydrated with `client:load`. The Tailwind + shadcn-svelte component library provides accessible UI primitives.

## Container build

Three-stage OCI build:

1. **Node (frontend):** `npm ci && npm run build` → `frontend/dist/`
2. **Go (binary):** `CGO_ENABLED=0 go build` with embedded frontend
3. **Alpine (runtime):** binary + ffmpeg. Database on a mounted volume.

## Observability

`GET /metrics` exposes Prometheus-format metrics behind token auth. Metrics cover card count, publish rates, storage, job queue depth, and errors. No anonymous telemetry or phone-home — adoption is tracked via container registry downloads only. Structured logging via Go `slog` (JSON format).
