Zum Hauptinhalt springen

Datenbank-Migrationen

Konvention

Migrationen werden von Drizzle Kit aus den Schema-Definitionen generiert und liegen unter drizzle/migrations/.

Zusätzlich existiert drizzle/deploy.sql — ein idempotentes Full-Schema-Script für Erstinstallationen.

Tools

  • Drizzle Kit (v0.31.9) — Schema-Diff, Migration-Generation, Migration-Ausführung
  • Drizzle ORM (v0.45.1) — Typsicherer DB-Client mit postgres Driver

Workflow

Schema ändern

  1. Schema-Dateien unter src/server/db/schema/ bearbeiten:
DateiInhalt
auth.tsAuth.js-Tabellen (users, accounts, sessions)
tenant.tsTenants, Profiles, Settings, Invitations
production.tsSuppliers, Lots, Machines, Profiles, Barrels, etc.
b2b.tsCustomers, Orders, Invoices, Pricing, Forecasts, Subscriptions
shopify.tsShopify-Mirror-Tabellen
shipping.tsDHL Shipments, Labels, Tracking
admin.tsPlatform Admin (Impersonation, Audit)
order-form.tsB2B-Dealer-Portal
enums.tsPostgreSQL Enum-Definitionen
  1. Migration generieren:

    npx drizzle-kit generate
  2. Generierte SQL-Datei unter drizzle/migrations/ prüfen

  3. Migration anwenden (lokal oder beim Build):

    npx drizzle-kit migrate

Build-Hook

Migrationen werden automatisch beim Build ausgeführt:

"build": "drizzle-kit migrate && next build"

Das stellt sicher, dass die Datenbank bei jedem Deployment aktuell ist.

Manuelle SQL (RLS, Trigger, Functions)

Für komplexe Migrationen, die Drizzle Kit nicht generieren kann (RLS-Policies, Trigger, Functions), manuell SQL in die generierte Migrationsdatei einfügen oder eine separate .sql-Datei unter drizzle/migrations/ anlegen.

Checkliste für neue Tabellen

Jede neue Tenant-Tabelle MUSS alle 6 Sicherheitsschichten haben:

-- 1. Tabelle erstellen
CREATE TABLE my_table (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
-- ... weitere Spalten
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ -- Falls Soft-Delete nötig
);

-- 2. RLS aktivieren
ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;

-- 3. Policies
CREATE POLICY "select" ON my_table FOR SELECT
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);

CREATE POLICY "insert" ON my_table FOR INSERT
WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid);

CREATE POLICY "update" ON my_table FOR UPDATE
USING (tenant_id = current_setting('app.current_tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid);

CREATE POLICY "delete" ON my_table FOR DELETE
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);

-- 4. Immutable tenant_id
CREATE TRIGGER prevent_tenant_change
BEFORE UPDATE ON my_table
FOR EACH ROW
WHEN (OLD.tenant_id IS DISTINCT FROM NEW.tenant_id)
EXECUTE FUNCTION raise_immutable_error();

-- 5. Auto-Update updated_at
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON my_table
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();

-- 6. Indizes
CREATE INDEX idx_my_table_tenant ON my_table(tenant_id);
CREATE INDEX idx_my_table_active ON my_table(tenant_id)
WHERE deleted_at IS NULL; -- Falls Soft-Delete

-- 7. Cross-Tenant FK Trigger (falls FKs auf andere Domain-Tabellen)
CREATE OR REPLACE FUNCTION validate_my_table_fk_tenants()
RETURNS TRIGGER AS $$
BEGIN
-- Prüfe FK 1
IF EXISTS (SELECT 1 FROM other_table WHERE id = NEW.other_id AND tenant_id != NEW.tenant_id) THEN
RAISE EXCEPTION 'Cross-tenant FK violation';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER validate_fks
BEFORE INSERT OR UPDATE OF other_id ON my_table
FOR EACH ROW
EXECUTE FUNCTION validate_my_table_fk_tenants();

Datentyp-Regeln

RegelRichtigFalsch
Geld / GewichteNUMERIC(10,2)FLOAT, REAL, DOUBLE PRECISION
ZeitstempelTIMESTAMPTZTIMESTAMP
IDsUUIDSERIAL, INTEGER
Status-WertePostgreSQL ENUMTEXT mit CHECK
Boolesche FlagsBOOLEAN NOT NULL DEFAULT falseTEXT ('yes'/'no')

Rollback

Es gibt kein automatisches Rollback. Für destruktive Migrationen:

  1. Immer einen Rollback-Plan in der Migration als Kommentar dokumentieren
  2. Destruktive Operationen (DROP, ALTER TYPE) erfordern Nutzer-Bestätigung
  3. Daten-Migrationen: Zuerst additive Änderung, dann Daten migrieren, dann alte Spalte entfernen (in separaten Migrationen)