2.5 KiB
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<dyn QuoteRepository> 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<Arc<dyn QuoteRepository>>
↓
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.