# quotesdb — Architecture ## Component Overview | Component | Path | Description | |-----------|------|-------------| | API binary | `src/bin/api/main.rs` | Rust/Axum backend on Cloudflare Workers. Handles all data operations via SQLx and Cloudflare D1. | | UI binary | `src/bin/ui/main.rs` | Yew (Rust/Wasm) frontend on Cloudflare Pages. Communicates with the API over HTTP. | | Shared library | `src/lib.rs` | Types and utilities shared between api and ui binaries. Must compile for both host and wasm32. | | Integration tests | `tests/` | Standard Cargo integration tests. Spin up the API against in-memory SQLite and make real HTTP requests. | | Infra | `infra/` | OpenTofu configuration for Cloudflare Worker, D1 database, and Pages project. | ## Component Interactions ``` Browser └──> Cloudflare Pages (ui binary compiled to Wasm) └──> Cloudflare Workers (api binary) └──> 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. ## Build Targets | Artifact | Command | Compile target | |----------|---------|----------------| | API server | `cargo run` or `cargo build` | host (native) | | UI Wasm bundle | `trunk serve` or `trunk build` | `wasm32-unknown-unknown` | | Tests | `cargo test` | host (native) | ## Shared Code Constraints `src/lib.rs` must compile for **both** `wasm32-unknown-unknown` (ui) and the host target (api). Avoid: - Threading primitives (`std::thread`, `std::sync::Mutex`) - Filesystem access (`std::fs`) - Any API that is not available in a Wasm environment Use `#[cfg(not(target_arch = "wasm32"))]` and `#[cfg(target_arch = "wasm32")]` guards where needed. ## Rate Limiting Rate limiting is enforced at the Cloudflare WAF layer using a `cloudflare_ruleset` resource with `phase = "http_ratelimit"` (see `infra/rate-limits.tf`). All limits are keyed on the client IP address (`ip.src`). Blocked requests receive a Cloudflare-generated `429 Too Many Requests` response before the Worker is invoked. ### Configured Limits | Endpoint | Method | Limit | Window | Mitigation timeout | |----------|--------|-------|--------|--------------------| | `PUT /api/quotes` | PUT | 5 requests | 10 minutes | 10 minutes | | `POST /api/quotes/:id/report` | POST | 3 requests | 1 hour | 1 hour | | `POST /api/quotes/:id` | POST | 10 requests | 1 minute | 1 minute | | `DELETE /api/quotes/:id` | DELETE | 10 requests | 1 minute | 1 minute | GET endpoints are not rate-limited at the WAF layer — Cloudflare's CDN caches most read responses, making per-IP GET limits unnecessary. ### Rule Ordering Cloudflare evaluates `http_ratelimit` rules top-to-bottom with first-match semantics. The `POST /api/quotes/:id/report` rule is placed **before** the general `POST /api/quotes/:id` rule to prevent the broader pattern from matching report requests. ### Plan Requirements The Cloudflare free tier allows **1 custom rate limiting rule per zone**. This configuration uses a single `cloudflare_ruleset` resource with multiple `rules` blocks, which counts as one ruleset but consumes multiple rule slots. Verify the account's plan limits before applying — a Pro or higher plan is recommended for the full four-rule set. ### Layered Protection WAF rate limiting complements (not replaces) Turnstile CAPTCHA on the submission form. CAPTCHA handles bot detection for quote creation; rate limiting enforces hard caps across all mutating endpoints including updates, deletes, and abuse reports. ## Key Dependency Versions Resolved versions for the UI Wasm target (scoped to `[target.'cfg(target_arch = "wasm32")'.dependencies]`): | Crate | Version | Notes | |-------|---------|-------| | `yew` | `"0.22"` | Latest stable (0.22.1). | | `yew-router` | `"0.19"` | Latest stable (0.19.0). Requires `yew ^0.22.0` — compatible. | | `wasm-bindgen` | `"0.2"` | Compatible with `wasm-bindgen-cli 0.2.108` in the Nix dev shell. | Rationale: yew-router 0.19 explicitly requires `yew ^0.22.0`, making this the only correct combination with the latest stable Yew. The `^0.2` wasm-bindgen constraint in both crates is satisfied by the Nix-pinned `0.2.108`.