# QuotesDB — Finalized Design **Date:** 2026-02-27 --- ## Database Schema ```sql CREATE TABLE quotes ( id TEXT PRIMARY KEY, -- NanoID (~21 chars) text TEXT NOT NULL, author TEXT NOT NULL, source TEXT, -- optional: book, speech, etc. date TEXT, -- optional: ISO date YYYY-MM-DD auth_code TEXT NOT NULL, -- 4-word passphrase e.g. ocean-table-purple-storm created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE quote_tags ( quote_id TEXT NOT NULL REFERENCES quotes(id) ON DELETE CASCADE, tag TEXT NOT NULL, PRIMARY KEY (quote_id, tag) ); ``` --- ## API Endpoints | Method | Path | Description | Auth | |--------|------|-------------|------| | GET | `/api/` | OpenAPI spec (JSON) | None | | GET | `/api/quotes` | List quotes, 10/page. Query: `?page=N&author=X&tag=Y` | None | | GET | `/api/quotes/random` | Random quote | None | | GET | `/api/quotes/:id` | Get quote by NanoID | None | | PUT | `/api/quotes` | Create a quote | None (auth_code optional in body) | | POST | `/api/quotes/:id` | Update a quote | `X-Auth-Code` header | | DELETE | `/api/quotes/:id` | Delete a quote | `X-Auth-Code` header | > **Router order:** `GET /api/quotes/random` must be registered **before** `GET /api/quotes/:id`. --- ## Request/Response Shapes ### PUT /api/quotes — Create Request body (`auth_code` optional — generated if omitted): ```json { "text": "...", "author": "...", "source": "Stanford Commencement 2005", "tags": ["motivation"], "date": "2005-06-12", "auth_code": "ocean-table-purple-storm" } ``` Response `201 Created`: ```json { "quote": { "id": "V1StGXR8_Z5jdHi6B-myT", "text": "...", "author": "...", "source": "Stanford Commencement 2005", "tags": ["motivation"], "date": "2005-06-12", "created_at": "2026-02-27T00:00:00Z", "updated_at": "2026-02-27T00:00:00Z" }, "auth_code": "ocean-table-purple-storm" } ``` ### GET /api/quotes — List Response `200 OK`: ```json { "quotes": [...], "page": 1, "total_pages": 4, "total_count": 38 } ``` ### GET /api/quotes/:id — Get by ID Response `200 OK`: the quote object (without `auth_code`). Response `404 Not Found`: ```json { "error": "not found" } ``` ### GET /api/quotes/random — Random Quote Response `200 OK`: the quote object. Response `404 Not Found` (empty database): ```json { "error": "no quotes found" } ``` ### POST /api/quotes/:id — Update Request: same shape as create (fields to update). Auth: `X-Auth-Code` header required. Response `200 OK`: updated quote object. Response `403 Forbidden`: `{ "error": "forbidden" }` Response `404 Not Found`: `{ "error": "not found" }` ### DELETE /api/quotes/:id — Delete Auth: `X-Auth-Code` header required. Response `204 No Content`. Response `403 Forbidden`: `{ "error": "forbidden" }` Response `404 Not Found`: `{ "error": "not found" }` ### Error Responses All error responses use: ```json { "error": "message" } ``` With appropriate HTTP status codes. --- ## Auth - No user accounts. Each quote has an `auth_code` (4-word passphrase). - Auth codes are stored **plaintext** in the `quotes` table. - Provided via `X-Auth-Code` header for update and delete operations. - On mismatch: `403 Forbidden`. - Auth code is always returned in the create response body. - If not provided on create, the server generates a random 4-word passphrase. ### Passphrase Generation Use a curated wordlist of common English words. Generate 4 random words joined by hyphens, e.g. `ocean-table-purple-storm`. --- ## Frontend Routes (Yew) | Route | Page | |-------|------| | `/` | Home — random quote + "Browse all" link | | `/browse` | Paginated list with author/tag filter controls | | `/quotes/:id` | Single quote — view, edit (auth prompt), delete (auth prompt) | | `/author/:name` | All quotes by an author | | `/submit` | New quote submission form | --- ## Tech Stack | Layer | Technology | |-------|-----------| | Language | Rust | | Backend framework | Axum + Tokio | | Backend target | Cloudflare Workers (workers-rs) | | Database (prod) | Cloudflare D1 (SQLite-compatible) | | Database (local) | Turso file-backed SQLite | | Query layer | SQLx | | Frontend framework | Yew | | Frontend compile target | wasm32-unknown-unknown | | Frontend build tool | Trunk | | Frontend hosting | Cloudflare Pages | | Infrastructure | OpenTofu + Cloudflare provider | --- ## Infrastructure (Cloudflare) - **Worker:** `quotesdb-api` — handles all API requests - **D1 Database:** bound to the Worker as `DB` - **Pages:** `quotesdb-ui` — serves the Wasm frontend - **Custom domain:** `quotes.elijah.run` — pointing to the Pages project