feat(api,web): eigene Module — Tabelle, API, Seitenleiste, Rahmen-Seite

- Tabelle CustomModule mit Zeilenschutz (tenant_isolation_policy), Migration 20260929120000
- API /custom-modules: Lesen für jeden Angemeldeten, Schreiben nur Administrator, nur https ohne Zugangsdaten
- Seitenleiste zeigt eigene Module unter ihrer Kategorie, Rahmen-Seite mit Sandbox und „In neuem Tab öffnen“
- MODULE_CATEGORIES als gemeinsame Liste, Zugriffsklassifikation nachgemessen fortgeschrieben

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 07:25:33 +02:00
parent 643b1a2caa
commit b9d87be360
23 changed files with 1270 additions and 40 deletions
@@ -0,0 +1,47 @@
-- 260929-9wc — Eigene Module: externe Seiten als Seitenleisten-Eintraege.
--
-- Zweck: neue Tabelle "CustomModule". Der Administrator legt Eintraege an
-- (Name, https-Adresse, Kategorie), alle Benutzer des Mandanten sehen sie in
-- der Seitenleiste und ein Klick zeigt die Seite im Rahmen. Mehrere Zeilen je
-- Mandant, Vorbild "ProxmoxServer" (tenantId-Spalte, keine Relation zu
-- Tenant).
--
-- Von Hand geschrieben (Vorbild 20260923140000_proxmox_server), von Hand
-- gepflegter Kopfkommentar Pflicht bei jeder RLS-Migration in diesem Projekt.
--
-- Zeilenschutz (Pflicht — sonst schlaegt rls-coverage.spec.ts fehl): die
-- Tabelle traegt `tenantId` und `tenant_isolation_policy` OHNE
-- Benutzerdimension (`USING ("tenantId" = current_tenant_id())`) — eigene
-- Module sind Verwaltungsdaten des Mandanten, nicht persoenliche Daten eines
-- einzelnen Benutzers.
--
-- BEWUSST KEINE `system_read_policy`: es gibt keinen Hintergrunddienst, der
-- eigene Module ueber alle Mandanten lesen muesste; jeder Zugriff laeuft
-- mandantengebunden ueber `forTenant(prisma, tenantId)`.
--
-- Rechte fuer die Anwendungsrolle tessera_app kommen automatisch ueber
-- ALTER DEFAULT PRIVILEGES aus 20260909130000_rls_app_role — hier nichts zu
-- tun.
--
-- WICHTIG: wie alle bisherigen RLS-Migrationen wirken diese Regeln erst,
-- wenn die Anwendung als Rolle ohne Umgehungsrecht verbindet (Schalter
-- heute AUS, siehe docs/mandantentrennung-datenbankrolle.md).
CREATE TABLE "CustomModule" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"url" TEXT NOT NULL,
"category" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "CustomModule_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "CustomModule_tenantId_idx" ON "CustomModule"("tenantId");
ALTER TABLE "CustomModule" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "CustomModule" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "CustomModule"
USING ("tenantId" = current_tenant_id());