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.
169 lines
6.0 KiB
Markdown
169 lines
6.0 KiB
Markdown
---
|
|
# quotesdb-hmbh
|
|
title: Create infra/schema.sql — idempotent D1 schema for quotes and quote_tags
|
|
status: completed
|
|
type: task
|
|
priority: high
|
|
created_at: 2026-03-10T23:32:10Z
|
|
updated_at: 2026-03-10T23:32:18Z
|
|
blocked_by:
|
|
- quotesdb-3euj
|
|
- quotesdb-4tec
|
|
---
|
|
|
|
<context>
|
|
TRIAGE 5c0c64 resolved: the chosen D1 migration strategy is a **separate wrangler step**.
|
|
|
|
Production schema is applied once after `tofu apply` using:
|
|
|
|
```sh
|
|
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`.
|
|
</context>
|
|
|
|
<goal>
|
|
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.
|
|
</goal>
|
|
|
|
<implementation>
|
|
|
|
## 1. Create `infra/schema.sql`
|
|
|
|
```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:
|
|
```sh
|
|
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`
|
|
|
|
```sh
|
|
# 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
|
|
|
|
```sh
|
|
# 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.
|
|
</implementation>
|
|
|
|
<why-not-the-other-options>
|
|
|
|
**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.
|
|
|
|
</why-not-the-other-options>
|
|
|
|
<constraints>
|
|
- `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.
|
|
</constraints>
|
|
|
|
<validation>
|
|
This ticket has no Rust compilation artifact. Validate that the SQL is correct:
|
|
|
|
```sh
|
|
# 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
|
|
```
|
|
</validation>
|
|
|
|
<related-tickets>
|
|
- 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
|
|
</related-tickets>
|
|
|
|
<commit>
|
|
`feat(quotesdb): add infra/schema.sql with idempotent D1 schema for quotes and quote_tags`
|
|
</commit>
|