From 775761929c3861492dc556c6f8e8ee2bc0269ef2 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Fri, 27 Feb 2026 22:01:49 -0800 Subject: [PATCH] chore(quotesdb): bootstrap project skeleton and design doc - Scaffold api/, ui/, tests/, infra/, docs/ directories - Stub Cargo.toml for api, ui, and tests crates - Write finalized design doc to docs/plans/2026-02-27-quotesdb-design.md - Add placeholder PLANNING.md, ARCHITECTURE.md, README.md per domain - Add stub main.rs and tests.rs for api and ui - Add index.html and Trunk.toml for ui - Add placeholder infra/main.tf with Cloudflare provider stub Co-Authored-By: Claude Sonnet 4.6 --- quotesdb/.nbd/tickets/d6ba23.md | 7 + quotesdb/api/Cargo.toml | 14 ++ quotesdb/api/README.md | 31 +++ quotesdb/api/docs/ARCHITECTURE.md | 9 + quotesdb/api/docs/PLANNING.md | 9 + quotesdb/api/src/main.rs | 1 + quotesdb/api/src/tests.rs | 2 + quotesdb/docs/ARCHITECTURE.md | 23 +++ quotesdb/docs/PLANNING.md | 19 ++ .../docs/plans/2026-02-27-quotesdb-design.md | 193 ++++++++++++++++++ quotesdb/infra/main.tf | 11 + quotesdb/tests/Cargo.toml | 5 + quotesdb/tests/README.md | 25 +++ quotesdb/tests/docs/PLANNING.md | 9 + quotesdb/ui/Cargo.toml | 14 ++ quotesdb/ui/README.md | 31 +++ quotesdb/ui/Trunk.toml | 2 + quotesdb/ui/docs/ARCHITECTURE.md | 9 + quotesdb/ui/docs/PLANNING.md | 9 + quotesdb/ui/index.html | 9 + quotesdb/ui/src/main.rs | 1 + quotesdb/ui/src/tests.rs | 2 + 22 files changed, 435 insertions(+) create mode 100644 quotesdb/.nbd/tickets/d6ba23.md create mode 100644 quotesdb/api/Cargo.toml create mode 100644 quotesdb/api/README.md create mode 100644 quotesdb/api/docs/ARCHITECTURE.md create mode 100644 quotesdb/api/docs/PLANNING.md create mode 100644 quotesdb/api/src/main.rs create mode 100644 quotesdb/api/src/tests.rs create mode 100644 quotesdb/docs/ARCHITECTURE.md create mode 100644 quotesdb/docs/PLANNING.md create mode 100644 quotesdb/docs/plans/2026-02-27-quotesdb-design.md create mode 100644 quotesdb/infra/main.tf create mode 100644 quotesdb/tests/Cargo.toml create mode 100644 quotesdb/tests/README.md create mode 100644 quotesdb/tests/docs/PLANNING.md create mode 100644 quotesdb/ui/Cargo.toml create mode 100644 quotesdb/ui/README.md create mode 100644 quotesdb/ui/Trunk.toml create mode 100644 quotesdb/ui/docs/ARCHITECTURE.md create mode 100644 quotesdb/ui/docs/PLANNING.md create mode 100644 quotesdb/ui/index.html create mode 100644 quotesdb/ui/src/main.rs create mode 100644 quotesdb/ui/src/tests.rs diff --git a/quotesdb/.nbd/tickets/d6ba23.md b/quotesdb/.nbd/tickets/d6ba23.md new file mode 100644 index 0000000..317225b --- /dev/null +++ b/quotesdb/.nbd/tickets/d6ba23.md @@ -0,0 +1,7 @@ ++++ +title = "Bootstrap quotesdb project skeleton" +priority = 8 +status = "in_progress" +ticket_type = "task" +dependencies = ["ec118c"] ++++ diff --git a/quotesdb/api/Cargo.toml b/quotesdb/api/Cargo.toml new file mode 100644 index 0000000..0cc0ec8 --- /dev/null +++ b/quotesdb/api/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "quotesdb-api" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +common = { path = "../../common" } + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 diff --git a/quotesdb/api/README.md b/quotesdb/api/README.md new file mode 100644 index 0000000..007462d --- /dev/null +++ b/quotesdb/api/README.md @@ -0,0 +1,31 @@ +# quotesdb-api + +Rust/Axum backend for the quotesdb quotes service, deployed as a Cloudflare Worker. + +## What + +A JSON REST API for creating, browsing, and managing quotes. Each quote is identified by a NanoID and protected by a 4-word passphrase auth code. + +## How + +Built with Axum on Tokio, targeting Cloudflare Workers via `workers-rs`. Data is stored in Cloudflare D1 (SQLite-compatible) in production and Turso file-backed SQLite locally. Queries use SQLx. + +## Run + +```sh +cargo run +``` + +## Test + +```sh +cargo fmt && cargo check && cargo clippy && cargo test +``` + +## License + +Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option. + +## Disclaimer + +This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6). diff --git a/quotesdb/api/docs/ARCHITECTURE.md b/quotesdb/api/docs/ARCHITECTURE.md new file mode 100644 index 0000000..46dfa68 --- /dev/null +++ b/quotesdb/api/docs/ARCHITECTURE.md @@ -0,0 +1,9 @@ +# quotesdb-api — Architecture + +## Component Overview + +_To be filled in during Phase 1 planning._ + +## Component Interactions + +_To be filled in during Phase 1 planning._ diff --git a/quotesdb/api/docs/PLANNING.md b/quotesdb/api/docs/PLANNING.md new file mode 100644 index 0000000..0b0d73f --- /dev/null +++ b/quotesdb/api/docs/PLANNING.md @@ -0,0 +1,9 @@ +# quotesdb-api — Planning + +## Development Phases + +_To be filled in during Phase 1 planning._ + +## Work Log + +_Updated as work progresses._ diff --git a/quotesdb/api/src/main.rs b/quotesdb/api/src/main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/quotesdb/api/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/quotesdb/api/src/tests.rs b/quotesdb/api/src/tests.rs new file mode 100644 index 0000000..5c6016f --- /dev/null +++ b/quotesdb/api/src/tests.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +mod tests {} diff --git a/quotesdb/docs/ARCHITECTURE.md b/quotesdb/docs/ARCHITECTURE.md new file mode 100644 index 0000000..019222d --- /dev/null +++ b/quotesdb/docs/ARCHITECTURE.md @@ -0,0 +1,23 @@ +# quotesdb — Architecture + +## Component Overview + +| Component | Directory | Description | +|-----------|-----------|-------------| +| API | `api/` | Rust/Axum backend on Cloudflare Workers. Handles all data operations via SQLx and Cloudflare D1. | +| UI | `ui/` | Yew (Rust/Wasm) frontend on Cloudflare Pages. Communicates with the API over HTTP. | +| Tests | `tests/` | Integration test crate. Spins up the API against in-memory SQLite and makes real HTTP requests. | +| Infra | `infra/` | OpenTofu configuration for Cloudflare Worker, D1 database, and Pages project. | + +## Component Interactions + +``` +Browser + └──> Cloudflare Pages (quotesdb-ui) + └──> Cloudflare Workers (quotesdb-api) + └──> Cloudflare D1 (SQLite) +``` + +The UI is a static Wasm bundle served from Cloudflare Pages. It makes fetch requests to the Worker API, which reads and writes to a D1 database bound to the Worker. + +Integration tests bypass the UI and talk directly to the API over HTTP, using a local in-memory SQLite database. diff --git a/quotesdb/docs/PLANNING.md b/quotesdb/docs/PLANNING.md new file mode 100644 index 0000000..31d185d --- /dev/null +++ b/quotesdb/docs/PLANNING.md @@ -0,0 +1,19 @@ +# quotesdb — Planning + +## Development Phases + +### Phase 0: Design (complete) + +Finalized database schema, API endpoints, request/response shapes, auth model, and frontend routes. + +### Phase 1: Ticket Planning (pending) + +Four parallel planning agents create nbd tickets for each domain (api, ui, tests, infra). + +### Phase 2: Implementation (pending) + +Four parallel implementation orchestrators work through their domain tickets. + +## Work Log + +- **2026-02-27** — Phase 0 complete. Project skeleton bootstrapped. Design doc written. diff --git a/quotesdb/docs/plans/2026-02-27-quotesdb-design.md b/quotesdb/docs/plans/2026-02-27-quotesdb-design.md new file mode 100644 index 0000000..77b1d1e --- /dev/null +++ b/quotesdb/docs/plans/2026-02-27-quotesdb-design.md @@ -0,0 +1,193 @@ +# 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 diff --git a/quotesdb/infra/main.tf b/quotesdb/infra/main.tf new file mode 100644 index 0000000..2649914 --- /dev/null +++ b/quotesdb/infra/main.tf @@ -0,0 +1,11 @@ +# quotesdb infrastructure — OpenTofu / Cloudflare +# Placeholder: to be filled in during the infra planning phase. + +terraform { + required_providers { + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 4" + } + } +} diff --git a/quotesdb/tests/Cargo.toml b/quotesdb/tests/Cargo.toml new file mode 100644 index 0000000..460bee2 --- /dev/null +++ b/quotesdb/tests/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "quotesdb-tests" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" diff --git a/quotesdb/tests/README.md b/quotesdb/tests/README.md new file mode 100644 index 0000000..13560f4 --- /dev/null +++ b/quotesdb/tests/README.md @@ -0,0 +1,25 @@ +# quotesdb-tests + +Integration test suite for the quotesdb service. + +## What + +HTTP-level integration tests that spin up the API server against an in-memory SQLite database and verify all endpoints. + +## How + +Separate Rust crate with tests in `tests/`. Each test suite covers a distinct endpoint or behaviour. + +## Run + +```sh +cargo test +``` + +## License + +Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option. + +## Disclaimer + +This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6). diff --git a/quotesdb/tests/docs/PLANNING.md b/quotesdb/tests/docs/PLANNING.md new file mode 100644 index 0000000..cdc9565 --- /dev/null +++ b/quotesdb/tests/docs/PLANNING.md @@ -0,0 +1,9 @@ +# quotesdb-tests — Planning + +## Development Phases + +_To be filled in during Phase 1 planning._ + +## Work Log + +_Updated as work progresses._ diff --git a/quotesdb/ui/Cargo.toml b/quotesdb/ui/Cargo.toml new file mode 100644 index 0000000..08d3c40 --- /dev/null +++ b/quotesdb/ui/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "quotesdb-ui" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +common = { path = "../../common" } + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 diff --git a/quotesdb/ui/README.md b/quotesdb/ui/README.md new file mode 100644 index 0000000..2ebb94e --- /dev/null +++ b/quotesdb/ui/README.md @@ -0,0 +1,31 @@ +# quotesdb-ui + +Yew (Rust/Wasm) frontend for the quotesdb quotes service, hosted on Cloudflare Pages. + +## What + +A web UI for browsing, submitting, and managing quotes. Compiled to WebAssembly from Rust using the Yew framework. + +## How + +Built with Yew, compiled to `wasm32-unknown-unknown` via Trunk. Communicates with the quotesdb-api backend. + +## Run + +```sh +trunk serve +``` + +## Test + +```sh +cargo fmt && cargo check && cargo clippy && cargo test +``` + +## License + +Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option. + +## Disclaimer + +This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6). diff --git a/quotesdb/ui/Trunk.toml b/quotesdb/ui/Trunk.toml new file mode 100644 index 0000000..8d8f852 --- /dev/null +++ b/quotesdb/ui/Trunk.toml @@ -0,0 +1,2 @@ +[build] +target = "index.html" diff --git a/quotesdb/ui/docs/ARCHITECTURE.md b/quotesdb/ui/docs/ARCHITECTURE.md new file mode 100644 index 0000000..0b247e7 --- /dev/null +++ b/quotesdb/ui/docs/ARCHITECTURE.md @@ -0,0 +1,9 @@ +# quotesdb-ui — Architecture + +## Component Overview + +_To be filled in during Phase 1 planning._ + +## Component Interactions + +_To be filled in during Phase 1 planning._ diff --git a/quotesdb/ui/docs/PLANNING.md b/quotesdb/ui/docs/PLANNING.md new file mode 100644 index 0000000..54c10a9 --- /dev/null +++ b/quotesdb/ui/docs/PLANNING.md @@ -0,0 +1,9 @@ +# quotesdb-ui — Planning + +## Development Phases + +_To be filled in during Phase 1 planning._ + +## Work Log + +_Updated as work progresses._ diff --git a/quotesdb/ui/index.html b/quotesdb/ui/index.html new file mode 100644 index 0000000..77dd39a --- /dev/null +++ b/quotesdb/ui/index.html @@ -0,0 +1,9 @@ + + + + + QuotesDB + + + + diff --git a/quotesdb/ui/src/main.rs b/quotesdb/ui/src/main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/quotesdb/ui/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/quotesdb/ui/src/tests.rs b/quotesdb/ui/src/tests.rs new file mode 100644 index 0000000..5c6016f --- /dev/null +++ b/quotesdb/ui/src/tests.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +mod tests {}