feat(quick-261003-387): Modulkategorien je Organisation - Tabellen, Grundbestand, Lese-Endpunkt bis in die Seitenleiste
- Tabellen ModuleCategory und ModuleCategoryPlacement mit Zeilenschutz, Spalte CustomModule.sortOrder - Dienst legt den Grundbestand beim ersten Lesen an, legt die wirksame Kategorie ueber Modullisten - GET /module-categories, PATCH :key (nur Administratoren), /modules/active liefert wirksame Kategorie - Seitenleiste ordnet Gruppen und Eintraege nach Kategorienspeicher, zeigt umbenannte Namen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
-- quick-261003-387 — Modulkategorien durch Administratoren bearbeitbar.
|
||||
--
|
||||
-- Zweck: zwei neue Tabellen und eine neue Spalte.
|
||||
-- * "ModuleCategory": die Kategorien einer Organisation (Kennung, optionaler
|
||||
-- eigener Name, Reihenfolge, Systemkennzeichen fuer "Eigene Module").
|
||||
-- Die Kennung ist unveraenderlich, sie steht als URL-Segment in
|
||||
-- /modules/<kennung>/<slug>.
|
||||
-- * "ModuleCategoryPlacement": Zuordnung eines Marktplatz-Moduls zu einer
|
||||
-- Kategorie der Organisation samt Reihenfolge. Die Spalte "Module"."category"
|
||||
-- (fuer alle Organisationen gleich) bleibt unveraendert; die wirksame
|
||||
-- Kategorie ist die Zuordnung, sonst diese Spalte.
|
||||
-- * "CustomModule"."sortOrder": Reihenfolge gemeinsamer eigener Module
|
||||
-- innerhalb ihrer Kategorie (NULL = noch nicht sortiert).
|
||||
-- Es gibt keine Rueckfuellung in SQL: der Dienst legt den Grundbestand je
|
||||
-- Organisation beim ersten Lesen an.
|
||||
--
|
||||
-- Von Hand geschrieben (Vorbild 20261002150000_nextcloud_status), von Hand
|
||||
-- gepflegter Kopfkommentar Pflicht bei jeder RLS-Migration in diesem Projekt.
|
||||
--
|
||||
-- Zeilenschutz (Pflicht — sonst schlaegt rls-coverage.spec.ts fehl): beide
|
||||
-- Tabellen tragen `tenantId` und `tenant_isolation_policy` OHNE
|
||||
-- Benutzerdimension (`USING ("tenantId" = current_tenant_id())`) — die
|
||||
-- Kategorien sind gemeinsame Einstellungen der Organisation, nicht
|
||||
-- persoenliche Daten eines Benutzers. Keine `system_read_policy`: es gibt
|
||||
-- keinen Hintergrunddienst, der darueber liest.
|
||||
--
|
||||
-- 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).
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "CustomModule" ADD COLUMN "sortOrder" INTEGER;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ModuleCategory" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isSystem" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ModuleCategory_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ModuleCategoryPlacement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"moduleId" TEXT NOT NULL,
|
||||
"categoryKey" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ModuleCategoryPlacement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ModuleCategory_tenantId_idx" ON "ModuleCategory"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ModuleCategory_tenantId_key_key" ON "ModuleCategory"("tenantId", "key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ModuleCategoryPlacement_tenantId_idx" ON "ModuleCategoryPlacement"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ModuleCategoryPlacement_tenantId_moduleId_key" ON "ModuleCategoryPlacement"("tenantId", "moduleId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ModuleCategoryPlacement" ADD CONSTRAINT "ModuleCategoryPlacement_moduleId_fkey" FOREIGN KEY ("moduleId") REFERENCES "Module"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Zeilenschutz
|
||||
ALTER TABLE "ModuleCategory" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "ModuleCategory" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "ModuleCategory"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
|
||||
ALTER TABLE "ModuleCategoryPlacement" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "ModuleCategoryPlacement" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "ModuleCategoryPlacement"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
@@ -120,6 +120,7 @@ model Module {
|
||||
updatedAt DateTime @updatedAt
|
||||
activations TenantModuleActivation[]
|
||||
grants ModuleGrant[]
|
||||
categoryPlacements ModuleCategoryPlacement[]
|
||||
}
|
||||
|
||||
model TenantModuleActivation {
|
||||
@@ -849,7 +850,11 @@ model CustomModule {
|
||||
tenantId String
|
||||
name String
|
||||
url String
|
||||
category String // eine der MODULE_CATEGORIES aus @tessera/shared
|
||||
category String // Kennung einer ModuleCategory der Organisation
|
||||
// quick-261003-387: Reihenfolge innerhalb der Kategorie (nur gemeinsame
|
||||
// Eintraege; null = noch nicht sortiert, steht hinten). Persoenliche
|
||||
// Eintraege bekommen nie eine sortOrder.
|
||||
sortOrder Int?
|
||||
// quick-260929-dzu: null = gemeinsamer Eintrag (vom Administrator, fuer alle
|
||||
// sichtbar); gesetzt = persoenlicher Eintrag, nur fuer diesen Benutzer
|
||||
// sichtbar. Faellt der Benutzer weg, fallen seine Eintraege mit.
|
||||
@@ -906,3 +911,39 @@ model WelcomeMailTemplate {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
// quick-261003-387: Modulkategorien je Organisation, durch Administratoren
|
||||
// pflegbar. `key` ist unveraenderlich (URL-Segment /modules/<key>/<slug>),
|
||||
// `name` null = Uebersetzung moduleCategories.<key>. isSystem nur fuer
|
||||
// "custom-modules" (Eigene Module): umbenennbar und verschiebbar, nicht
|
||||
// loeschbar.
|
||||
model ModuleCategory {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
key String
|
||||
name String?
|
||||
sortOrder Int @default(0)
|
||||
isSystem Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([tenantId, key])
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
// quick-261003-387: Zuordnung eines Marktplatz-Moduls zu einer Kategorie der
|
||||
// Organisation samt Reihenfolge. Wirksame Kategorie = Zuordnung, sonst
|
||||
// Module.category (die Spalte selbst bleibt fuer alle gleich).
|
||||
model ModuleCategoryPlacement {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
moduleId String
|
||||
module Module @relation(fields: [moduleId], references: [id], onDelete: Cascade)
|
||||
categoryKey String
|
||||
sortOrder Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([tenantId, moduleId])
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user