+++ title = "quotesdb" priority = 8 status = "todo" ticket_type = "project" dependencies = ["ce1e4f", "f3dc74", "c3503b", "25c413"] +++ REQUIRED: Use `superpowers:executing-plans` to implement this plan task-by-task. SUGGESTED: Use `superpowers:dispatching-parallel-agents` for parallel agent dispatch. SUGGESTED: Use `superpowers:subagent-driven-development` for agent dispatch within a session. SUGGESTED: Use `superpowers:verification-before-completion` before marking any phase complete. SUGGESTED: Use `superpowers:finishing-a-development-branch` when ready to merge. SUGGESTED: Use `superpowers:using-git-worktrees` for parallel work in isolated branches. SUGGESTED: Use `superpowers:test-driven-development` during development. Bootstrap the `quotesdb` service (Rust/Axum backend on Cloudflare Workers + Yew frontend on Cloudflare Pages), then orchestrate parallel agents to plan and implement the work. All four sub-project tickets (`f3dc74` api, `c3503b` ui, `ce1e4f` qa, `25c413` infra) must be completed and closed before this ticket can close. A new `quotesdb` service is being added to the vibed mono-repo. The design was finalized in Phase 0. This plan covers three phases: 1. **Phase 0 (done):** Design finalized — see `docs/plans/2026-02-27-quotesdb-design.md` 2. **Phase 1:** Dispatch 4 parallel planning agents (each in a git worktree) to create nbd tickets per domain 3. **Phase 2:** Dispatch 4 parallel implementation orchestrators per domain **Tech Stack:** Rust, Axum, Tokio, workers-rs, SQLx, Turso (local) / Cloudflare D1 (prod), Yew, Trunk, OpenTofu, Cloudflare Workers + Pages ### 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 | **Note:** `GET /api/quotes/random` must be registered BEFORE `GET /api/quotes/:id` in the router. ### Request/Response Shapes **PUT /api/quotes** — Create ```json // 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 { "quote": { "id": "V1StG...", "text": "...", "author": "...", ... }, "auth_code": "ocean-table-purple-storm" } ``` **GET /api/quotes** — List ```json { "quotes": [...], "page": 1, "total_pages": 4, "total_count": 38 } ``` **Error responses:** `{ "error": "message" }` with appropriate HTTP status. ### Auth - No user accounts. Each quote has an `auth_code` (4-word passphrase). - Auth codes are stored **plaintext**. - Provided via `X-Auth-Code` header for update/delete. - On mismatch: `403 Forbidden`. - Auth code is always returned in the create response. ### Frontend Pages (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 form | ## Phase 1 — Parallel Planning Agents > **Required skill:** Use `superpowers:dispatching-parallel-agents` before dispatching. Dispatch 4 agents IN PARALLEL (single message, 4 Task tool calls). Each works in a dedicated git worktree on its own branch. Each agent's job: 1. Read the design doc at `docs/plans/2026-02-27-quotesdb-design.md` 2. Plan their domain thoroughly 3. Create nbd tickets for every piece of work (using `nbd create --ftype md --json`) 4. Commit their tickets on their branch ### Agent 1: api-planner **Branch:** `quotesdb/api` | **Directory:** `src/bin/api/` > You are a senior Rust backend engineer. Plan and ticket all backend API work for `quotesdb`. > > The backend is Rust + Axum + Tokio targeting Cloudflare Workers (workers-rs), with SQLx/Turso for dev and Cloudflare D1 in production. An auto-generated OpenAPI spec is required. > > Create one ticket per: Cargo.toml setup, database migrations, each endpoint handler, auth code generator, NanoID generator, pagination logic, tag filtering, OpenAPI spec generation, unit tests, and documentation files. ### Agent 2: ui-planner **Branch:** `quotesdb/ui` | **Directory:** `src/bin/ui/` > You are a senior Rust frontend engineer. Plan and ticket all frontend UI work for `quotesdb`. > > The frontend is Rust + Yew compiled to wasm32-unknown-unknown, built by Trunk, hosted on Cloudflare Pages. > > Create one ticket per: Cargo.toml + Trunk.toml setup, Yew app shell + routing, each page component (Home, Browse, Quote Detail, Author, Submit), API client module, auth code modal, pagination component, and documentation files. ### Agent 3: qa-planner **Branch:** `quotesdb/qa` | **Directory:** `tests/` > You are a senior QA engineer. Plan and ticket all integration test work for `quotesdb`. > > Tests live in `tests/` as Cargo integration tests, using a real HTTP client against a locally-spawned API server with a temporary SQLite database. > > Create one ticket per: dev-dependencies setup, test server harness, and one test suite per endpoint (list, get, random, create, update, delete, tags, router ordering). ### Agent 4: infra-planner **Branch:** `quotesdb/infra` | **Directory:** `infra/` > You are a Terraform/OpenTofu expert. Plan and ticket all infrastructure work for `quotesdb`. > > Resources needed: Cloudflare Worker (API), Cloudflare D1 database, Cloudflare Pages project, custom domain `quotes.elijah.run`. Use OpenTofu with the Cloudflare provider. > > Create one ticket per: OpenTofu project init, Workers script resource, D1 database resource, Pages project, custom domain, secrets docs, migration workflow docs, and infra README. --- ## Phase 2 — Parallel Implementation Orchestrators > **Required skill:** Use `superpowers:dispatching-parallel-agents` before dispatching. After all planning branches are merged, dispatch 4 orchestrator agents IN PARALLEL. Each orchestrator: 1. Lists all tickets for their domain (`nbd ready --json`) 2. Dispatches a sub-agent per ticket (one at a time — complete one before starting the next) 3. Reviews the sub-agent's work after each ticket (validate: `cargo fmt && cargo check && cargo clippy && cargo test`) 4. Continues to the next ticket **Domain orchestrators:** - **api-orchestrator** — works in `src/bin/api/`, dispatches 1 sub-agent per api ticket - **ui-orchestrator** — works in `src/bin/ui/`, dispatches 1 sub-agent per ui ticket - **qa-orchestrator** — works in `tests/`, dispatches 1 sub-agent per qa ticket - **infra-orchestrator** — works in `infra/`, dispatches 1 sub-agent per infra ticket Each sub-agent receives: design doc context, the specific ticket body, validation commands, and the conventional commit scope (`feat(quotesdb): ...`). After Phase 2 completes, verify the full stack: ```sh # From quotesdb/ cargo fmt && cargo check && cargo clippy && cargo test # Start API server locally cargo run # Build UI to Wasm trunk build # Dry-run infra (from infra/) tofu plan ``` Manual smoke test: ```sh # Create a quote curl -X PUT http://localhost:8787/api/quotes \ -H "Content-Type: application/json" \ -d '{"text":"Hello world","author":"Test"}' # List quotes curl http://localhost:8787/api/quotes # Random quote curl http://localhost:8787/api/quotes/random ```