# quotesdb API — Architecture ## Overview Single Axum router binary (`src/bin/api/main.rs`) that serves a quotes REST API. Targets both: - **Native** (host): Axum + Tokio + rusqlite — for local dev and integration tests - **wasm32-unknown-unknown**: workers-rs + Cloudflare D1 — for production Cloudflare Workers ## Component structure ``` src/bin/api/ ├── main.rs # Entry point — opens DB, wires router, starts server (native only) ├── db/ │ ├── mod.rs # QuoteRepository trait + ListResult/DeleteResult/DbError types │ ├── migrations.rs # SQL DDL strings (CREATE TABLE IF NOT EXISTS) │ ├── native.rs # NativeRepository: tokio-rusqlite implementation │ ├── connection.rs # open() helper — opens SQLite with WAL and FK pragma │ └── d1.rs # D1Repository: workers-rs stub (wasm32 only) └── handlers/ └── mod.rs # All 7 route handlers + router() factory function ``` ## DB layer The `QuoteRepository` trait abstracts over two backends: | Target | Implementation | Storage | |--------|---------------|---------| | Native (`not(wasm32)`) | `NativeRepository` | Local SQLite via tokio-rusqlite | | wasm32 | `D1Repository` | Cloudflare D1 via workers-rs | On native targets, the trait uses `async_trait` (Send-capable futures), allowing `Arc` to be used as Axum state. On wasm32, the trait uses `async_trait(?Send)` because D1Database wraps JS values that aren't Send. ## Request flow (native) ``` TCP :3000 → Axum router → handler fn ↓ State> ↓ NativeRepository.method() ↓ tokio-rusqlite → SQLite file ``` ## Auth model No user accounts. Each quote has an `auth_code` (4-word passphrase from EFF word list), generated at creation time and returned once in the creation response. Stored plaintext in `quotes.auth_code`. For update and delete, the caller supplies the auth code in the `X-Auth-Code` request header. A mismatch returns `403 Forbidden`. ## OpenAPI spec `api/openapi.yaml` is the source of truth. `build.rs` converts it to JSON at compile time, writing to `$OUT_DIR/openapi.json`. The `GET /api/` handler serves it via `include_str!`. ## Router ordering `GET /api/quotes/random` is registered **before** `GET /api/quotes/:id` to prevent "random" being matched as an ID.