db(cards): baseline migration + drizzle-tracking bootstrap script
Some checks are pending
CI / validate (push) Waiting to run

Schließt die Ops-Lücke „kein versioniertes Schema-Tracking" aus
FEATURE_IDEAS.md.

* apps/api/src/db/migrations/0000_baseline.sql — Drizzle-generierte
  Baseline-Migration, 355 Zeilen, 25 Tabellen + 5 Enums (cards- und
  marketplace-Schema). Eingefrostet auf den Live-Stand 2026-05-12.
* apps/api/scripts/bootstrap-drizzle-tracking.ts — neues Script,
  markiert die Baseline in einer bestehenden DB als „bereits
  angewandt", ohne SQL erneut auszuführen. Verwendet sha256 wie
  drizzle-orm/migrator (Hash 312d67ba1aeb…), idempotent.
* package.json: drizzle:migrate + drizzle:bootstrap-tracking
  npm-scripts.
* docs/playbooks/DRIZZLE_MIGRATIONS_BOOTSTRAP.md — Hand-Over für
  Prod (Bootstrap einmalig, dann normaler Workflow:
  schema → generate → commit → migrate, kein push --force mehr).

Lokal verifiziert: 17/104 Tests grün, bootstrap idempotent,
drizzle-kit migrate erkennt die Baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-12 18:53:52 +02:00
parent 5a29dd9a8c
commit 4bb1390180
7 changed files with 3523 additions and 5 deletions

View file

@ -0,0 +1,155 @@
# Drizzle-Migrationen — Bootstrap und Workflow
Stand: 2026-05-12.
Cards hat bis 2026-05-12 alle Schema-Änderungen über
`drizzle-kit push --force` gefahren — Schema-Sync ohne versionierte
Migrationen. Live ist das gefährlich: kein Audit-Trail, keine
sichere Rollback-Story, schwer reviewable.
Ab Commit `<dieser>` gibt es eine **versionierte Migration-Welt** mit
einer Baseline (`0000_baseline.sql`), die das bestehende Schema
festfriert.
---
## Was lokal schon passiert ist
1. `pnpm drizzle:generate``apps/api/src/db/migrations/0000_baseline.sql`
(25 Tabellen, 5 Enums, alle FKs/Indizes — das gesamte cards- und
marketplace-Schema, eingefroren auf den Stand 2026-05-12).
2. `apps/api/scripts/bootstrap-drizzle-tracking.ts` (neues npm-script
`pnpm drizzle:bootstrap-tracking`) — markiert in einer existierenden
Live-DB die Baseline als „bereits angewandt", ohne SQL erneut
auszuführen.
3. Lokal verifiziert: Bootstrap ist idempotent, `pnpm drizzle:migrate`
erkennt die Migration danach als bekannt.
---
## Was du auf der Prod-Box machen musst (einmalig)
```bash
ssh mana-server
cd ~/projects/cards
git pull --ff-only origin main
```
### 1. Bootstrap auf der Live-DB ausführen
```bash
# DATABASE_URL aus der env-Datei lesen, nicht hart codieren.
DB_PW=$(grep CARDS_DB_PASSWORD infrastructure/.env.production | cut -d= -f2-)
docker exec -e DATABASE_URL="postgresql://cards:${DB_PW}@cards-postgres:5432/cards" \
cards-api node /app/apps/api/scripts/bootstrap-drizzle-tracking.ts
```
…**oder** wenn das im Container-Image-Setup nicht klappt (Script ist
TypeScript, der API-Container kommt mit Bun):
```bash
# Alternative: vom Host aus über Container-Network ausführen.
# Mac Mini hat Bun installiert.
cd ~/projects/cards/apps/api
DATABASE_URL="postgresql://cards:${DB_PW}@127.0.0.1:5436/cards" \
bun run scripts/bootstrap-drizzle-tracking.ts
```
**Erwartete Ausgabe:**
```
MARKED 0000_baseline (hash 312d67ba1aeb…)
Tracking-Tabelle hat jetzt 1 Eintrag/Einträge.
```
Wenn statt `MARKED` schon `SKIP` kommt: jemand hat das Script bereits
gelaufen lassen. Idempotent → kein Problem.
### 2. Verifizieren
```bash
docker exec cards-postgres psql -U cards -d cards -c \
"SELECT * FROM drizzle.__drizzle_migrations;"
```
Erwartete Antwort: eine Zeile mit Hash `312d67ba1aeb…` und
`created_at = 1778604624860`.
### 3. Smoke-Test
```bash
cd ~/projects/cards/apps/api
DATABASE_URL="postgresql://cards:${DB_PW}@127.0.0.1:5436/cards" \
bun x drizzle-kit migrate
```
Erwartet: `[✓] migrations applied successfully!` ohne dass das
Schema wirklich angefasst wird (es kommt nur ein NOTICE
„relation '__drizzle_migrations' already exists, skipping").
---
## Künftiger Workflow
### Neue Schema-Änderung
1. **Editiere das Schema** in `apps/api/src/db/schema/**.ts`.
2. **Generiere die Migration** lokal:
```bash
cd apps/api
pnpm drizzle:generate --name <kurz_beschreibend>
```
3. **Inspiziere** das generierte SQL in `src/db/migrations/0001_<name>.sql`
(oder höher) — bei destruktiven Änderungen (DROP COLUMN, ALTER
TYPE) ggf. von Hand patchen, damit Daten nicht verloren gehen.
4. **Commit** Schema-Files + Migration-Files + Journal zusammen:
```bash
git add apps/api/src/db/schema apps/api/src/db/migrations
git commit -m "db(cards): <was>"
```
5. **Test lokal**:
```bash
DATABASE_URL="postgresql://cards:cards@localhost:5435/cards" \
pnpm drizzle:migrate
```
### Deploy
1. `git push` auf Forgejo.
2. Auf der Box: `git pull --ff-only`.
3. **Migration anwenden** (kein push --force mehr!):
```bash
cd ~/projects/cards/apps/api
DATABASE_URL="postgresql://cards:${DB_PW}@127.0.0.1:5436/cards" \
bun x drizzle-kit migrate
```
4. Erst danach Container rebuilden + restarten, wenn der neue Code
auf das neue Schema angewiesen ist.
---
## Tabu
- **`drizzle-kit push --force` auf Prod nie wieder.** Das Script
bleibt in `package.json` als Dev-Convenience, aber für Prod ist
nur `drizzle-kit migrate` der Weg.
- **Migration-Files nicht nachträglich umschreiben**, sobald sie
in einem Prod-Pull-Stand sind — der Hash würde sich ändern und
Drizzle würde sie erneut anwenden wollen.
- **Wenn du das Schema lokal mit `push` schon „weiter" als die
Migration hast**, ist das ein Drift-Bug. Lösung: lokal die DB
rückbauen (`drop schema cards cascade; drop schema marketplace cascade`),
dann Migrationen frisch anwenden. Niemals diesen Drift in einen
Commit hineinwachsen lassen.
---
## Bei Problemen
- `pnpm drizzle:migrate` schmeißt „relation already exists" und
bricht ab? Bootstrap-Script ist nie gelaufen — Schritt 1 oben.
- Hash-Mismatch (Migration angeblich „neu" trotz Bootstrap)? Das
Migration-File wurde nach dem Bootstrap noch ediert. Entweder
Edit rückgängig oder den Tracking-Eintrag von Hand auf den neuen
Hash bringen — ausschließlich nach Code-Review.