2.9 KiB
| title | status | type | priority | created_at | updated_at |
|---|---|---|---|---|---|
| Implement D1Repository for Cloudflare Workers | completed | feature | high | 2026-03-10T23:32:10Z | 2026-03-10T23:32:10Z |
Goal
Implement all 7 stub methods in src/bin/api/db/d1.rs so the API works against Cloudflare D1 in production.
Changes Required
src/bin/api/db/d1.rs
1. Add unsafe Send/Sync (wasm32 is single-threaded; safe in practice):
// SAFETY: wasm32-unknown-unknown is single-threaded.
unsafe impl Send for D1Repository {}
unsafe impl Sync for D1Repository {}
These are required so D1Repository satisfies the Arc<dyn QuoteRepository + Send + Sync> bound used in the Axum state.
2. Helper row structs (serde::Deserialize, field names = SQL column names):
QuoteRow { id, text, author, source: Option<String>, date: Option<String>, created_at, updated_at }AuthRow { auth_code: String }TagRow { tag: String }CountRow { count: u32 }
3. Helper method fetch_tags(&self, id: &str) -> Result<Vec<String>, DbError>:
SELECT tag FROM quote_tags WHERE quote_id = ?1 ORDER BY tag, bind with JsValue::from_str(id), deserialise as Vec<TagRow>.
4. Implement each QuoteRepository method:
-
run_migrations: Call
self.db.exec()for each migration constant frommigrations::in sequence (CREATE_QUOTES, CREATE_QUOTE_TAGS, CREATE_TAG_INDEX, CREATE_AUTHOR_INDEX). -
list_quotes(page, author, tag): Dynamic SQL with positional params (mirror native.rs logic). Run COUNT query for total_count, then data query with LIMIT 10 / OFFSET. Fetch tags per quote via helper. Page size = 10.
-
get_quote(id):
SELECT ... FROM quotes WHERE id = ?1. Use.first::<QuoteRow>(None). Fetch tags. ReturnOk(None)if missing. -
get_random_quote:
SELECT ... FROM quotes ORDER BY RANDOM() LIMIT 1. Use.first::<QuoteRow>(None). -
create_quote(input):
generate_id(),auth_code = input.auth_code.unwrap_or_else(generate_auth_code)- INSERT quotes row (bind NULL for optional source/date with
JsValue::NULL) - Batch INSERT tags via
db.batch() - SELECT back the row to get timestamps
- Return
(quote, auth_code)
-
update_quote(id, input, auth_code):
- SELECT auth_code — return Forbidden on mismatch
- Build dynamic SET clause for non-None fields +
updated_at = datetime('now') - Execute UPDATE
- If tags provided: DELETE existing, batch INSERT new
- SELECT updated row; return it
-
delete_quote(id, auth_code):
- SELECT auth_code — return NotFound if absent, Forbidden on mismatch
- DELETE FROM quotes (tags cascade)
- Return
DeleteResult::Deleted
JsValue bindings: JsValue::from_str(s) for strings, JsValue::from_f64(n as f64) for integers, JsValue::NULL for SQL NULL.
Validation
cargo build --release --bin api --target wasm32-unknown-unknown
cargo fmt && cargo check && cargo clippy && cargo test