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.

68 lines
2.8 KiB
Markdown

+++
title = "Implement D1Repository for Cloudflare Workers"
priority = 7
status = "done"
ticket_type = "feature"
dependencies = []
+++
## 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
```