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.
vibed/quotesdb/docs/plans/2026-02-27-quotesdb-design.md

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/random must be registered before GET /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 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