You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

62 lines
2.5 KiB
Markdown

# 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.