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.
vibed/quotesdb/.beans/quotesdb-hmbh--create-infra...

6.0 KiB

title status type priority created_at updated_at blocked_by
Create infra/schema.sql — idempotent D1 schema for quotes and quote_tags completed task high 2026-03-10T23:32:10Z 2026-03-10T23:32:18Z
quotesdb-3euj
quotesdb-4tec
TRIAGE 5c0c64 resolved: the chosen D1 migration strategy is a **separate wrangler step**.

Production schema is applied once after tofu apply using:

wrangler d1 execute quotesdb --file infra/schema.sql --remote

For local dev, the native main() calls repo.run_migrations() (rusqlite execute_batch). For tests, test setup calls NativeRepository::run_migrations() directly.

The D1 run_migrations() method in D1Repository (wasm32 path, ticket 00aff0) may still call CREATE TABLE IF NOT EXISTS defensively on startup — but the canonical provisioning path for production is the wrangler CLI command above, not a startup handler.

infra/schema.sql is the single source of truth for the SQL that wrangler applies. The Rust constants in db/migrations.rs (ticket 00aff0) contain the same SQL in split form suitable for the D1 prepare().run() API and rusqlite execute_batch.

Create `infra/schema.sql` — a self-contained, idempotent SQL file that provisions the full `quotesdb` schema on a blank D1 database in a single wrangler command.

1. Create infra/schema.sql

-- quotesdb D1 schema
-- =============================================================================
-- Apply to production D1:
--   wrangler d1 execute quotesdb --file infra/schema.sql --remote
--
-- Apply locally (wrangler dev):
--   wrangler d1 execute quotesdb --local --file infra/schema.sql
--
-- For native dev/test builds, NativeRepository::run_migrations() applies
-- equivalent SQL automatically via rusqlite on startup.
-- =============================================================================

-- Stores individual quotes. auth_code is the 4-word passphrase for edit/delete.
CREATE TABLE IF NOT EXISTS quotes (
  id         TEXT     PRIMARY KEY,            -- NanoID (~21 chars)
  text       TEXT     NOT NULL,
  author     TEXT     NOT NULL,
  source     TEXT,                            -- optional: book, speech, etc.
  date       TEXT,                            -- optional: ISO date YYYY-MM-DD
  auth_code  TEXT     NOT NULL,              -- 4-word passphrase, stored plaintext
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Join table linking quotes to zero or more tags. Cascades on quote deletion.
CREATE TABLE IF NOT EXISTS quote_tags (
  quote_id TEXT NOT NULL REFERENCES quotes(id) ON DELETE CASCADE,
  tag      TEXT NOT NULL,
  PRIMARY KEY (quote_id, tag)
);

2. Incremental migration convention (document in infra/README.md)

For future schema changes, create numbered migration files:

infra/migrations/
  001_initial.sql      -- (retroactive, same content as schema.sql)
  002_add_index.sql    -- future: e.g. CREATE INDEX IF NOT EXISTS ...

Apply individually:

wrangler d1 execute quotesdb --file infra/migrations/002_add_index.sql --remote

No automated migration tracking is needed at this project's scale.

3. Full deployment workflow to document in infra/README.md

# Step 1 — provision infrastructure (creates Worker, D1 database, Pages project)
cd infra/
tofu apply

# Step 2 — apply initial schema to D1 (run once after first apply)
cd ..
wrangler d1 execute quotesdb --file infra/schema.sql --remote

# Re-running step 2 is safe (CREATE TABLE IF NOT EXISTS).

4. Local dev workflow

# Start local API (applies schema automatically via NativeRepository::run_migrations())
cargo run
# or with a specific DB file:
DATABASE_URL=./dev.sqlite cargo run

No manual wrangler step needed for local dev.

null_resource local-exec (rejected): Provisioners are an OpenTofu anti-pattern. They don't re-run unless the resource is tainted, aren't tracked in state, are OS-dependent (requires wrangler installed on the CI runner at apply time), and hard to test. Breaking tofu apply idempotency is not worth the single-command convenience.

API startup migration for D1 (rejected): Cloudflare Workers spin up per-request via V8 isolates. Calling DDL (CREATE TABLE IF NOT EXISTS) on every request is wasteful and fragile. The native main() calls run_migrations() at startup because it runs as a real server, but the Workers handler does NOT. The D1 provisioning path must be a separate step.

- `infra/schema.sql` must use `CREATE TABLE IF NOT EXISTS` for idempotency — safe to re-run. - Schema must exactly match the design doc: NanoID PK, `auth_code` plaintext, optional `source` and `date`, CASCADE delete on `quote_tags`. - Do NOT run `wrangler d1 execute` inside OpenTofu (no `null_resource`). - `db/migrations.rs` (ticket 00aff0) contains equivalent SQL as Rust constants — keep in sync with `infra/schema.sql` manually when schema changes. This ticket has no Rust compilation artifact. Validate that the SQL is correct:
# Smoke-test the schema against a local SQLite file
sqlite3 /tmp/test_quotesdb.sqlite < infra/schema.sql
sqlite3 /tmp/test_quotesdb.sqlite ".tables"
# Should output: quote_tags  quotes

# Clean up
rm /tmp/test_quotesdb.sqlite
- Resolves dependency: TRIAGE 5c0c64 (D1 migrations strategy — wrangler step chosen) - Resolves dependency: TRIAGE 580e66 (DB migration strategy for Workers — same decision) - Blocked by: d0da0b (D1 resource — need database name confirmed as "quotesdb") - Informs: 75489a (migration workflow docs — documents the wrangler command from this ticket) - Informs: 00aff0 (DB abstraction — migrations.rs constants mirror this file's SQL) - Sub-project: quotesdb/infra `feat(quotesdb): add infra/schema.sql with idempotent D1 schema for quotes and quote_tags`