-- CreateTable CREATE TABLE "mail_logs" ( "id" TEXT NOT NULL, "tenant_id" TEXT, "scope" TEXT NOT NULL DEFAULT 'tenant', "to" TEXT NOT NULL, "template" TEXT NOT NULL, "locale" TEXT NOT NULL DEFAULT 'de', "status" TEXT NOT NULL DEFAULT 'pending', "provider_message_id" TEXT, "error" TEXT, "attempts" INTEGER NOT NULL DEFAULT 0, "dedupe_key" TEXT, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "sent_at" TIMESTAMP(3), CONSTRAINT "mail_logs_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "notification_preferences" ( "id" TEXT NOT NULL, "tenant_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "event_type" TEXT NOT NULL, "email" BOOLEAN NOT NULL DEFAULT true, "locale" TEXT, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP(3) NOT NULL, CONSTRAINT "notification_preferences_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "mail_logs_dedupe_key_key" ON "mail_logs"("dedupe_key"); -- CreateIndex CREATE INDEX "mail_logs_tenant_id_created_at_idx" ON "mail_logs"("tenant_id", "created_at"); -- CreateIndex CREATE INDEX "mail_logs_status_idx" ON "mail_logs"("status"); -- CreateIndex CREATE INDEX "notification_preferences_tenant_id_idx" ON "notification_preferences"("tenant_id"); -- CreateIndex CREATE UNIQUE INDEX "notification_preferences_user_id_event_type_key" ON "notification_preferences"("user_id", "event_type"); -- ============================================================================ -- Row Level Security (SEC1) -- -- Muster wie seit F-04 (20260730160000_rls_enforce): ENABLE + FORCE + Policy mit -- USING *und* WITH CHECK. Ohne WITH CHECK koennte die eingeschraenkte App-Rolle -- `isms_app` unter FORCE keine Zeilen mehr anlegen. -- -- mail_logs.tenant_id ist NULLABLE (Plattform-Mails ohne Mandantenbezug). Die -- Policy ist trotzdem identisch zu audit_logs, das dieselbe Situation hat: -- Zeilen mit tenant_id = NULL erfuellen die Bedingung nie und sind fuer `isms_app` -- weder les- noch schreibbar. Das ist beabsichtigt — Plattform-Zeilen werden -- ausschliesslich ueber den rohen `prisma`-Client (Owner-Rolle, BYPASSRLS) -- geschrieben, genau wie writePlatformAudit(). -- -- GRANT ist noetig, weil die pauschalen Rechte aus 20260702095816 nur fuer die -- damals existierenden Tabellen galten; ALTER DEFAULT PRIVILEGES greift nur fuer -- Objekte, die von der dort gesetzten Rolle erzeugt werden. -- ============================================================================ GRANT SELECT, INSERT, UPDATE, DELETE ON "mail_logs" TO isms_app; GRANT SELECT, INSERT, UPDATE, DELETE ON "notification_preferences" TO isms_app; ALTER TABLE "mail_logs" ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS tenant_isolation ON "mail_logs"; CREATE POLICY tenant_isolation ON "mail_logs" USING ("tenant_id" = current_setting('app.tenant_id', true)) WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true)); ALTER TABLE "mail_logs" FORCE ROW LEVEL SECURITY; ALTER TABLE "notification_preferences" ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS tenant_isolation ON "notification_preferences"; CREATE POLICY tenant_isolation ON "notification_preferences" USING ("tenant_id" = current_setting('app.tenant_id', true)) WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true)); ALTER TABLE "notification_preferences" FORCE ROW LEVEL SECURITY;