4.7 KiB
QuotesDB — Finalized Design
Date: 2026-02-27
Database Schema
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/randommust be registered beforeGET /api/quotes/:id.
Request/Response Shapes
PUT /api/quotes — Create
Request body (auth_code optional — generated if omitted):
{
"text": "...",
"author": "...",
"source": "Stanford Commencement 2005",
"tags": ["motivation"],
"date": "2005-06-12",
"auth_code": "ocean-table-purple-storm"
}
Response 201 Created:
{
"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:
{
"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:
{ "error": "not found" }
GET /api/quotes/random — Random Quote
Response 200 OK: the quote object.
Response 404 Not Found (empty database):
{ "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:
{ "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
quotestable. - Provided via
X-Auth-Codeheader 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