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.
72 lines
2.9 KiB
Markdown
72 lines
2.9 KiB
Markdown
---
|
|
# quotesdb-k5rx
|
|
title: Implement D1Repository for Cloudflare Workers
|
|
status: completed
|
|
type: feature
|
|
priority: high
|
|
created_at: 2026-03-10T23:32:10Z
|
|
updated_at: 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):
|
|
```rust
|
|
// 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 from `migrations::` 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. Return `Ok(None)` if missing.
|
|
|
|
- **get_random_quote**: `SELECT ... FROM quotes ORDER BY RANDOM() LIMIT 1`. Use `.first::<QuoteRow>(None)`.
|
|
|
|
- **create_quote(input)**:
|
|
1. `generate_id()`, `auth_code = input.auth_code.unwrap_or_else(generate_auth_code)`
|
|
2. INSERT quotes row (bind NULL for optional source/date with `JsValue::NULL`)
|
|
3. Batch INSERT tags via `db.batch()`
|
|
4. SELECT back the row to get timestamps
|
|
5. Return `(quote, auth_code)`
|
|
|
|
- **update_quote(id, input, auth_code)**:
|
|
1. SELECT auth_code — return Forbidden on mismatch
|
|
2. Build dynamic SET clause for non-None fields + `updated_at = datetime('now')`
|
|
3. Execute UPDATE
|
|
4. If tags provided: DELETE existing, batch INSERT new
|
|
5. SELECT updated row; return it
|
|
|
|
- **delete_quote(id, auth_code)**:
|
|
1. SELECT auth_code — return NotFound if absent, Forbidden on mismatch
|
|
2. DELETE FROM quotes (tags cascade)
|
|
3. 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
|
|
```sh
|
|
cargo build --release --bin api --target wasm32-unknown-unknown
|
|
cargo fmt && cargo check && cargo clippy && cargo test
|
|
```
|