From 7df983052ba21113e8e5bf15473daa7ccbf06d52 Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Tue, 4 Aug 2026 22:47:38 +0100 Subject: [PATCH] README: replace migration section with pre-launch recreate-the-schema policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The project is pre-launch: there is no production database and all dev starts from a fresh volume recreated from init-scripts/init-script.sql. The obsolete 'Database migrations' section (ALTER statements and apply-before-deploy notes for existing deployments) is replaced with the no-ALTER policy — schema changes are edited directly into the CREATE statements, no migration-managed delta exists, and the diff on the next recreate IS the migration. --- README.md | 69 +++++-------------------------------------------------- 1 file changed, 6 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 28e5796..9d34f82 100644 --- a/README.md +++ b/README.md @@ -105,71 +105,14 @@ The heavier static analyzers (`golangci-lint`, `staticcheck`, `gosec`) are **not CI caches Go modules (`~/go/pkg/mod`) and npm dependencies (`~/.npm`, `node_modules`) via `actions/cache` — keyed on `go.sum` and `package-lock.json` respectively. Cache is served by Gitea's built-in cache server at `git.popertots.com`. First run downloads everything (~3m35s), subsequent runs restore from cache in seconds. -### Database migrations +### Database schema policy (pre-launch — no ALTERs) -The schema lives in `init-scripts/init-script.sql` and is applied automatically on a **fresh** volume via `docker-entrypoint-initdb.d`. Existing deployments must apply the payment-system delta manually (the schema is not migration-managed): +The schema is single-source in `init-scripts/init-script.sql`, applied automatically on a **fresh** volume via `docker-entrypoint-initdb.d`. This project is **pre-launch**: there is no production database, and all dev work starts from a fresh DB recreation. Therefore: -```sql --- Refund system columns (refunds table) -ALTER TABLE refunds ADD COLUMN IF NOT EXISTS refund_attempts INT NOT NULL DEFAULT 0; -ALTER TABLE refunds ADD COLUMN IF NOT EXISTS origin VARCHAR(16) NOT NULL DEFAULT 'manual'; -ALTER TABLE refunds ADD COLUMN IF NOT EXISTS idempotency_key VARCHAR(64) UNIQUE; - --- refund_failed notification reason (admin_notification_reason enum) --- NOTE: ALTER TYPE ... ADD VALUE cannot run inside a transaction block; run on a connection with autocommit. -ALTER TYPE admin_notification_reason ADD VALUE IF NOT EXISTS 'refund_failed'; - --- Refunds may now reference non-booking payments (gift-card purchase refunds) -ALTER TABLE refunds ALTER COLUMN booking_id DROP NOT NULL; - --- Terminal checkout in-flight guard + payment_type passthrough (terminal_checkouts table) --- NOTE: CREATE TABLE is a fresh addition, not a column change. Apply before deploying --- the terminal-payment changes or CreateTerminalPayment/GetCheckoutStatus fail at runtime. -CREATE TABLE IF NOT EXISTS terminal_checkouts ( - checkout_id VARCHAR(64) PRIMARY KEY, - booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE, - payment_type payment_type NOT NULL DEFAULT 'full', - status VARCHAR(20) NOT NULL DEFAULT 'PENDING', - amount NUMERIC(10,2) NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); -CREATE INDEX IF NOT EXISTS idx_terminal_checkouts_booking ON terminal_checkouts(booking_id, status); -CREATE INDEX IF NOT EXISTS idx_terminal_checkouts_status ON terminal_checkouts(status); - --- Square webhook dedup (square_webhook_events table) --- NOTE: CREATE TABLE is a fresh addition, not a column change. Apply before deploying --- the webhook handler changes or the dedup INSERT fails at runtime. --- Required for the Square webhook dedup; without it webhooks fail closed 503. -CREATE TABLE IF NOT EXISTS square_webhook_events (event_id TEXT PRIMARY KEY, received_at TIMESTAMPTZ NOT NULL DEFAULT NOW()); -``` - -`backend/handlers/payments/refunds.go` casts `'refund_failed'::admin_notification_reason` (the sweep job in `internal/jobs/cleanup.go` only registers the handler), so an un-migrated DB fails at runtime — apply these before deploying the payment changes. - -#### Saved-card per-user uniqueness + Square customer provisioning (P14) - -The `user_saved_cards.square_card_id` UNIQUE constraint is now scoped **per user** (`UNIQUE (user_id, square_card_id)`), so the same physical card saved by two users produces two independent rows instead of user B mutating user A's saved-card row. Existing deployments must swap the constraint (the auto-generated constraint name is `user_saved_cards_square_card_id_key`): - -```sql -ALTER TABLE user_saved_cards DROP CONSTRAINT user_saved_cards_square_card_id_key; -ALTER TABLE user_saved_cards ADD CONSTRAINT user_saved_cards_user_id_square_card_id_key UNIQUE (user_id, square_card_id); -``` - -`square_customer_id TEXT` (nullable) was also added to `user_saved_cards` — populated the first time a user saves a card (Square customer provisioning, P14) and reused thereafter: - -```sql -ALTER TABLE user_saved_cards ADD COLUMN IF NOT EXISTS square_customer_id TEXT; -``` - -#### Saved-card Square references scrubbing (GDPR account anonymization) - -Account anonymization (`anonymize_user`, `delete_guest_user`, `AnonymizeStaleGuestAccounts`) now NULLs `square_card_id` / `square_customer_id` on `user_saved_cards` so external Square references are removed on erasure (Feature Catalog §9.2). This requires `square_card_id` to be nullable — existing deployments must apply: - -```sql -ALTER TABLE user_saved_cards ALTER COLUMN square_card_id DROP NOT NULL; -``` - -Fresh installs get the nullable column from `init-scripts/init-script.sql`; the schema is not migration-managed, so this one-liner is required for existing DBs before deploying the anonymization changes. +- **No `ALTER TABLE` / `ALTER TYPE` / `ADD VALUE` statements anywhere** — not in `init-script.sql`, not in tests, not in code. +- Any schema change is edited **directly into the `CREATE` statements** in `init-script.sql`. +- There is **no migration-managed delta** and no "apply before deploying" step. If a local dev DB needs updating, drop and recreate it (`docker compose down -v && docker compose up --build -d`), or apply the change by hand locally — never commit ALTERs. +- Do not document changes as migration snippets; the schema diff on the next recreate is the migration. ## Full Documentation