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.
- infra/turnstile.tf: provision Turnstile widget (managed mode, quotes.elijah.run domain) with site_key and secret_key outputs - infra/variables.tf: add var.domain (default: quotes.elijah.run) - src/lib.rs: add cf_turnstile_token: Option<String> (#[serde(default)]) to CreateQuoteInput; update doctest - Cargo.toml: add reqwest (0.12, rustls-tls) under native-only dependencies - src/bin/api/handlers/mod.rs: add verify_turnstile() and CAPTCHA gate in create_handler, both gated on #[cfg(not(target_arch = "wasm32"))] - src/bin/api/db/native.rs: add cf_turnstile_token: None to all CreateQuoteInput struct literals in tests - api/openapi.yaml: document cf_turnstile_token field in QuoteCreateRequest schema - index.html: add Turnstile JS script tag - src/bin/ui/pages/submit.rs: add turnstile_token state, use_effect_with callback registration, widget div, token included in CreateQuoteInput - docs/LOCAL_DEV.md: add Cloudflare Turnstile CAPTCHA section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
3 months ago | |
|---|---|---|
| .. | ||
| .gitignore | 3 months ago | |
| README.md | 3 months ago | |
| d1.tf | 3 months ago | |
| dns.tf | 3 months ago | |
| main.tf | 3 months ago | |
| pages.tf | 3 months ago | |
| providers.tf | 3 months ago | |
| schema.sql | 3 months ago | |
| turnstile.tf | 3 months ago | |
| variables.tf | 3 months ago | |
| worker.tf | 3 months ago | |
README.md
quotesdb Infrastructure
OpenTofu configuration for deploying quotesdb to Cloudflare.
Resources provisioned
| Resource | Description |
|---|---|
cloudflare_d1_database.quotesdb |
D1 SQLite database backing the API |
cloudflare_workers_script.api |
Compiled Wasm Worker serving /api/* |
cloudflare_worker_route.api |
Routes quotes.elijah.run/api/* to the Worker |
cloudflare_pages_project.ui |
Pages project hosting the Yew SPA |
cloudflare_record.ui |
CNAME quotes.elijah.run → Pages |
cloudflare_pages_domain.ui |
Custom domain binding on Pages |
Required credentials
| Variable | Description |
|---|---|
TF_VAR_cloudflare_api_token |
Cloudflare API token (Workers, D1, Pages, DNS edit) |
TF_VAR_cloudflare_account_id |
Cloudflare account ID |
TF_VAR_cloudflare_zone_id |
Zone ID for elijah.run |
Export these before running tofu:
export TF_VAR_cloudflare_api_token="..."
export TF_VAR_cloudflare_account_id="..."
export TF_VAR_cloudflare_zone_id="..."
First-time setup (chicken-and-egg)
D1 must exist before the Worker can bind to it. On the very first deploy:
cd infra/
tofu init
tofu apply -target=cloudflare_d1_database.quotesdb
wrangler d1 execute quotesdb --file schema.sql --remote
tofu apply
Subsequent deploys: CI/CD handles everything automatically.
Local apply
cd quotesdb/infra/
tofu init
tofu plan
tofu apply
State
State is stored locally in terraform.tfstate (gitignored). For a team setup, migrate to a remote backend (S3-compatible bucket, Terraform Cloud, etc.).
Files
| File | Purpose |
|---|---|
main.tf |
Terraform block and provider version constraints |
providers.tf |
Cloudflare provider configuration |
variables.tf |
Input variable declarations |
d1.tf |
Cloudflare D1 database resource |
worker.tf |
Cloudflare Workers script + route |
pages.tf |
Cloudflare Pages project |
dns.tf |
DNS record and custom domain binding |
schema.sql |
Idempotent D1 schema (applied via wrangler, not tofu) |
.gitignore |
Ignores state, lock, and credential files |