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 <noreply@anthropic.com>main
parent
cfcddefc80
commit
775761929c
@ -0,0 +1,7 @@
|
|||||||
|
+++
|
||||||
|
title = "Bootstrap quotesdb project skeleton"
|
||||||
|
priority = 8
|
||||||
|
status = "in_progress"
|
||||||
|
ticket_type = "task"
|
||||||
|
dependencies = ["ec118c"]
|
||||||
|
+++
|
||||||
@ -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
|
||||||
@ -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).
|
||||||
@ -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._
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# quotesdb-api — Planning
|
||||||
|
|
||||||
|
## Development Phases
|
||||||
|
|
||||||
|
_To be filled in during Phase 1 planning._
|
||||||
|
|
||||||
|
## Work Log
|
||||||
|
|
||||||
|
_Updated as work progresses._
|
||||||
@ -0,0 +1 @@
|
|||||||
|
fn main() {}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
||||||
@ -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.
|
||||||
@ -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.
|
||||||
@ -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
|
||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
[package]
|
||||||
|
name = "quotesdb-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
@ -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).
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# quotesdb-tests — Planning
|
||||||
|
|
||||||
|
## Development Phases
|
||||||
|
|
||||||
|
_To be filled in during Phase 1 planning._
|
||||||
|
|
||||||
|
## Work Log
|
||||||
|
|
||||||
|
_Updated as work progresses._
|
||||||
@ -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
|
||||||
@ -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).
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
[build]
|
||||||
|
target = "index.html"
|
||||||
@ -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._
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# quotesdb-ui — Planning
|
||||||
|
|
||||||
|
## Development Phases
|
||||||
|
|
||||||
|
_To be filled in during Phase 1 planning._
|
||||||
|
|
||||||
|
## Work Log
|
||||||
|
|
||||||
|
_Updated as work progresses._
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>QuotesDB</title>
|
||||||
|
<link data-trunk rel="rust" />
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
fn main() {}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
||||||
Loading…
Reference in New Issue