4.3 KiB
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.