From 8fa4c870c74ff450de1efe549dc55b7f41757ed9 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Fri, 23 Jan 2026 21:05:15 +0100 Subject: [PATCH] upd --- .../analysis_and_implementation_plan.md | 852 ++++++++++++++++++ .../BACKEND_FEATURE_STRUCTURE_ANALYSIS.md | 367 -------- .../FEATURE_NAMING_CONVENTION.md | 122 --- .../PROGRESS_TRACKING.md | 358 -------- implementation/rbac_access_concept_done.md | 579 ++++++++++++ .../rbac_access_umsetzungskonzept done.md | 463 ++++++++++ .../trustee_feature_rbac_architecture.md | 687 ++++++++++++++ 7 files changed, 2581 insertions(+), 847 deletions(-) create mode 100644 implementation/Refactor Nyla UI basic components with new mandate SaaS model/analysis_and_implementation_plan.md delete mode 100644 implementation/Saas Multi Tenant Mandate/BACKEND_FEATURE_STRUCTURE_ANALYSIS.md delete mode 100644 implementation/Saas Multi Tenant Mandate/FEATURE_NAMING_CONVENTION.md delete mode 100644 implementation/Saas Multi Tenant Mandate/PROGRESS_TRACKING.md create mode 100644 implementation/rbac_access_concept_done.md create mode 100644 implementation/rbac_access_umsetzungskonzept done.md create mode 100644 implementation/trustee_feature_rbac_architecture.md diff --git a/implementation/Refactor Nyla UI basic components with new mandate SaaS model/analysis_and_implementation_plan.md b/implementation/Refactor Nyla UI basic components with new mandate SaaS model/analysis_and_implementation_plan.md new file mode 100644 index 0000000..f3db2f1 --- /dev/null +++ b/implementation/Refactor Nyla UI basic components with new mandate SaaS model/analysis_and_implementation_plan.md @@ -0,0 +1,852 @@ +# Refactor Nyla UI - Analyse und Implementation Plan + +## Datum: 23. Januar 2026 + +--- + +## Executive Summary + +Die Seiten **Chat Playground**, **Workflows**, **Automations**, **Prompts**, **Files** und **Connections** im neuen UI (`frontend_nyla`) sind inkomplett implementiert. Sie nutzen: +- Eigene, vereinfachte Table-Komponenten statt des etablierten `FormGenerator`-Systems +- Fehlende Dashboard- und Chat-Elemente (PlaygroundPage) +- Keine RBAC-Berechtigungsprüfung +- Keine Backend-Pagination/Sorting/Filtering +- Keine standardisierten CRUD-Modals + +### Design-Prinzipien für die Implementation + +> **WICHTIG:** Das bestehende Look & Feel von Nyla MUSS beibehalten werden! +> +> - Keine neuen Styles oder Design-Patterns einführen +> - Bestehende CSS-Module und Komponenten-Styles wiederverwenden +> - Nur **fehlende Logik ergänzen**, nicht das visuelle Design ändern +> - Neue Komponenten müssen sich nahtlos in das bestehende Design einfügen + +--- + +## 1. IST-Analyse + +### 1.1 Chat Playground (PlaygroundPage.tsx) + +**Aktueller Zustand:** +Die PlaygroundPage ist extrem vereinfacht und hat fast alle Funktionen aus dem alten UI (`frontend_agents`) verloren. + +**Was FEHLT (Vergleich zu `frontend_agents/public/htmlparts/part_workflow.html`):** + +| Element | Alt (frontend_agents) | Neu (frontend_nyla) | Ziel | +|---------|----------------------|---------------------|------| +| **Layout** | 70/30 Spalten (Chat/Dashboard) | Single-Column, kein Dashboard | **Resizable Spalten mit Drag-Divider** | +| **Dashboard** | Hierarchisches Dashboard mit Rounds → Operations → Logs | ❌ Komplett fehlt | Logik portieren, Nyla-Styles | +| **Progress-Tracking** | Progress-Bars pro Operation | ❌ Komplett fehlt | Logik portieren, Nyla-Styles | +| **Collapse/Expand** | Für Rounds, Operations, Logs | ❌ Komplett fehlt | Logik portieren | +| **Prompt-Auswahl** | Dropdown mit Prompts aus API | ❌ Fehlt | Bestehende DropdownSelect nutzen | +| **Workflow-Modus** | Dropdown mit Enum aus Backend | ❌ Fehlt | Bestehende DropdownSelect nutzen | +| **Datei-Upload** | Button + Drag & Drop, mehrere Dateien | ❌ Fehlt | Bestehende DragDropOverlay nutzen | +| **Voice Recording** | Mikrofon-Button mit STT | ❌ Fehlt | Logik portieren, Nyla Button-Styles | +| **Stop/Reset Buttons** | Stop, Reset, Refresh Tokens | Nur Läuft... Button | Bestehende Button-Komponenten | +| **Data Statistics** | Sent/Received/Time/Tokens | ❌ Fehlt | Logik portieren, Nyla-Styles | +| **File Preview Modal** | Vorschau, Download, Copy, TTS | ❌ Fehlt | Bestehende Popup-Komponente nutzen | +| **Unified Content Area** | Kombinierte Logs + Messages chronologisch | Getrennte Listen | Bestehende Log/Messages nutzen | +| **Auto-Scroll** | Automatisches Scrollen | ❌ Fehlt | Bestehende AutoScroll nutzen | +| **Empty State** | Mit Link zur Einführung | Einfacher Text | Nyla-Styles | + +**Vorhandene Hooks im `frontend_nyla/src/hooks/playground/`:** +``` +- useDashboardInputForm.ts +- useDashboardLogTree.ts +- useWorkflowLifecycle.ts +- useWorkflowOperations.ts +- useWorkflowPolling.ts +- useWorkflows.ts +- playgroundUtils.ts +``` + +**Problem:** Die Hooks sind vorhanden, aber die UI-Komponenten nutzen sie nicht vollständig! + +### 1.2 Workflows, Automations, Prompts, Files, Connections + +**Aktueller Zustand:** +Diese Seiten haben eigene, einfache Table-Implementierungen statt `FormGeneratorTable`. + +**Vergleich:** + +| Feature | AdminUsersPage (korrekt) | WorkflowsPage (falsch) | +|---------|--------------------------|------------------------| +| `FormGeneratorTable` | ✅ Verwendet | ❌ Eigene `` | +| `FormGeneratorForm` | ✅ Für Create/Edit Modals | ❌ Keine Modals | +| `attributes` vom Backend | ✅ Über Hook | ❌ Hardcoded Columns | +| `columns` aus attributes | ✅ Dynamisch generiert | ❌ Hardcoded | +| Backend-Pagination | ✅ Via hookData | ❌ Client-seitig | +| Backend-Sorting | ✅ Via hookData | ❌ Client-seitig | +| Backend-Filtering | ✅ Via hookData | ❌ Client-seitig | +| Inline-Editing | ✅ Für Boolean-Felder | ❌ Nicht unterstützt | +| RBAC Permissions | ✅ `permissions?.create !== 'n'` | ❌ Keine Prüfung | +| Action Buttons | ✅ Standard + Custom | ❌ Nur Delete | +| Loading State | ✅ Overlay | ❌ Einfacher Text | +| Empty State | ✅ Mit Icon und CTA | ❌ Einfacher Text | +| Error State | ✅ Mit Retry-Button | ❌ Einfacher Text | + +--- + +## 2. Vorhandene Ressourcen im frontend_nyla + +### 2.1 FormGenerator-System (vollständig implementiert) + +**Komponenten:** +- `FormGeneratorTable` - Tabelle mit Pagination, Sorting, Filtering +- `FormGeneratorForm` - Backend-driven Formulare +- `FormGeneratorList` - Listenansicht +- `FormGeneratorControls` - Such- und Filter-Controls +- Action Buttons: Edit, Delete, View, Copy, Custom + +**Features:** +- FK-Resolution (Foreign Keys anzeigen als Labels) +- TextMultilingual-Support +- Inline-Editing für Boolean-Felder +- Column-Resizing mit LocalStorage-Persistenz +- Multi-Column-Sorting + +### 2.2 Vorhandene Hooks + +**Workflows:** +- `useWorkflows.ts` - `useUserWorkflows()`, `useWorkflowOperations()` + +**Automations:** +- `useAutomations.ts` - `useAutomations()`, `useAutomationOperations()` + +**Prompts:** +- `usePrompts.ts` - `usePrompts()`, `usePromptOperations()` + +**Files:** +- `useFiles.ts` - `useUserFiles()`, `useFileOperations()` + +**Connections:** +- `useConnections.ts` - `useConnections()` (vollständig) + +**Playground-spezifisch:** +- `hooks/playground/useDashboardInputForm.ts` +- `hooks/playground/useDashboardLogTree.ts` +- `hooks/playground/useWorkflowLifecycle.ts` +- `hooks/playground/useWorkflowOperations.ts` +- `hooks/playground/useWorkflowPolling.ts` + +### 2.3 Bestehende Nyla UI-Komponenten (WIEDERVERWENDEN!) + +> **WICHTIG:** Diese Komponenten definieren das Nyla Look & Feel und sollten +> bei der Implementation wiederverwendet werden! + +**UiComponents (in `components/UiComponents/`):** +- `Button/` - Standard-Buttons, CreateButton, UploadButton +- `TextField/` - Input-Felder +- `DropdownSelect/` - Select-Komponente +- `DragDropOverlay/` - Für File-Upload mit Drag & Drop +- `Log/` - Log-Darstellung mit LogMessage +- `Messages/` - Chat-Nachrichten +- `Popup/` - Modal-Dialoge +- `Toast/` - Notifications +- `Tabs/` - Tab-Navigation +- `AutoScroll/` - Auto-Scroll-Funktionalität + +**Diese Komponenten-Styles übernehmen:** +- Farben (CSS-Variablen) +- Typografie +- Abstände/Padding +- Border-Radien +- Hover-/Focus-States + +--- + +## 3. Implementation Plan + +### Phase 1: Chat Playground (Priorität HOCH) + +#### 3.1.1 Layout-Refactoring + +**Datei:** `frontend_nyla/src/pages/workflows/PlaygroundPage.tsx` + +**Design-Prinzip:** Bestehendes Nyla Look & Feel beibehalten, nur fehlende Logik ergänzen! + +1. **Resizable Spalten-Layout mit Drag-Divider:** + +Statt eines fixen 70/30-Layouts wird ein **dynamisch anpassbares Layout** implementiert: +- Default: 70% Chat / 30% Dashboard +- Benutzer kann die Trennlinie mit der Maus verschieben +- Min/Max-Grenzen: Chat min 40%, Dashboard min 20% +- Spaltenbreite wird in LocalStorage persistiert + +```tsx +import { useResizablePanels } from '../../hooks/useResizablePanels'; + +const PlaygroundPage: React.FC = () => { + // Hook für resizable panels mit LocalStorage-Persistenz + const { + leftWidth, // in Prozent (default 70) + isDragging, + handleMouseDown, // Auf Divider anwenden + } = useResizablePanels({ + storageKey: 'playground-panel-width', + defaultLeftWidth: 70, + minLeftWidth: 40, + maxLeftWidth: 80, + }); + + return ( +
+
+
+ {/* Links - Chat Messages (dynamische Breite) */} +
+ +
+ + {/* Resizable Divider */} +
+
+
+ + {/* Rechts - Dashboard (dynamische Breite) */} +
+ +
+
+
+ +
+ + +
+
+ ); +}; +``` + +#### 3.1.1.1 Hook: useResizablePanels + +**Zu erstellen:** `frontend_nyla/src/hooks/useResizablePanels.ts` + +```typescript +import { useState, useCallback, useEffect, useRef } from 'react'; + +interface UseResizablePanelsOptions { + storageKey: string; + defaultLeftWidth: number; // in % + minLeftWidth: number; // in % + maxLeftWidth: number; // in % +} + +export const useResizablePanels = ({ + storageKey, + defaultLeftWidth, + minLeftWidth, + maxLeftWidth, +}: UseResizablePanelsOptions) => { + // Initialer Wert aus LocalStorage oder Default + const [leftWidth, setLeftWidth] = useState(() => { + const stored = localStorage.getItem(storageKey); + if (stored) { + const parsed = parseFloat(stored); + if (!isNaN(parsed) && parsed >= minLeftWidth && parsed <= maxLeftWidth) { + return parsed; + } + } + return defaultLeftWidth; + }); + + const [isDragging, setIsDragging] = useState(false); + const containerRef = useRef(null); + + // Mouse-Event-Handler + const handleMouseDown = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + setIsDragging(true); + containerRef.current = (e.target as HTMLElement).closest('.chatColumns'); + }, []); + + useEffect(() => { + if (!isDragging) return; + + const handleMouseMove = (e: MouseEvent) => { + if (!containerRef.current) return; + + const containerRect = containerRef.current.getBoundingClientRect(); + const newLeftWidth = ((e.clientX - containerRect.left) / containerRect.width) * 100; + + // Clamp zwischen min und max + const clampedWidth = Math.max(minLeftWidth, Math.min(maxLeftWidth, newLeftWidth)); + setLeftWidth(clampedWidth); + }; + + const handleMouseUp = () => { + setIsDragging(false); + // In LocalStorage speichern + localStorage.setItem(storageKey, leftWidth.toString()); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, [isDragging, leftWidth, storageKey, minLeftWidth, maxLeftWidth]); + + return { + leftWidth, + isDragging, + handleMouseDown, + setLeftWidth, + }; +}; +``` + +#### 3.1.1.2 CSS für Resizable Divider + +**In `Playground.module.css` hinzufügen:** + +```css +/* Resizable Divider - passend zum Nyla Design */ +.resizeDivider { + width: 6px; + cursor: col-resize; + background-color: transparent; + position: relative; + flex-shrink: 0; + z-index: 10; + transition: background-color 0.15s ease; +} + +.resizeDivider:hover, +.resizeDivider.dragging { + background-color: var(--color-border-hover, rgba(255, 255, 255, 0.1)); +} + +.dividerHandle { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 4px; + height: 40px; + border-radius: 2px; + background-color: var(--color-text-muted, rgba(255, 255, 255, 0.3)); + opacity: 0; + transition: opacity 0.15s ease; +} + +.resizeDivider:hover .dividerHandle, +.resizeDivider.dragging .dividerHandle { + opacity: 1; +} + +/* Verhindert Text-Selektion während Drag */ +.chatColumns.dragging { + user-select: none; +} +``` + +#### 3.1.2 Neue Komponenten erstellen + +> **Design-Prinzip:** Alle neuen Komponenten müssen das bestehende Nyla Look & Feel nutzen! +> - Bestehende CSS-Variablen verwenden (aus `:root` oder Theme) +> - Vorhandene Button-/Input-Styles wiederverwenden +> - Keine neuen Farbschemata oder Typografie einführen +> - Komponenten-Styles in Module-CSS kapseln + +**Zu erstellen in `frontend_nyla/src/components/Playground/`:** + +| Komponente | Beschreibung | Logik basiert auf | Styles | +|------------|--------------|-------------------|--------| +| `WorkflowDashboard.tsx` | Hierarchisches Dashboard | `workflowUiRendererDashboard.js` | Nyla-Styles | +| `UnifiedContentArea.tsx` | Kombinierte Logs + Messages | `workflowUiRenderer.js` | Nyla-Styles | +| `UserInputArea.tsx` | Prompt-Auswahl, Input, Buttons | `part_workflow.html` | Bestehende Nyla Input-Styles | +| `DataStatistics.tsx` | Sent/Received/Time/Tokens | `part_workflow.html` | Nyla-Styles | +| `FilePreviewModal.tsx` | Datei-Vorschau | Existiert teilweise | Bestehende Modal-Styles | +| `WorkflowControls.tsx` | Stop, Reset, Start Buttons | `workflowUiControls.js` | Bestehende Button-Styles | + +**Wichtig:** Diese Komponenten sollen die **Logik** aus dem alten `frontend_agents` übernehmen, +aber das **visuelle Design** von Nyla beibehalten. Das heisst: +- Hooks und State-Management aus `frontend_agents` portieren +- CSS-Styles aus den bestehenden Nyla-Komponenten verwenden/erweitern + +#### 3.1.3 Dashboard Implementation + +**State-Struktur:** +```typescript +interface DashboardLogTree { + rounds: Map; + rootOperations: string[]; + expanded: boolean; + isCompleted: boolean; + }>; + operations: Map; + parentId: string | null; + expanded: boolean; + latestProgress: number | null; + latestStatus: string | null; + roundNumber: number; + }>; + logExpandedStates: Map; + currentRound: number | null; +} +``` + +**Verwendung von existierendem Hook:** +```typescript +import { useDashboardLogTree } from '../../hooks/playground/useDashboardLogTree'; + +const { + dashboardLogTree, + processDashboardLogs, + toggleRoundExpanded, + toggleOperationExpanded, + clearDashboard, +} = useDashboardLogTree(); +``` + +#### 3.1.4 Input-Bereich Implementation + +**Neue Elemente:** +- Prompt-Dropdown (aus `usePrompts()`) +- Workflow-Modus-Dropdown (aus `/api/options/workflow.mode`) +- File-Upload mit Drag & Drop +- Voice-Recording-Button +- Stop/Reset/Start-Buttons + +### Phase 2-6: Data-Seiten (Workflows, Automations, Prompts, Files, Connections) + +> **Design-Prinzip für alle Data-Seiten:** +> - `FormGeneratorTable` und `FormGeneratorForm` verwenden (bereits Nyla-konform) +> - Bestehende Page-Header-Styles aus Admin-Seiten übernehmen +> - Keine neuen Layouts/Designs einführen +> - Nur fehlende Hooks/Logik ergänzen + +### Phase 2: Workflows Page (Priorität HOCH) + +#### 3.2.1 Hook erweitern + +**Datei:** `frontend_nyla/src/hooks/useWorkflows.ts` + +Erweitern um: +```typescript +export const useWorkflowsAdmin = () => { + const { data, loading, error, refetch } = useApi('/api/workflows/'); + + // Attributes vom Backend + const { data: attributesResponse } = useApi('/api/attributes/ChatWorkflow'); + + return { + data: data?.items || [], + attributes: attributesResponse?.attributes || [], + columns: generateColumns(attributesResponse?.attributes), + permissions: data?.permissions, + pagination: data?.pagination, + loading, + error, + refetch, + // Operations + handleDelete: async (id: string) => { /* ... */ }, + handleInlineUpdate: async (id: string, data: Partial) => { /* ... */ }, + }; +}; +``` + +#### 3.2.2 Page umschreiben + +**Datei:** `frontend_nyla/src/pages/workflows/WorkflowsPage.tsx` + +Nach Pattern von `AdminUsersPage.tsx`: + +```tsx +import { FormGeneratorTable } from '../../components/FormGenerator'; +import { useWorkflowsAdmin } from '../../hooks/useWorkflows'; + +export const WorkflowsPage: React.FC = () => { + const { + data: workflows, + attributes, + columns, + permissions, + pagination, + loading, + error, + refetch, + handleDelete, + handleInlineUpdate, + } = useWorkflowsAdmin(); + + return ( +
+ + , + onClick: handleContinue, + title: 'Workflow fortsetzen', + } + ]} + onDelete={handleDelete} + hookData={{ + refetch, + permissions, + pagination, + handleDelete, + handleInlineUpdate, + }} + /> +
+ ); +}; +``` + +### Phase 3: Automations Page + +**Analog zu Workflows Page:** +1. Hook erweitern: `useAutomationsAdmin()` +2. Page umschreiben mit `FormGeneratorTable` +3. Custom Actions: Execute, Toggle Active + +### Phase 4: Prompts Page + +**Spezifische Anpassungen:** +1. Hook erweitern: `usePromptsAdmin()` +2. Page umschreiben mit `FormGeneratorTable` +3. Create/Edit Modal mit `FormGeneratorForm` +4. Content-Feld als Textarea + +### Phase 5: Files Page + +**Spezifische Anpassungen:** +1. Hook erweitern: `useFilesAdmin()` +2. Page umschreiben mit `FormGeneratorTable` +3. Custom Actions: Download, Preview +4. Upload-Button mit `UploadButton`-Komponente + +### Phase 6: Connections Page + +**Spezifische Anpassungen:** +1. Hook erweitern: `useConnectionsAdmin()` +2. Page umschreiben mit `FormGeneratorTable` +3. Custom Actions: Connect, Refresh Token +4. Provider-spezifische Buttons (Google, Microsoft) + +--- + +## 4. Detaillierte Änderungsliste + +### 4.1 Zu erstellende Dateien + +``` +frontend_nyla/src/ +├── hooks/ +│ └── useResizablePanels.ts # NEU: Hook für resizable panels +├── components/ +│ └── Playground/ +│ ├── index.ts +│ ├── Playground.module.css # NEU: Layout-Styles inkl. Divider +│ ├── WorkflowDashboard/ +│ │ ├── index.ts +│ │ ├── WorkflowDashboard.tsx # Logik aus workflowUiRendererDashboard.js +│ │ ├── WorkflowDashboard.module.css +│ │ ├── DashboardRound.tsx +│ │ ├── DashboardOperation.tsx +│ │ └── DashboardProgressBar.tsx +│ ├── UnifiedContentArea/ +│ │ ├── index.ts +│ │ ├── UnifiedContentArea.tsx # Logik aus workflowUiRenderer.js +│ │ ├── UnifiedContentArea.module.css +│ │ ├── MessageItem.tsx +│ │ └── LogItem.tsx +│ ├── UserInputArea/ +│ │ ├── index.ts +│ │ ├── UserInputArea.tsx # Logik aus workflow.js + part_workflow.html +│ │ ├── UserInputArea.module.css +│ │ ├── PromptSelector.tsx +│ │ ├── WorkflowModeSelector.tsx +│ │ └── VoiceRecordButton.tsx +│ ├── WorkflowControls/ +│ │ ├── index.ts +│ │ ├── WorkflowControls.tsx # Logik aus workflowUiControls.js +│ │ └── WorkflowControls.module.css +│ └── DataStatistics/ +│ ├── index.ts +│ ├── DataStatistics.tsx +│ └── DataStatistics.module.css +``` + +> **Hinweis:** Alle `.module.css` Dateien sollen bestehende Nyla CSS-Variablen nutzen +> und sich nahtlos ins bestehende Design einfügen! + +### 4.2 Zu ändernde Dateien + +| Datei | Änderung | +|-------|----------| +| `pages/workflows/PlaygroundPage.tsx` | Kompletter Rewrite mit neuen Komponenten | +| `pages/workflows/WorkflowsPage.tsx` | Umschreiben auf FormGeneratorTable | +| `pages/workflows/AutomationsPage.tsx` | Umschreiben auf FormGeneratorTable | +| `pages/basedata/PromptsPage.tsx` | Umschreiben auf FormGeneratorTable | +| `pages/basedata/FilesPage.tsx` | Umschreiben auf FormGeneratorTable | +| `pages/basedata/ConnectionsPage.tsx` | Umschreiben auf FormGeneratorTable | +| `hooks/useWorkflows.ts` | Admin-Hook hinzufügen | +| `hooks/useAutomations.ts` | Admin-Hook hinzufügen | +| `hooks/usePrompts.ts` | Admin-Hook hinzufügen | +| `hooks/useFiles.ts` | Admin-Hook hinzufügen | +| `hooks/useConnections.ts` | Admin-Hook hinzufügen | + +### 4.3 CSS-Anpassungen + +**Neue CSS-Module:** +- `Playground.module.css` - Hauptlayout +- `WorkflowDashboard.module.css` - Dashboard-Styles +- `UnifiedContentArea.module.css` - Content-Styles + +**Bestehende zu aktualisieren:** +- `WorkflowPages.module.css` - Für alle Workflow-Seiten + +--- + +## 5. API-Endpunkte (bereits vorhanden) + +| Endpunkt | Verwendung | +|----------|------------| +| `/api/workflows/` | Liste, CRUD | +| `/api/workflows/{id}` | Einzelner Workflow | +| `/api/workflows/{id}/chat-data` | Unified Chat-Daten | +| `/api/automations/` | Liste, CRUD | +| `/api/prompts/` | Liste, CRUD | +| `/api/files/` | Liste, CRUD | +| `/api/connections/` | Liste, CRUD | +| `/api/attributes/{EntityType}` | Attribute-Definitionen | +| `/api/options/workflow.mode` | Workflow-Modus-Enum | + +--- + +## 6. Migrations-Checkliste + +### Chat Playground +- [ ] `useResizablePanels` Hook erstellen +- [ ] Resizable Spalten-Layout implementieren (mit Drag-Divider) +- [ ] WorkflowDashboard-Komponente erstellen (Logik aus frontend_agents, Nyla-Styles) +- [ ] UnifiedContentArea-Komponente erstellen (Logik aus frontend_agents, Nyla-Styles) +- [ ] UserInputArea mit allen Features erstellen (Nyla Input-Styles) +- [ ] DataStatistics-Komponente erstellen (Nyla-Styles) +- [ ] Polling integrieren (bestehende Hooks nutzen) +- [ ] File-Upload mit Drag & Drop (bestehende DragDropOverlay nutzen) +- [ ] Voice-Recording integrieren +- [ ] LocalStorage-Persistenz für Panel-Breite + +### Workflows Page +- [ ] Hook `useWorkflowsAdmin` erstellen +- [ ] Page auf FormGeneratorTable umstellen +- [ ] Action Buttons konfigurieren +- [ ] RBAC-Permissions implementieren + +### Automations Page +- [ ] Hook `useAutomationsAdmin` erstellen +- [ ] Page auf FormGeneratorTable umstellen +- [ ] Toggle Active implementieren +- [ ] Execute-Action implementieren + +### Prompts Page +- [ ] Hook `usePromptsAdmin` erstellen +- [ ] Page auf FormGeneratorTable umstellen +- [ ] Create/Edit Modals mit FormGeneratorForm +- [ ] Content-Textarea korrekt anzeigen + +### Files Page +- [ ] Hook `useFilesAdmin` erstellen +- [ ] Page auf FormGeneratorTable umstellen +- [ ] Download/Preview Actions +- [ ] Upload-Button integrieren + +### Connections Page +- [ ] Hook `useConnectionsAdmin` erstellen +- [ ] Page auf FormGeneratorTable umstellen +- [ ] Connect/Refresh Actions +- [ ] Provider-spezifische Buttons + +--- + +## 7. Schätzung Aufwand + +| Phase | Aufwand | Priorität | +|-------|---------|-----------| +| Phase 1: Chat Playground | Hoch | KRITISCH | +| Phase 2: Workflows Page | Mittel | Hoch | +| Phase 3: Automations Page | Mittel | Hoch | +| Phase 4: Prompts Page | Niedrig | Mittel | +| Phase 5: Files Page | Niedrig | Mittel | +| Phase 6: Connections Page | Niedrig | Mittel | + +--- + +## 8. Anhang: Pattern-Referenz + +### Standard-Page-Struktur + +```tsx +import React, { useState, useMemo } from 'react'; +import { FormGeneratorTable, FormGeneratorForm } from '../../components/FormGenerator'; +import { useEntityAdmin, useEntityOperations } from '../../hooks/useEntity'; +import styles from './Page.module.css'; + +interface Entity { + id: string; + [key: string]: any; +} + +export const EntityPage: React.FC = () => { + // Hooks + const { + data, + attributes, + columns, + permissions, + pagination, + loading, + error, + refetch, + fetchById, + handleInlineUpdate, + updateOptimistically, + } = useEntityAdmin(); + + const { + handleCreate, + handleUpdate, + handleDelete, + } = useEntityOperations(); + + // State + const [showCreateModal, setShowCreateModal] = useState(false); + const [editingItem, setEditingItem] = useState(null); + + // Permissions + const canCreate = permissions?.create !== 'n'; + const canUpdate = permissions?.update !== 'n'; + const canDelete = permissions?.delete !== 'n'; + + // Form Attributes + const formAttributes = useMemo(() => { + return (attributes || []).filter(attr => !['id'].includes(attr.name)); + }, [attributes]); + + // Handlers + const handleEditClick = async (item: Entity) => { + const fullItem = await fetchById(item.id); + if (fullItem) setEditingItem(fullItem); + }; + + const handleCreateSubmit = async (data: Partial) => { + const result = await handleCreate(data); + if (result.success) { + setShowCreateModal(false); + refetch(); + } + }; + + const handleEditSubmit = async (data: Partial) => { + if (!editingItem) return; + const result = await handleUpdate(editingItem.id, data); + if (result.success) { + setEditingItem(null); + refetch(); + } + }; + + // Render + return ( +
+ setShowCreateModal(true) : undefined} + /> + + + + {/* Create Modal */} + {showCreateModal && ( + setShowCreateModal(false)}> + setShowCreateModal(false)} + /> + + )} + + {/* Edit Modal */} + {editingItem && ( + setEditingItem(null)}> + setEditingItem(null)} + /> + + )} +
+ ); +}; +``` + +--- + +## 9. Design-Konsistenz-Checklist + +Bei jeder Implementation prüfen: + +- [ ] **CSS-Variablen:** Werden bestehende Nyla CSS-Variablen verwendet? (Farben, Abstände, etc.) +- [ ] **Komponenten:** Werden bestehende UiComponents wiederverwendet wo möglich? +- [ ] **Styles:** Sind neue Styles konsistent mit bestehendem Nyla-Design? +- [ ] **Keine Neuerungen:** Wurden keine neuen Design-Patterns eingeführt? +- [ ] **Dark/Light Mode:** Funktioniert die Komponente in beiden Modi? +- [ ] **Responsive:** Funktioniert die Komponente auf verschiedenen Bildschirmgrössen? + +--- + +*Dokumentation erstellt am 23. Januar 2026* +*Aktualisiert: Resizable Panels und Design-Prinzipien hinzugefügt* diff --git a/implementation/Saas Multi Tenant Mandate/BACKEND_FEATURE_STRUCTURE_ANALYSIS.md b/implementation/Saas Multi Tenant Mandate/BACKEND_FEATURE_STRUCTURE_ANALYSIS.md deleted file mode 100644 index f24e4e3..0000000 --- a/implementation/Saas Multi Tenant Mandate/BACKEND_FEATURE_STRUCTURE_ANALYSIS.md +++ /dev/null @@ -1,367 +0,0 @@ -# Backend Feature-Struktur Analyse - -## 1. Übersicht Aktuelle Architektur - -### 1.1 Feature-Hierarchie - -``` -Mandate (Mandant) - └── FeatureInstance (Feature-Instanz) - └── Feature-Datenbank-Tabellen - └── Datensätze mit mandateId + featureInstanceId -``` - -### 1.2 Datenbank-Struktur - -| Datenbank | Zweck | Feature | -|-----------|-------|---------| -| `poweron_app` | Zentrale App-Daten | System (User, Mandate, Roles, Features) | -| `poweron_trustee` | Treuhand-Feature | Trustee | -| `poweron_chat` | Chat-Feature | Chatbot/Workflow | -| `poweron_realestate` | Immobilien-Feature | RealEstate | - ---- - -## 2. Feature-Definitionen - -### 2.1 Datenmodelle (`datamodels/`) - -| Feature | Datenmodell-Datei | Tabellen | -|---------|-------------------|----------| -| **Trustee** | `datamodelTrustee.py` | TrusteeOrganisation, TrusteeRole, TrusteeAccess, TrusteeContract, TrusteeDocument, TrusteePosition, TrusteePositionDocument | -| **RealEstate** | `datamodelRealEstate.py` | RealEstateProperty, RealEstateUnit, etc. | -| **Chat/Workflow** | `datamodelChat.py`, `datamodelWorkflow.py` | ChatSession, ChatMessage, WorkflowExecution, etc. | -| **Files** | `datamodelFiles.py` | StoredFile, FileMetadata | -| **System** | `datamodelUam.py`, `datamodelMembership.py` | User, Mandate, FeatureAccess | -| **RBAC** | `datamodelRbac.py` | Role, AccessRule | -| **Features** | `datamodelFeatures.py` | Feature, FeatureInstance | - -### 2.2 Zentrale Feature-Modelle (`datamodelFeatures.py`) - -```python -class Feature: - code: str # PK: "trustee", "chatbot", "realestate" - label: dict # I18n: {"de": "Treuhand", "en": "Trustee"} - icon: str # "mdi-account-group" - -class FeatureInstance: - id: str # UUID - featureCode: str # FK → Feature.code - mandateId: str # FK → Mandate.id (CASCADE DELETE) - label: str # "Buchhaltung 2025" - enabled: bool # True/False -``` - ---- - -## 3. Interface-Schicht (`interfaces/`) - -### 3.1 Feature-spezifische Interfaces - -| Interface-Datei | Feature | Datenbank | -|-----------------|---------|-----------| -| `interfaceDbTrusteeObjects.py` | Trustee | poweron_trustee | -| `interfaceDbChatObjects.py` | Chatbot | poweron_chat | -| `interfaceDbRealEstateObjects.py` | RealEstate | poweron_realestate | -| `interfaceDbAppObjects.py` | System | poweron_app | -| `interfaceFeatures.py` | Feature-Verwaltung | poweron_app | - -### 3.2 Trustee-Interface Beispiel - -```python -class TrusteeObjects: - def __init__(self, currentUser, mandateId, featureInstanceId): - # Verbindet sich mit poweron_trustee Datenbank - self.mandateId = mandateId - self.featureInstanceId = featureInstanceId - # Initialisiert RBAC - self.rbac = RbacClass(...) - - # CRUD mit RBAC + Feature-Level Filterung - def createOrganisation(self, data): - data["mandateId"] = self.mandateId - data["featureInstanceId"] = self.featureInstanceId - ... -``` - -### 3.3 Multi-Tenant Isolation - -Jedes Interface: -1. Erhält `mandateId` und `featureInstanceId` aus dem Request-Context -2. Setzt diese Werte automatisch bei CREATE -3. Filtert bei READ/UPDATE/DELETE nach diesen Werten -4. Prüft RBAC-Berechtigungen (System + Feature-Level) - ---- - -## 4. Route-Schicht (`routes/`) - -### 4.1 Feature-Routes - -| Route-Datei | Prefix | Feature | -|-------------|--------|---------| -| `routeFeatures.py` | `/api/features` | Feature-Verwaltung | -| `routeFeatureTrustee.py` | `/api/trustee` | Trustee-CRUD | -| `routeFeatureChatbot.py` | `/api/chatbot` | Chatbot | -| `routeFeatureAutomation.py` | `/api/automation` | Workflow | -| `routeFeatureRealEstate.py` | `/api/realestate` | Immobilien | - -### 4.2 Trustee-Routen (mit instanceId im Pfad) ✅ - -``` -/api/trustee/{instanceId}/organisations GET, POST -/api/trustee/{instanceId}/organisations/{id} GET, PUT, DELETE -/api/trustee/{instanceId}/roles GET, POST -/api/trustee/{instanceId}/roles/{id} GET, PUT, DELETE -/api/trustee/{instanceId}/access GET, POST -/api/trustee/{instanceId}/access/{id} GET, PUT, DELETE -/api/trustee/{instanceId}/contracts GET, POST -/api/trustee/{instanceId}/contracts/{id} GET, PUT, DELETE -/api/trustee/{instanceId}/documents GET, POST -/api/trustee/{instanceId}/documents/{id} GET, PUT, DELETE -/api/trustee/{instanceId}/positions GET, POST -/api/trustee/{instanceId}/positions/{id} GET, PUT, DELETE -/api/trustee/{instanceId}/position-documents GET, POST, DELETE -``` - -**Validierung:** Jeder Request validiert automatisch: -1. Existenz der FeatureInstance -2. Zugehörigkeit zum Feature "trustee" -3. User-Zugriff auf die Instance (via FeatureAccess) - -### 4.3 Feature-Verwaltungs-Routen - -``` -/api/features/ GET, POST (SysAdmin) -/api/features/{code} GET -/api/features/instances GET, POST -/api/features/instances/{id} GET, DELETE -/api/features/instances/{id}/sync-roles POST -/api/features/my GET (User's Instanzen) -/api/features/templates/roles GET, POST (SysAdmin) -``` - ---- - -## 5. Status & Verbesserungsbedarf - -### 5.1 Erledigte Verbesserungen ✅ - -| Verbesserung | Status | Beschreibung | -|--------------|--------|--------------| -| **URL mit instanceId** | ✅ Erledigt | Trustee Routes haben jetzt `/{instanceId}/` Prefix | -| **Instance-Validierung** | ✅ Erledigt | Automatische Prüfung von Existenz, Feature-Typ und User-Zugriff | -| **Frontend API angepasst** | ✅ Erledigt | `trusteeApi.ts` nutzt neue URL-Struktur | - -### 5.2 Offene Punkte - -| Problem | Beschreibung | Priorität | -|---------|--------------|-----------| -| **Andere Features** | Chatbot, Workflow, RealEstate noch ohne instanceId | Mittel | -| **Keine Feature-Registry** | Feature-Views sind hardcoded | Niedrig | - -### 5.3 Aktuelle URL-Struktur (Trustee) - -``` -# MIT instanceId im Pfad (implementiert): -/api/trustee/{instanceId}/organisations -/api/trustee/{instanceId}/contracts -/api/trustee/{instanceId}/documents -/api/trustee/{instanceId}/positions -... -``` - ---- - -## 6. Empfohlene Feature-Struktur - -### 6.1 Neue Route-Struktur pro Feature - -``` -/api/{featureCode}/{instanceId}/{entity} -``` - -Beispiel Trustee: -``` -/api/trustee/{instanceId}/organisations -/api/trustee/{instanceId}/contracts -/api/trustee/{instanceId}/documents -/api/trustee/{instanceId}/positions -``` - -Beispiel Chatbot: -``` -/api/chatbot/{instanceId}/sessions -/api/chatbot/{instanceId}/messages -``` - -### 6.2 Feature-Definition in DB (poweron_app.Feature) - -```sql -INSERT INTO "Feature" (code, label, icon) VALUES -('trustee', '{"de": "Treuhand", "en": "Trustee"}', 'mdi-account-group'), -('chatbot', '{"de": "Chatbot", "en": "Chatbot"}', 'mdi-robot'), -('workflow', '{"de": "Workflow", "en": "Workflow"}', 'mdi-sitemap'), -('realestate', '{"de": "Immobilien", "en": "Real Estate"}', 'mdi-home-city'); -``` - -### 6.3 Feature-Datenbank-Zuordnung - -```python -FEATURE_DATABASES = { - "trustee": "poweron_trustee", - "chatbot": "poweron_chat", - "workflow": "poweron_chat", # Gleiche DB wie chatbot - "realestate": "poweron_realestate", -} -``` - ---- - -## 7. Bestehende Feature-Tabellen - -### 7.1 Trustee (poweron_trustee) - -| Tabelle | Beschreibung | Felder | -|---------|--------------|--------| -| `TrusteeOrganisation` | Firmen | id, label, enabled, mandateId, featureInstanceId | -| `TrusteeRole` | Rollen | id, desc, mandateId, featureInstanceId | -| `TrusteeAccess` | Zugriffe | id, organisationId, roleId, userId, contractId, mandateId, featureInstanceId | -| `TrusteeContract` | Verträge | id, organisationId, label, enabled, mandateId, featureInstanceId | -| `TrusteeDocument` | Dokumente | id, organisationId, contractId, documentName, documentData, mandateId, featureInstanceId | -| `TrusteePosition` | Positionen | id, organisationId, contractId, valuta, bookingAmount, ..., mandateId, featureInstanceId | -| `TrusteePositionDocument` | Verknüpfung | id, documentId, positionId, mandateId, featureInstanceId | - -### 7.2 Chatbot/Workflow (poweron_chat) - -| Tabelle | Beschreibung | -|---------|--------------| -| `ChatSession` | Chat-Sitzungen | -| `ChatMessage` | Nachrichten | -| `WorkflowExecution` | Workflow-Ausführungen | -| `WorkflowStep` | Workflow-Schritte | - -### 7.3 RealEstate (poweron_realestate) - -| Tabelle | Beschreibung | -|---------|--------------| -| `RealEstateProperty` | Liegenschaften | -| `RealEstateUnit` | Einheiten | -| `RealEstateTenant` | Mieter | - ---- - -## 8. RBAC-Struktur - -### 8.1 System-Level RBAC (poweron_app.Role) - -```python -class Role: - id: str - roleLabel: str # "admin", "viewer" - featureCode: str # "trustee", None für System-Rollen - mandateId: str # None für globale Templates - featureInstanceId: str # None für Mandate-Level Rollen - isSystemRole: bool # True für system-weite Rollen -``` - -### 8.2 Access Rules - -```python -class AccessRule: - roleId: str - context: str # "DATA", "UI", "RESOURCE" - item: str # "TrusteeContract", "trustee-contracts" - view: bool - read: str # "a" (all), "g" (group), "o" (own), "n" (none) - create: str - update: str - delete: str -``` - -### 8.3 Feature-Level RBAC (Trustee-spezifisch) - -```python -# TrusteeAccess definiert Zugriff auf Organisationen -# Rollen: "admin", "operate", "userreport" - -TrusteeAccess: - userId → User - organisationId → TrusteeOrganisation - roleId → TrusteeRole ("admin", "operate", "userreport") - contractId → TrusteeContract (optional, für Vertrags-Scope) -``` - ---- - -## 9. Migration / Nächste Schritte - -### 9.1 Backend-Änderungen - -1. **Routes mit instanceId** - - [x] `routeFeatureTrustee.py` erweitern: `/api/trustee/{instanceId}/...` ✅ - - [ ] Andere Features entsprechend anpassen (Chatbot, Workflow, RealEstate) - -2. **Feature-Registry** - - [ ] `FEATURE_REGISTRY` in Backend implementieren - - [ ] Views und Tabellen pro Feature definieren - -3. **Datenbank-Isolation** - - [x] Trustee-Tabellen haben `featureInstanceId` ✅ - - [ ] Prüfen, dass alle anderen Feature-Tabellen `featureInstanceId` haben - -### 9.2 Frontend-Anpassungen (bereits implementiert) - -- [x] URL-Struktur: `/mandates/{mandateId}/{featureCode}/{instanceId}/...` -- [x] `useCurrentInstance()` Hook -- [x] `FeatureStore` mit `/api/features/my` -- [x] Permission-Hooks für Tabellen und Views -- [x] `trusteeApi.ts` mit instanceId im URL-Pfad ✅ - ---- - -## 10. Zusammenfassung - -### Aktuelle Struktur (IST) - -``` -Feature → hat eigene Datenbank -Feature → hat eigene Datenmodelle (datamodel*.py) -Feature → hat eigene Interface-Klasse (interface*.py) -Feature → hat eigene Routes (routeFeature*.py) -FeatureInstance → gehört zu Mandate -Datensätze → haben mandateId + featureInstanceId -``` - -### Empfohlene Struktur (SOLL) - -``` -1. Routes mit instanceId im Pfad -2. Feature-Registry im Backend -3. Konsistente RBAC-Prüfung in allen Interfaces -4. View-Definitionen pro Feature -5. Einheitliche Datenbank-Isolation -``` - -### Vorhandene Features - -| Feature | Datenbank | Routes | Status | -|---------|-----------|--------|--------| -| Trustee | poweron_trustee | `/api/trustee/` | Produktiv | -| Chatbot | poweron_chat | `/api/chatbot/` | Produktiv | -| Workflow | poweron_chat | `/api/automation/` | Produktiv | -| RealEstate | poweron_realestate | `/api/realestate/` | In Entwicklung | - ---- - -*Erstellt: 2026-01-17* -*Aktualisiert: 2026-01-19* -*Analyse-Basis: poweron/gateway Backend-Code* - -## Änderungshistorie - -| Datum | Änderung | -|-------|----------| -| 2026-01-19 | Trustee Routes auf instanceId im URL-Pfad umgestellt | -| 2026-01-19 | Instance-Validierung mit Zugriffsprüfung implementiert | -| 2026-01-17 | Initiale Analyse erstellt | diff --git a/implementation/Saas Multi Tenant Mandate/FEATURE_NAMING_CONVENTION.md b/implementation/Saas Multi Tenant Mandate/FEATURE_NAMING_CONVENTION.md deleted file mode 100644 index bffe234..0000000 --- a/implementation/Saas Multi Tenant Mandate/FEATURE_NAMING_CONVENTION.md +++ /dev/null @@ -1,122 +0,0 @@ -# Feature Naming Convention - Analyse & Umsetzung - -## 1. Naming Convention Standard - -Jedes Feature folgt dieser Namenskonvention: - -| Komponente | Dateiname | Beispiel (Feature: Trustee) | -|------------|-----------|----------------------------| -| Datenmodell | `datamodelXxx.py` | `datamodelTrustee.py` | -| Interface | `interfaceDbXxx.py` | `interfaceDbTrustee.py` | -| Route | `routeFeatureXxx.py` | `routeFeatureTrustee.py` | - ---- - -## 2. Aktuelle Struktur (IST) - -### 2.1 Trustee Feature ✅ - -| Komponente | Dateiname | Status | -|------------|-----------|--------| -| Datenmodell | `datamodelTrustee.py` | ✅ | -| Interface | `interfaceDbTrustee.py` | ✅ | -| Route | `routeFeatureTrustee.py` | ✅ | - -### 2.2 Chat-basierte Features (Chatbot, ChatDynamic, Workflow) ✅ - -| Komponente | Dateiname | Beschreibung | -|------------|-----------|--------------| -| **Core-Datenmodell** | `datamodelChat.py` | Shared Model für alle Chat-Features | -| Interface | `interfaceDbChatbot.py` | DB-Interface für Chat-Daten | -| Route Chatbot | `routeFeatureChatbot.py` | Chatbot-Endpoints | -| Route ChatDynamic | `routeFeatureChatDynamic.py` | Dynamic Chat-Endpoints | -| Route Workflow | `routeFeatureWorkflow.py` | Workflow-Endpoints | - -### 2.3 RealEstate Feature ✅ - -| Komponente | Dateiname | Status | -|------------|-----------|--------| -| Datenmodell | `datamodelRealEstate.py` | ✅ | -| Interface | `interfaceDbRealEstate.py` | ✅ | -| Route | `routeFeatureRealEstate.py` | ✅ | - -### 2.4 Workflow Feature ✅ - -| Komponente | Dateiname | Status | -|------------|-----------|--------| -| Datenmodell | `datamodelWorkflow.py` | ✅ | -| Interface | (Teil von Chatbot DB) | ✅ | -| Route | `routeFeatureWorkflow.py` | ✅ | - ---- - -## 3. Zusammenfassung der Änderungen - -### 3.1 Umbenennungen ✅ ERLEDIGT - -| Von | Nach | Status | -|-----|------|--------| -| `interfaceDbTrusteeObjects.py` | `interfaceDbTrustee.py` | ✅ | -| `interfaceDbChatObjects.py` | `interfaceDbChatbot.py` | ✅ | -| `interfaceDbRealEstateObjects.py` | `interfaceDbRealEstate.py` | ✅ | -| `routeFeatureAutomation.py` | `routeFeatureWorkflow.py` | ✅ | - -> **Hinweis:** `datamodelChat.py` wurde **NICHT** umbenannt, da es ein Core-Datenmodell ist, -> das von mehreren Features verwendet wird (Chatbot, ChatDynamic, Workflow). - -### 3.2 Betroffene Import-Stellen - -Nach jeder Umbenennung müssen Imports aktualisiert werden in: -- `app.py` (Router-Registrierung) -- Andere Route-Dateien (Cross-Imports) -- Interface-Dateien (Model-Imports) -- Feature-Module (Interface-Imports) - ---- - -## 4. Feature-Übersicht nach Umbenennung - -| Feature | Datenmodell | Interface | Route | Datenbank | -|---------|-------------|-----------|-------|-----------| -| **Trustee** | `datamodelTrustee.py` | `interfaceDbTrustee.py` | `routeFeatureTrustee.py` | `poweron_trustee` | -| **Chatbot** | `datamodelChat.py` (shared) | `interfaceDbChatbot.py` | `routeFeatureChatbot.py` | `poweron_chat` | -| **ChatDynamic** | `datamodelChat.py` (shared) | `interfaceDbChatbot.py` | `routeFeatureChatDynamic.py` | `poweron_chat` | -| **Workflow** | `datamodelChat.py` + `datamodelWorkflow.py` | `interfaceDbChatbot.py` | `routeFeatureWorkflow.py` | `poweron_chat` | -| **RealEstate** | `datamodelRealEstate.py` | `interfaceDbRealEstate.py` | `routeFeatureRealEstate.py` | `poweron_realestate` | - ---- - -## 5. Nicht-Feature Dateien (bleiben unverändert) - -Diese Dateien sind **keine Feature-Dateien** und folgen anderen Konventionen: - -### Datenmodelle (System/Shared) -- `datamodelUam.py` - User Access Management -- `datamodelRbac.py` - Role-Based Access Control -- `datamodelFeatures.py` - Feature/FeatureInstance Definitionen -- `datamodelMembership.py` - FeatureAccess -- `datamodelPagination.py` - Pagination Utilities -- `datamodelAudit.py` - Audit Logging -- `datamodelFiles.py` - File Storage -- `datamodelMessaging.py` - Email/SMS -- `datamodelInvitation.py` - User Invitations - -### Interfaces (System/Shared) -- `interfaceDbAppObjects.py` - Zentrale App-Datenbank -- `interfaceFeatures.py` - Feature-Verwaltung -- `interfaceRbac.py` - RBAC-Operationen -- `interfaceMessaging.py` - Messaging-Service -- `interfaceBootstrap.py` - System-Bootstrap - -### Routes (System/Admin) -- `routeDataUsers.py` - User CRUD -- `routeDataMandates.py` - Mandate CRUD -- `routeRbac.py` - RBAC-Verwaltung -- `routeFeatures.py` - Feature-Verwaltung -- `routeAdmin.py` - Admin-Funktionen -- `routeSecurity*.py` - Auth-Endpoints - ---- - -*Erstellt: 2026-01-19* -*Status: ✅ Alle Umbenennungen abgeschlossen* diff --git a/implementation/Saas Multi Tenant Mandate/PROGRESS_TRACKING.md b/implementation/Saas Multi Tenant Mandate/PROGRESS_TRACKING.md deleted file mode 100644 index 8b6e8df..0000000 --- a/implementation/Saas Multi Tenant Mandate/PROGRESS_TRACKING.md +++ /dev/null @@ -1,358 +0,0 @@ -# Multi-Tenant Gateway - Implementation Progress Tracking - -**Konzept:** [mandate_implementation_gateway.md](./mandate_implementation_gateway.md) -**Letzte Aktualisierung:** 2026-01-18 - ---- - -## Gesamtfortschritt - -| Phase | Status | Fortschritt | -|-------|--------|-------------| -| Phase 1: Foundation | ✅ Abgeschlossen | 7/7 | -| Phase 2: RBAC Core | ✅ Abgeschlossen | 4/4 | -| Phase 3: Auth & Context | ✅ Abgeschlossen | 6/6 | -| Phase 4: Routes Migration | ✅ Abgeschlossen | 11/11 | -| Phase 5: Interfaces Migration | ✅ Abgeschlossen | 5/5 | -| Phase 6: Features Migration | ✅ Abgeschlossen | 7/7 | -| Phase 7: Cleanup | ✅ Abgeschlossen | 3/3 | -| Phase 8: Neue Features | ✅ Abgeschlossen | 5/5 | - -**Alle Phasen abgeschlossen!** - ---- - -## Phase 1: Foundation (Datenmodelle & DB) ✅ - -**Ziel:** Neue Datenstrukturen ohne Breaking Changes am bestehenden Code. - -| Schritt | Datei | Aktion | Status | -|---------|-------|--------|--------| -| 1.1 | `datamodelFeatures.py` | NEU: `Feature`, `FeatureInstance` | ✅ | -| 1.2 | `datamodelMembership.py` | NEU: `UserMandate`, `FeatureAccess`, `UserMandateRole`, `FeatureAccessRole` | ✅ | -| 1.3 | `datamodelInvitation.py` | NEU: `Invitation` | ✅ | -| 1.4 | `datamodelRbac.py` | ADD: `Role.mandateId`, `Role.featureInstanceId`, `Role.featureCode`, `AccessRule.roleId` | ✅ | -| 1.5 | `datamodelUam.py` | ADD: `User.isSysAdmin`, Mandate ohne `language` | ✅ | -| 1.6 | `datamodelSecurity.py` | Token ohne `mandateId` | ✅ | -| 1.7 | `interfaceBootstrap.py` | roleId-basiert, erstellt UserMandate + UserMandateRole | ✅ | - -**Notizen:** -- Bootstrap erstellt jetzt Root-Mandate, System-Rollen (sysadmin, admin, user) und verknüpft Initial-User via Junction Tables -- `isSysAdmin` Feld hat Validator für None → False Konvertierung -- App startet erfolgreich mit neuer Struktur - ---- - -## Phase 2: RBAC Core (Kern-Logik) ✅ - -**Ziel:** Neues RBAC-System parallel zum alten lauffähig. - -| Schritt | Datei | Aktion | Status | -|---------|-------|--------|--------| -| 2.1 | `security/rbac.py` | Neue `getRulesForUserBulk()` mit Junction Tables | ✅ | -| 2.2 | `interfaceRbac.py` | `buildRbacWhereClause()` mit explizitem `mandateId` | ✅ | -| 2.3 | `interfaceDbAppObjects.py` | Neue Membership-Methoden (parallel zu alten) | ✅ | -| 2.4 | `interfaceFeatures.py` | NEU: Feature-Instanz-Management | ✅ | - -**Notizen:** -- `getRulesForUserBulk()` implementiert mit optimierten SQL JOINs (3 Queries statt N+1) -- Membership-Methoden hinzugefügt: `getUserMandate`, `createUserMandate`, `deleteUserMandate`, `getRoleIdsForUserMandate`, etc. -- Feature-Methoden hinzugefügt: `getFeatureAccess`, `createFeatureAccess`, `getRoleIdsForFeatureAccess` -- `interfaceFeatures.py` erstellt mit Template-Kopierung und Synchronisation - ---- - -## Phase 3: Auth & Context (Authentifizierung) ✅ - -**Ziel:** Request-Context-System einführen. - -| Schritt | Datei | Aktion | Status | -|---------|-------|--------|--------| -| 3.1 | `auth/authentication.py` | `RequestContext` + `getRequestContext()` + `requireSysAdmin()` | ✅ | -| 3.2 | `routeSecurityLocal.py` | Login mit neuem Token (ohne `mandateId`) | ✅ | -| 3.3 | `routeSecurityMsft.py` | OAuth anpassen (Token ohne `mandateId`) | ✅ | -| 3.4 | `routeSecurityGoogle.py` | OAuth anpassen (Token ohne `mandateId`) | ✅ | -| 3.5 | `datamodelSecurity.py` | Token ohne `mandateId` | ✅ (Phase 1) | -| 3.6 | `auth/tokenRefreshService.py` | Bereits korrekt (OAuth-Token, nicht JWT) | ✅ | - -**Notizen:** -- `RequestContext` Klasse implementiert: User + mandateId + featureInstanceId + roleIds -- `getRequestContext()` FastAPI Dependency: Liest `X-Mandate-Id` und `X-Instance-Id` Header -- `requireSysAdmin()` FastAPI Dependency: Prüft `isSysAdmin` Flag mit Audit-Logging -- JWT-Token enthalten KEIN `mandateId` mehr - Mandant-Kontext kommt per Request-Header -- Token-Validierung in `_getUserBase()` prüft nur noch `userId`, nicht mehr `mandateId` -- Audit-Logging für System-Funktionen (Login/Logout) verwendet `mandateId="system"` (kein User-Kontext) - ---- - -## Phase 4: Routes Migration ✅ - -**Ziel:** Alle Routes auf neues Context-System migrieren. - -### 4.A - Admin Routes ✅ - -| Schritt | Datei | Status | -|---------|-------|--------| -| 4.A.1 | `routeSecurityAdmin.py` | ✅ | -| 4.A.2 | `routeAdminRbacRoles.py` | ✅ | -| 4.A.3 | `routeDataUsers.py` | ✅ | -| 4.A.4 | `routeDataMandates.py` | ✅ | -| 4.A.5 | `routeRbac.py` | ✅ | - -**Notizen Phase 4.A:** -- Alle Admin-Routes verwenden jetzt `requireSysAdmin()` Dependency -- `roleLabels`-basierte Prüfungen durch `isSysAdmin` ersetzt -- `currentUser.mandateId` durch `RequestContext` ersetzt -- User-Filterung via `UserMandate` Junction Table -- Audit-Logging verwendet `context.mandateId` oder "system" - -### 4.B - Feature Routes ✅ - -| Schritt | Datei | Status | -|---------|-------|--------| -| 4.B.1 | `routeFeatureTrustee.py` (← routeDataTrustee.py) | ✅ | -| 4.B.2 | `routeFeatureChatbot.py` (← routeChatbot.py) | ✅ | -| 4.B.3 | `routeFeatureRealEstate.py` (← routeRealEstate.py) | ✅ | -| 4.B.4 | `routeFeatureNeutralization.py` (← routeDataNeutralization.py) | ✅ | -| 4.B.5 | `routeFeatureAutomation.py` (← routeAdminAutomationEvents.py) | ✅ | -| 4.B.6 | `routeFeatureChatDynamic.py` (← routeChatPlayground.py) | ✅ | - -**Notizen Phase 4.B:** -- Alle Feature-Routes umbenannt mit `routeFeature...` Prefix -- `getCurrentUser` durch `getRequestContext` ersetzt -- `currentUser.mandateId` durch `context.mandateId` ersetzt -- `currentUser.roleLabels` durch `context.roleIds` ersetzt -- `requireSysAdmin()` für Sysadmin-Endpoints (z.B. Automation Events) -- Alte Route-Dateien gelöscht, Imports in `app.py` aktualisiert - ---- - -## Phase 5: Interfaces Migration ✅ - -**Ziel:** Alle Interfaces auf Context-System migrieren. - -| Schritt | Datei | Status | -|---------|-------|--------| -| 5.1 | `interfaceDbTrusteeObjects.py` | ✅ | -| 5.2 | `interfaceDbChatObjects.py` | ✅ | -| 5.3 | `interfaceDbRealEstateObjects.py` | ✅ | -| 5.4 | `interfaceDbComponentObjects.py` | ✅ | -| 5.5 | `interfaceVoiceObjects.py` | ✅ | - -**Notizen Phase 5:** -- `getInterface(currentUser)` → `getInterface(currentUser, mandateId=...)` -- `setUserContext(currentUser)` → `setUserContext(currentUser, mandateId=...)` -- `self.mandateId = currentUser.mandateId` → `self.mandateId = mandateId` (aus Parameter) -- SysAdmins können ohne `mandateId` arbeiten (cross-mandate Operationen) -- Singleton-Cache-Keys verwenden jetzt `userId_mandateId` statt `userId_user.mandateId` -- Feature-Routes aktualisiert: `getInterface(context.user, mandateId=context.mandateId)` - ---- - -## Phase 6: Features Migration ✅ - -**Ziel:** Feature-Module auf Context-System migrieren. - -| Schritt | Datei | Status | -|---------|-------|--------| -| 6.1 | `features/chatbot/mainChatbot.py` | ✅ | -| 6.2 | `features/realEstate/mainRealEstate.py` | ✅ | -| 6.3 | `features/dynamicOptions/mainDynamicOptions.py` | ✅ | -| 6.4 | `features/neutralizePlayground/mainNeutralizePlayground.py` | ✅ | -| 6.5 | `workflows/workflowManager.py` | ✅ | -| 6.6 | `workflows/methods/methodBase.py` | ✅ (keine Änderung nötig) | -| 6.7 | `services/serviceNeutralization/mainServiceNeutralization.py` | ✅ (verwendet interfaceDbApp.mandateId) | - -**Notizen Phase 6:** -- `Services` Klasse aktualisiert: `mandateId` als expliziter Parameter -- `getInterface(user, workflow, mandateId)` für alle Services -- Feature-Funktionen aktualisiert: `mandateId` als Parameter statt `currentUser.mandateId` -- Route-Aufrufe aktualisiert: `str(context.mandateId)` wird übergeben -- `workflowManager.py`: Verwendet `self.services.mandateId` für neue Workflows -- `mainDynamicOptions.py`: Verwendet `services.mandateId` für Benutzer-Lookup - ---- - -## Phase 7: Cleanup & Breaking Changes ✅ - -**Ziel:** Alte Felder entfernen (erst wenn alles migriert ist!) - -| Schritt | Datei | Aktion | Status | -|---------|-------|--------|--------| -| 7.1 | `datamodelUam.py` | ENTFERNE `User.mandateId`, `User.roleLabels` | ✅ | -| 7.2 | `interfaceDbAppObjects.py` | Alte Methoden entfernen | ✅ | -| 7.3 | `interfaceBootstrap.py` | Alte roleLabels-Logik entfernen | ✅ | - -**Änderungen Phase 7:** -- `datamodelUam.py`: `User.mandateId` und `User.roleLabels` Felder entfernt -- `interfaceDbAppObjects.py`: - - `createUser()`: `roleLabels` Parameter entfernt, nur noch `isSysAdmin` - - `updateUser()`: `mandateId` Handling entfernt - - `deleteRole()`: Prüft jetzt `UserMandateRole` statt `user.roleLabels` - - `getAccessRules()`: Neuer `roleId` Parameter hinzugefügt - - `countRoleAssignments()`: Neue Methode für Role-Zählung via `UserMandateRole` -- `interfaceBootstrap.py`: `mandateId` und `roleLabels` aus Admin/Event User Erstellung entfernt -- `security/rbac.py`: `user.roleLabels` Fallback entfernt -- `routeDataUsers.py`: Verwendet `createUserMandate()` für Role-Assignment -- `routeSecurityLocal.py`: `roleLabels` aus User-Registrierung entfernt -- `routeRbac.py`: Verwendet `context.roleIds` statt `user.roleLabels` -- `routeAdminRbacRoles.py`: Komplett auf `UserMandateRole` umgestellt mit Hilfsfunktionen - ---- - -## Phase 8: Neue Features ✅ - -**Ziel:** Neue Funktionalität hinzufügen. - -| Schritt | Datei | Status | -|---------|-------|--------| -| 8.1 | `routeFeatures.py` (NEU) | ✅ | -| 8.2 | `routeInvitations.py` (NEU) | ✅ | -| 8.3 | `routeRbacExport.py` (NEU) | ✅ | -| 8.4 | `routeGdpr.py` (NEU) | ✅ | -| 8.5 | `app.py` (Routes registriert) | ✅ | - -**Notizen Phase 8:** -- `routeFeatures.py`: Feature/FeatureInstance CRUD, Template-Rollen-Sync, `/api/features/my` Endpoint -- `routeInvitations.py`: Token-basierte Einladungen, öffentliche Validierung, Accept-Flow -- `routeRbacExport.py`: Global und Mandate-scoped Export/Import von RBAC-Regeln -- `routeGdpr.py`: DSGVO Art. 15 (Datenexport), Art. 17 (Löschrecht), Art. 20 (Datenportabilität) -- Alle neuen Routes in `app.py` registriert - ---- - -## Zusätzliche Änderungen (Nicht in Phasen) - -| Datei | Änderung | Status | -|-------|----------|--------| -| `connectorDbPostgre.py` | Entfernt Warning bei leerem userId (Bootstrap) | ✅ | -| `routeSecurityAdmin.py` | DB-Funktionen generisch gemacht (dynamische DB-Discovery) | ✅ | -| `env_*.env` | Konsolidierte DB-Konfiguration (DB_HOST, DB_USER, etc.) | ✅ | -| `interfaceDb*Objects.py` | Hardcoded DB-Namen (poweron_app, poweron_chat, etc.) | ✅ | -| `rootAccess.py` | Bootstrap-Aufruf wenn kein Initial-User | ✅ | - ---- - -## Bekannte Probleme / TODOs - -- [x] DB-Indizes für Junction Tables erstellen (Performance) → `sql/001_multi_tenant_indexes_triggers_fk.sql` -- [x] IMMUTABLE Triggers für Role/AccessRule auf DB-Ebene → `sql/001_multi_tenant_indexes_triggers_fk.sql` -- [x] Foreign Key Constraints für CASCADE DELETE → `sql/001_multi_tenant_indexes_triggers_fk.sql` -- [x] Add/Remove User from Mandate Endpoints → `routeDataMandates.py` -- [x] Combined Register + Accept Invitation Endpoint → `routeInvitations.py` - ---- - -## Changelog - -### 2026-01-17 (Phase 8 - Erweiterungen) -- **Fehlende Endpoints nachgeliefert:** - - `routeDataMandates.py`: User-Management innerhalb Mandaten - - `GET /api/mandates/{mandateId}/users` - Benutzer im Mandant auflisten - - `POST /api/mandates/{mandateId}/users` - Benutzer zu Mandant hinzufügen - - `DELETE /api/mandates/{mandateId}/users/{targetUserId}` - Benutzer aus Mandant entfernen - - `PUT /api/mandates/{mandateId}/users/{targetUserId}/roles` - Benutzer-Rollen aktualisieren - - SysAdmin Self-Eskalation Prevention: SysAdmin kann sich nicht selbst hinzufügen - - Orphan Prevention: Letzter Admin kann nicht entfernt werden - - `routeInvitations.py`: Combined Register + Accept - - `POST /api/invitations/register-and-accept` - Registrierung + Einladung in einem Schritt (öffentlich) -- **DB Migration Script erstellt:** - - `sql/001_multi_tenant_indexes_triggers_fk.sql` - - Indexes für alle Junction Tables (UserMandate, UserMandateRole, FeatureAccess, etc.) - - IMMUTABLE Triggers für Role und AccessRule (mandateId/featureInstanceId/roleId) - - Foreign Key Constraints mit CASCADE DELETE - -### 2026-01-17 (Phase 8) -- **Phase 8 abgeschlossen:** - - `routeFeatures.py` NEU: Feature-Definitionen (SysAdmin), FeatureInstance CRUD (Mandate-Admin) - - `GET /api/features/` - Liste aller Features - - `GET /api/features/{featureCode}` - Feature-Details - - `POST /api/features/` - Feature erstellen (SysAdmin) - - `GET /api/features/instances` - Feature-Instanzen im Mandant - - `POST /api/features/instances` - Feature-Instanz erstellen - - `DELETE /api/features/instances/{id}` - Feature-Instanz löschen - - `POST /api/features/instances/{id}/sync-roles` - Rollen synchronisieren - - `GET /api/features/my` - Eigene Feature-Instanzen (kein Mandant-Kontext nötig) - - `GET /api/features/templates/roles` - Template-Rollen (SysAdmin) - - `POST /api/features/templates/roles` - Template-Rolle erstellen (SysAdmin) - - `routeInvitations.py` NEU: Token-basierte Einladungen für Self-Service Onboarding - - `POST /api/invitations/` - Einladung erstellen (Mandate-Admin) - - `GET /api/invitations/` - Einladungen auflisten - - `DELETE /api/invitations/{id}` - Einladung widerrufen - - `GET /api/invitations/validate/{token}` - Einladung validieren (öffentlich) - - `POST /api/invitations/accept/{token}` - Einladung annehmen (authentifiziert) - - `routeRbacExport.py` NEU: RBAC Export/Import - - `GET /api/rbac/export/global` - Globale Templates exportieren (SysAdmin) - - `POST /api/rbac/import/global` - Globale Templates importieren (SysAdmin) - - `GET /api/rbac/export/mandate` - Mandant-RBAC exportieren (Mandate-Admin) - - `POST /api/rbac/import/mandate` - Mandant-RBAC importieren (Mandate-Admin) - - `routeGdpr.py` NEU: DSGVO-Compliance Endpoints - - `GET /api/user/me/data-export` - Datenexport (Art. 15) - - `GET /api/user/me/data-portability` - Datenportabilität JSON-LD (Art. 20) - - `DELETE /api/user/me` - Account löschen (Art. 17) - - `GET /api/user/me/consent-info` - Datenschutz-Informationen - - `app.py`: Alle neuen Routes registriert - -### 2026-01-18 -- **Phase 6 abgeschlossen:** - - `services/__init__.py`: `Services` Klasse akzeptiert `mandateId` Parameter - - `getInterface(user, workflow, mandateId)` übergibt `mandateId` an alle Interface-Aufrufe - - `mainChatbot.py`: `chatProcess(user, mandateId, ...)` - `mandateId` für Workflow-Erstellung - - `mainRealEstate.py`: `processNaturalLanguageCommand(user, mandateId, ...)` + alle CREATE-Operationen - - `mainDynamicOptions.py`: `services.mandateId` statt `currentUser.mandateId` - - `mainNeutralizePlayground.py`: `NeutralizationPlayground(user, mandateId)` Konstruktor - - `workflowManager.py`: `self.services.mandateId` für neue Workflows - - Route-Aufrufe aktualisiert: `str(context.mandateId)` wird übergeben - - `methodBase.py`: Keine Änderung nötig (verwendet Services) - - `mainServiceNeutralization.py`: Keine Änderung nötig (verwendet `interfaceDbApp.mandateId`) -- **Phase 5 abgeschlossen:** - - `interfaceDbTrusteeObjects.py`: `getInterface(user, mandateId)` + `setUserContext(user, mandateId)` - - `interfaceDbChatObjects.py`: `getInterface(user, mandateId)` + `setUserContext(user, mandateId)` - - `interfaceDbRealEstateObjects.py`: `getInterface(user, mandateId)` + `setUserContext(user, mandateId)` - - `interfaceDbComponentObjects.py`: `getInterface(user, mandateId)` + `setUserContext(user, mandateId)` - - `interfaceVoiceObjects.py`: `getVoiceInterface(user, mandateId)` + `setUserContext(user, mandateId)` - - Feature-Routes aktualisiert: `getInterface(context.user, mandateId=context.mandateId)` -- **Phase 4.B abgeschlossen:** - - `routeFeatureTrustee.py`: Alle Endpoints auf `getRequestContext()` umgestellt - - `routeFeatureChatbot.py`: Streaming + RBAC mit `context.user` - - `routeFeatureRealEstate.py`: `context.mandateId` für Projekt-/Parzellen-Filterung - - `routeFeatureNeutralization.py`: Config + Processing mit `context.user` - - `routeFeatureAutomation.py`: `requireSysAdmin()` für alle Endpoints - - `routeFeatureChatDynamic.py`: Workflow-Start/Stop mit `context.user` - - Alte Route-Dateien gelöscht (6 Dateien) - - `app.py` Imports aktualisiert -- **Phase 5 - GREENFIELD Cleanup:** - - ALLE Fallbacks auf `currentUser.mandateId` entfernt (GREENFIELD = keine Backwards Compatibility) - - `interfaceDbAppObjects.py`: `self.mandateId` kommt nur aus Parameter, nicht aus User - - `interfaceDbTrusteeObjects.py`: Fallback-Logik entfernt - - `interfaceDbChatObjects.py`: `self.mandateId` statt `self.currentUser.mandateId` - - `interfaceDbRealEstateObjects.py`: Fallback-Logik entfernt - - `interfaceDbComponentObjects.py`: `self.mandateId` für alle Record-Erstellungen - - `interfaceVoiceObjects.py`: Fallback-Logik entfernt - - `interfaceRbac.py`: Keine Fallbacks mehr, `mandateId` ist explizit erforderlich - - Modul-Header aktualisiert (keine "Backwards Compatibility" Referenzen mehr) - -### 2026-01-17 -- **Phase 3 abgeschlossen:** - - `RequestContext` Klasse in `auth/authentication.py` implementiert - - `getRequestContext()` FastAPI Dependency mit `X-Mandate-Id` und `X-Instance-Id` Header-Handling - - `requireSysAdmin()` FastAPI Dependency für System-Level-Operationen - - JWT-Token enthalten kein `mandateId` mehr (Local, Microsoft, Google) - - Token-Validierung prüft nur noch `userId` - - `auth/__init__.py` exportiert neue Funktionen -- **Phase 4.A abgeschlossen:** - - `routeSecurityAdmin.py`: Alle Endpoints auf `requireSysAdmin()` umgestellt - - `routeAdminRbacRoles.py`: `requireSysAdmin()` + `getRootInterface()` - - `routeDataUsers.py`: `getRequestContext()` für Mandate-Scope, UserMandate-Filterung - - `routeDataMandates.py`: `requireSysAdmin()` (Mandates sind System-Ressourcen) - - `routeRbac.py`: Permissions mit `getRequestContext()`, Rules/Roles mit `requireSysAdmin()` - -### 2026-01-16 -- Phase 1 abgeschlossen -- Startup-Fehler behoben (isSysAdmin Validator, Bootstrap-Aufruf) -- DB-Konfiguration konsolidiert -- DB-Admin-Funktionen generisch gemacht -- **Phase 2 abgeschlossen:** - - `getRulesForUserBulk()` in `security/rbac.py` implementiert (optimierte Bulk-Queries) - - Membership-Methoden in `interfaceDbAppObjects.py` hinzugefügt - - `interfaceFeatures.py` NEU erstellt (Feature-Instanz-Management mit Template-Kopierung) \ No newline at end of file diff --git a/implementation/rbac_access_concept_done.md b/implementation/rbac_access_concept_done.md new file mode 100644 index 0000000..a350218 --- /dev/null +++ b/implementation/rbac_access_concept_done.md @@ -0,0 +1,579 @@ +# RBAC Zugriffskonzept (Katalog + Seiten + Endpoints) + +## Kontext aus dem Codebestand (aktueller Stand) + +- RBAC-Regeln und Rollen existieren bereits mit immutable Kontextfeldern und AccessRule-Unterstuetzung fuer DATA/UI/RESOURCE. Siehe `AccessRuleContext`, `Role`, `AccessRule` in: +``` +21:116:gateway/modules/datamodels/datamodelRbac.py +class AccessRuleContext(str, Enum): + DATA = "DATA" + UI = "UI" + RESOURCE = "RESOURCE" +... +class AccessRule(BaseModel): + roleId: str + context: AccessRuleContext + item: Optional[str] + view: bool + read: Optional[AccessLevel] + create: Optional[AccessLevel] + update: Optional[AccessLevel] + delete: Optional[AccessLevel] +``` +- RBAC-Endpunkte fuer Permissions und Access Rules existieren bereits (Access Rules sind SysAdmin-only): +``` +35:214:gateway/modules/routes/routeRbac.py +@router.get("/permissions") +@router.get("/permissions/all") +@router.get("/rules") +``` +- Mandantenzuordnung von Benutzern/Rollen laeuft ueber `/api/mandates/{mandateId}/users` und die Junction-Tabellen `UserMandateRole` (Mandanten-Kontext) und `FeatureAccessRole` (Feature-Instanz-Kontext): +``` +15:149:gateway/modules/datamodels/datamodelMembership.py +class UserMandateRole(BaseModel): + userMandateId: str + roleId: str +... +class FeatureAccessRole(BaseModel): + featureAccessId: str + roleId: str +``` +- Im Frontend existieren Admin-Seiten fuer Benutzer-Mandant-Mitgliedschaften und Mandanten-Rollen: +``` +1:120:frontend_nyla/src/pages/admin/AdminUserMandatesPage.tsx +1:185:frontend_nyla/src/pages/admin/AdminMandateRolesPage.tsx +``` + +Damit ist klar: +- Zugriffregeln existieren bereits (view + CRUD mit none/my/group/all). +- Fehlend ist ein **Katalog + Editor-UI** fuer Objektregeln (DATA/UI/RESOURCE). + +## Entscheidungen / Antworten zu Inputs + +1) **Shared Katalog-Location (Gateway):** + - Es gibt aktuell keinen gemeinsamen RBAC-Objektkatalog in datamodels. Datamodels definieren nur Schema. + - Ziel-Location: `gateway/modules/security/rbacCatalog/` (Service-Layer), nicht `datamodels`. + - DATA-Objekte koennen on-the-fly aus Modellen/Attributen abgeleitet werden, ohne Persistenz. + - UI- und RESOURCE-Objekte brauchen explizite Registry-Eintraege, persistent in einer Katalog-Tabelle, dann ueber Gateway-Endpunkte abrufbar. + - Feature-Initialisierung im Gateway registriert die UI/RESOURCE-Keys. Die Admin-UI zeigt alle persistierten Katalogobjekte (keine Filterung nach Registrierungsstatus). + - Persistenz ist Pflicht, um Regeln nicht zu verlieren, wenn Features entfernt oder deaktiviert werden. + +2) **Admin-Seite fuer Mandantenverwaltung: gleich wie Rollen-Zuordnung?** + - Mandanten-CRUD getrennt von Rollen-Zuordnung. + - Heute: Mandanten-CRUD ist SysAdmin-only (`/api/mandates`), Rollen-Zuordnung ist Mandanten-Admin (`/api/mandates/{mandateId}/users`). + - Ziel: separate **AdminMandatesPage** (SysAdmin), **AdminUserMandatesPage** fuer Rollen-Zuordnung. + - Zusaetzlich: **AdminRbacRulesPage** (Feature-Instanz-Admin) fuer Objektregeln. + +3) **Registry-Eigentum und Quellen:** + - Feature-Initialisierung im Gateway definiert alle UI/RESOURCE-Objekte der Features. + - Frontend registriert keine Feature-UI-Objekte. + - System-UI-Objekte werden direkt im Backend definiert (keine UI-Abhaengigkeit). + +4) **UI-Permissions:** + - UI-Kontext nutzt nur `view`. + - Kein create/update/delete fuer UI-Kontext. + +5) **Katalog-Scope:** + - Der Katalog enthaelt alle Objekte (global + mandate + feature instance). + +6) **Bootstrap-Scope (interfaceBootstrap.py):** + - Bootstrap laeuft nur, wenn die Datenbank leer ist. + - Bootstrap erzeugt nur System-RBAC (Root-Mandant, Admin/Event-User, globale Rollen, Basisregeln). + - Bootstrap definiert keine Feature-Rollen oder Feature-Zugriffsregeln. + - System-UI-Objekte sind im Backend definiert und koennen im Bootstrap registriert werden. + +## Zielarchitektur + +### A) RBAC Objektkatalog (shared im Gateway) + +**Ziel:** Ein einziger Objektbaum fuer DATA / UI / RESOURCE, mit Mandant + Feature-Instanz-Kontext. + +**Quelle je Objekttyp:** +- **DATA**: abgeleitet aus Model-Metadaten (Tabellen + Felder). +- **UI**: Registry aus Feature-Initialisierung plus backend-definierte System-UI-Objekte. +- **RESOURCE**: Registry aus Feature-Initialisierung. + +**Persistenz:** +- Kleine Katalog-Tabelle `RbacObject` (oder aehnlich) fuer UI/RESOURCE. +- DATA-Objekte nicht speichern; on-demand generieren. +- Nicht registrierte Objekte bleiben im Katalog und bleiben selektierbar; keine Filterung nach Registrierung. + +**Model (gateway/modules/datamodels/datamodelRbacCatalog.py):** +- `id` +- `objectKey` (unique, z.B. `ui.feature.trustee.contracts.tab.documents`) +- `context` (DATA | UI | RESOURCE) +- `featureCode` (z.B. `trustee`) +- `featureInstanceId` (nullable) +- `mandateId` (nullable) +- `label` (multilingual, optional) +- `meta` (JSON, optional, z.B. Endpoint-Info oder UI-Hints) + +**Registry-Logik (gateway/modules/security/rbacCatalog/catalogService.py):** +- `getDataCatalog(mandateId, featureInstanceId)` -> aus Modellen ableiten: + - `data.table.
` + - `data.table.
.` +- `getUiCatalog(mandateId, featureInstanceId, featureCode)` -> aus `RbacObject` (UI-Kontext) +- `getResourceCatalog(mandateId, featureInstanceId, featureCode)` -> aus `RbacObject` (RESOURCE-Kontext) +- `getCatalog(...)` -> zusammenfuehren; alle Objekte (global + mandate + feature instance) + +**ObjectKey-Konvention:** +- DATA: + - `data.table.
` + - `data.table.
.` +- UI: + - `ui.feature...` + - Beispiel: `ui.feature.trustee.contracts.tab.documents` +- RESOURCE: + - `resource.feature..` + - Beispiel: `resource.feature.trustee.contracts.create` + +### B) RBAC Rules Editor (neue Admin-Seite) + +**Seitenname:** `AdminRbacRulesPage` +**Zielgruppe:** Feature-Instanz-Admin +**Route:** `/admin/feature-instances/:featureInstanceId/rbac` +**Navigation:** Feature-Instanz-Seiten “Rollen” + “Zugriffe” ersetzen durch: +- “Feature-Benutzer” (bestehende User/Rollen-Zuordnung) +- “Zugriffe (RBAC)” (neue Seite fuer Objektregeln) + +**Layout:** +- Links: Objektbaum (DATA / UI / RESOURCE) +- Oben: Rollen-Auswahl (Feature-Instanz-Rollen) +- Rechts: Regel-Grid (view/read/create/update/delete mit Scope) + - UI: nur `view` + - RESOURCE: view + CRUD mit none/my/group/all + - DATA: CRUD mit none/my/group/all + +**Verhalten:** +- Auswahl eines Objekts zeigt Regeln je Rolle. +- Aenderungen ueber create/update/delete von AccessRule. +- Nur Rollen der Feature-Instanz sind auswaehlbar. + +### C) Backend-Endpunkte (Vorschlag) + +#### Katalog-Endpunkte +``` +GET /api/rbac/catalog?featureInstanceId=...&featureCode=...&include=data,ui,resource +``` +Response: +``` +{ + "data": [{ "objectKey": "data.table.TrusteeContract", "label": "...", "children": [...] }], + "ui": [{ "objectKey": "ui.feature.trustee.contracts", "label": "...", "children": [...] }], + "resource": [{ "objectKey": "resource.feature.trustee.contracts.create", "label": "..." }] +} +``` + +``` +POST /api/rbac/catalog/ui +POST /api/rbac/catalog/resource +``` +Payload: +``` +{ + "featureCode": "trustee", + "featureInstanceId": "optional", + "mandateId": "optional", + "objects": [ + { "objectKey": "ui.feature.trustee.contracts", "label": { "en": "...", "de": "..." }, "meta": {} } + ] +} +``` +Notes: +- Endpunkte registrieren/aktualisieren UI/RESOURCE-Katalogeintraege. +- DATA-Objekte werden nie gepostet. +- Admin-UI darf alle persistenten Objekte selektieren. + +#### AccessRule-Endpunkte (bestehend, aber erweitert) +``` +GET /api/rbac/rules?roleId=...&context=UI&item=ui.feature.trustee.contracts +POST /api/rbac/rules +PUT /api/rbac/rules/{ruleId} +DELETE /api/rbac/rules/{ruleId} +``` +Policy: +- Nur SysAdmin darf Regeln verwalten. + +#### Optionale Options-Endpunkte fuer Admin-UI +``` +GET /api/options/mandate.roles?mandateId=... +GET /api/options/featureInstance.roles?featureInstanceId=... +GET /api/options/featureInstances?mandateId=... +``` + +### D) Enforcement +- **DATA:** ueber RBAC in der Daten-Schicht. +- **UI:** Frontend nutzt `getAllPermissions` und blendet aus via `view`. +- **RESOURCE:** Gateway prueft am Endpoint-Entry. + +## Implementierungsschritte (high level) + +1) Katalog-Model + Service im Gateway. +2) Katalog-Endpunkte in `routeRbacCatalog.py`. +3) Registry-Producer: + - Feature-Initialisierung registriert UI/RESOURCE. + - System-UI-Objekte werden im Backend registriert. +4) Neue Admin-Seite `AdminRbacRulesPage`. +5) Feature-Instanz-Navigation anpassen. + +## Bootstrap-Abgleich (aktuell vs Ziel) + +**Aktuell (interfaceBootstrap.py):** +- Root-Mandant, Admin-User, Event-User. +- Globale Template-Rollen: `admin`, `user`, `viewer`. +- AccessRules (nur wenn keine existieren): + - Default DATA rules (item=None). + - Table-spezifische DATA rules (Mandate, UserInDB, Standard-Tabellen, AuthEvent). + - UI rules (context=UI, item=None, view=True). + - RESOURCE rules (context=RESOURCE, item=None, view=True). +- Feature-Initialisierung und Feature-Template-Rollen. +- AccessRules fuer Feature-Template-Rollen. + +**Ziel:** +- Bootstrap nur System-RBAC, nur bei leerer DB. +- Feature-Definitionen/Template-Rollen/Feature-Regeln entfernen. +- Feature-Initialisierung verwaltet Katalog + Feature-Regeln. + +### Konkrete Aenderungen in `interfaceBootstrap.py` + +**Aus `initBootstrap()` entfernen:** +- `initFeatures(db)` +- `_initFeatureTemplateRoleAccessRules(db)` + +**Feature-Template-Rollen entfernen:** +- `_initFeatureTemplateRoles(db)` und alle Aufrufe + +**Feature-Regeln entfernen:** +- `_initFeatureTemplateRoleAccessRules(db)` und alle Aufrufe + +**System-RBAC behalten:** +- `initRoles()` fuer globale Rollen `admin`, `user`, `viewer` +- `initRbacRules()` fuer: + - Default DATA rules + - Table-spezifische DATA rules + - UI rules (view) + - RESOURCE rules (view) + +**Bootstrap-Guard:** +- Nur bei leerer DB. +- System-UI-Objekte im Backend registrieren (Bootstrap oder Startup). + +## Offene Punkte + +- Alle Regeln bleiben persistent, bis sie manuell geloescht werden. + +## Feature-Containerisierung + +### Ziel +Jedes Feature in einem eigenen Ordner, damit Features durch Ordner-Praesenz aktiv sind. Jeder Container enthaelt: +- `datamodelFeatureXxx.py` +- `interfaceFeatureXxx.py` +- `routeFeatureXxx.py` +- main module fuer RBAC-Objekte +- optionale Helpers + +### Aktuelle Feature-Dateien + +**Routes (gateway/modules/routes):** +- `routeFeatureTrustee.py` +- `routeFeatureChatbot.py` +- `routeFeatureChatDynamic.py` (chatworkflow) +- `routeFeatureNeutralization.py` +- `routeFeatureRealEstate.py` +- `routeFeatureWorkflow.py` (automation events) +- `routeFeatures.py` (Feature/Instance-Management, kein Feature) + +**Interfaces (gateway/modules/interfaces):** +- `interfaceDbTrustee.py` +- `interfaceDbChatbot.py` (shared mit workflow) +- `interfaceDbRealEstate.py` +- `interfaceFeatures.py` (Feature/Instance-Management, kein Feature) + +**Datamodels (gateway/modules/datamodels):** +- `datamodelTrustee.py` +- `datamodelChat.py` +- `datamodelWorkflow.py` +- `datamodelRealEstate.py` +- `datamodelNeutralizer.py` +- `datamodelFeatures.py` (Feature/Instance-Management, kein Feature) + +**Feature-Logik (gateway/modules/features):** +- `chatbot/` (mainChatbot.py, eventManager.py, chatbotConstants.py) +- `realEstate/` (mainRealEstate.py) +- `neutralizePlayground/` (mainNeutralizePlayground.py) + +**Nicht-Feature Module (aus features heraus):** +- `gateway/modules/features/featuresLifecycle.py` -> `gateway/modules/workflows/automation/` (Event Scheduler Handling) +- `gateway/modules/features/workflow/` -> `gateway/modules/workflows/automation/` (zentrale Workflow Execution Engine) +- `gateway/modules/features/dynamicOptions/` -> entfernen, dynamische Optionen ueber API-Routen + +### Zielstruktur pro Feature + +**trustee** (neu): +- Move: + - `modules/routes/routeFeatureTrustee.py` + - `modules/interfaces/interfaceDbTrustee.py` + - `modules/datamodels/datamodelTrustee.py` +- Add: + - `modules/features/trustee/mainTrustee.py` (RBAC UI/RESOURCE-Katalog) +- Add main module fuer RBAC-Katalog (falls noch nicht vorhanden) + +**chatbot**: +- Move: + - `modules/routes/routeFeatureChatbot.py` + - `modules/interfaces/interfaceDbChatbot.py` + - modules/datamodels/datamodelChatbot.py +- Keep: + - `modules/features/chatbot/mainChatbot.py`, Helpers +- Add main module fuer RBAC-Katalog (falls noch nicht vorhanden) + +**aichat** (chatworkflow): +- Move: + - `modules/routes/routeFeatureChatDynamic.py` + - `modules/interfaces/interfaceDbChat.py` + - modules/datamodels/datamodelChat.py +- Add main module fuer RBAC-Katalog (falls noch nicht vorhanden) + +**neutralizer** (Rename von neutralizePlayground): +- Move: + - `modules/routes/routeFeatureNeutralization.py` + - `modules/datamodels/datamodelNeutralizer.py` +- Keep: + - `modules/features/neutralizePlayground/mainNeutralizePlayground.py` (Rename Ordner zu `neutralizer`) +- Add main module fuer RBAC-Katalog (falls noch nicht vorhanden) + +**realestate**: +- Move: + - `modules/routes/routeFeatureRealEstate.py` + - `modules/interfaces/interfaceDbRealEstate.py` + - `modules/datamodels/datamodelRealEstate.py` +- Keep: + - `modules/features/realEstate/mainRealEstate.py` +- Add main module fuer RBAC-Katalog (falls noch nicht vorhanden) + +**automation** (neu): +- Move: + - `modules/routes/routeDataAutomation.py` + - `modules/interfaces/interfaceDbAutomation.py` <<- new, to put all automation related interfaces here>> + - `modules/datamodels/datamodelAutomation.py` <<- new, to put class AutomationDefinition here>> +- Add main module fuer RBAC-Katalog (falls noch nicht vorhanden) + +- Neue datamodel/interface/route/main Module + +### App-Routing als Plug-and-Play +`gateway/app.py` importiert `routeFeature*.py` aus Feature-Containern. +Pattern: +- `from modules.features..routeFeature import router as ` +- Nur existierende Ordner werden eingebunden. +- Feature-Ordner loeschen = Feature deaktivieren. + +### Notwendige Referenz-Updates +- Imports in `app.py` auf neue Pfade. +- Alle direkten Imports der alten `modules/routes/routeFeature*.py`. +- `featuresLifecycle.py` und `workflow` nach `gateway/modules/workflows/automation/` verschieben. +- `dynamicOptions` entfernen und UI/API auf Options-Routen umstellen. +- Referenzen auf `modules/interfaces/interfaceDb*.py` und `modules/datamodels/datamodel*.py` anpassen. + +### Machbarkeit und Bedenken +- Shared Interfaces/Models (chatbot/workflow) brauchen klare Ownership oder Shared-Modul. +- Feature-Lifecycle sollte Feature-Registry dynamisch laden. +- Feature-Registry (`routeFeatures.py`, `interfaceFeatures.py`, `datamodelFeatures.py`) bleibt global. + +### Fragen / Klaerungen +- Bleiben `routeFeatures.py` und `interfaceFeatures.py` global? -> JA +- Shared chatbot/chat: Shared-Modul oder Duplikation? --> Bereits dupliziert, somit separiert +- Feature-Discovery dynamisch (scan) oder explizite Imports in `app.py`? -> dynamisch + +### Propositionen +- `routeFeatures.py`, `interfaceFeatures.py`, `datamodelFeatures.py` global behalten (System-Feature-Registry). +- Explizite Imports in `app.py`, aber zentral ueber Feature-Index. --> Die einzelnen Features benötigen keine zentrale komponente mehr. Echt plug & play +- Event Scheduler + Workflow Engine in `gateway/modules/workflows/automation/` (kein Feature). +- `dynamicOptions` entfernen, dynamische Daten per spezialisierte API-Routen (RBAC-Admin Stil). + +### Bedenken +- Feature-Removal muss Cron/Jobs/Hook-Registrierungen entfernen. --> bereits implementiert. die werden dann nicht mehr gefunden +- Migration erzeugt viele Import-Aenderungen und Test-Anpassungen. --> korrekt. daher schritt für schritt plan machen +- `dynamicOptions`-Ablösung erfordert UI/API-Umstellung auf neue Options-Routen. + +## Services in Feature-Container integrieren + +### Ziel +Services, die eindeutig zu Features gehoeren, sollen in den jeweiligen Feature-Container verschoben werden und plug&play geladen werden (analog zu Routes). + +### Zuordnung +- **aichat**: + - `gateway/modules/aicore` + - `gateway/modules/services/serviceAi` + - `gateway/modules/services/serviceExtraction` + - `gateway/modules/services/serviceGeneration` + - `gateway/modules/services/serviceWeb` +- **neutralizer**: + - `gateway/modules/services/serviceNeutralization` +- **Generisch (bleibt shared):** + - alle anderen Services bleiben in `gateway/modules/services` + +### Plug&Play fuer Services (analog Routes) +- Feature-Container exportiert Service-Registrierung (z.B. `registerServices()`). +- Zentrale Service-Registry laedt nur vorhandene Feature-Container. +- Entfernt man den Feature-Ordner, werden auch Services nicht mehr geladen. + +### Umsetzungsschritte (Ergaenzung) + +**Schritt S1: aichat-Services verschieben** +- Move: + - `gateway/modules/aicore/` -> `gateway/modules/features/aichat/aicore/` +- Move: + - `gateway/modules/services/serviceAi/` -> `gateway/modules/features/aichat/services/serviceAi/` + - `gateway/modules/services/serviceExtraction/` -> `gateway/modules/features/aichat/services/serviceExtraction/` + - `gateway/modules/services/serviceGeneration/` -> `gateway/modules/features/aichat/services/serviceGeneration/` + - `gateway/modules/services/serviceWeb/` -> `gateway/modules/features/aichat/services/serviceWeb/` +- Referenzen anpassen: + - alle Imports dieser Services auf die neuen Pfade. + - Service-Registry in aichat registriert diese Module. + +**Schritt S2: neutralizer-Services verschieben** +- Move: + - `gateway/modules/services/serviceNeutralization/` -> `gateway/modules/features/neutralizer/services/serviceNeutralization/` +- Referenzen anpassen: + - alle Imports auf neuen Pfad. + - Service-Registry in neutralizer registriert dieses Modul. + +**Schritt S3: Service-Discovery** +- Zentraler Loader (shared) laedt Services pro Feature-Container dynamisch. +- Shared Services bleiben unveraendert in `gateway/modules/services`. + +## Umsetzungskonzept (schrittweise) + +### Schritt 0: Bestandsaufnahme und Freeze +- **Ziel:** saubere Basis, keine parallelen Feature-Refactors. +- **Aktion:** Branch erstellen, keine Feature-Folder loeschen vor Abschluss. + +### Schritt 1: Bootstrap auf System-RBAC reduzieren +- **Datei:** `gateway/modules/interfaces/interfaceBootstrap.py` +- **Aenderung:** + - In `initBootstrap()` entfernen: + - `initFeatures(db)` + - `_initFeatureTemplateRoleAccessRules(db)` + - Entfernen: + - `_initFeatureTemplateRoles(db)` (Funktion + Call-Sites) + - `_initFeatureTemplateRoleAccessRules(db)` (Funktion + Call-Sites) +- **Referenzen:** alle Verweise auf Feature-Template-Rollen und Feature-AccessRules loeschen. +- **Ergebnis:** Bootstrap erstellt nur System-RBAC bei leerer DB. + +### Schritt 2: Feature-Container vorbereiten (Ordnerstruktur) +- **Ziel:** pro Feature ein Container mit klarer Struktur. +- **Neuer Ordner pro Feature:** + - `gateway/modules/features//` + - Pflicht-Module: + - `datamodelFeatureXxx.py` + - `interfaceFeatureXxx.py` + - `routeFeatureXxx.py` + - `main.py` (registriert UI/RESOURCE Katalog) +- **Ergebnis:** leere Container fuer `trustee`, `chatbot`, `aichat`, `neutralizer`, `realestate`, `automation`. + +### Schritt 3: Trustee in Container verschieben +- **Move:** + - `gateway/modules/routes/routeFeatureTrustee.py` -> `gateway/modules/features/trustee/routeFeatureTrustee.py` + - `gateway/modules/interfaces/interfaceDbTrustee.py` -> `gateway/modules/features/trustee/interfaceFeatureTrustee.py` + - `gateway/modules/datamodels/datamodelTrustee.py` -> `gateway/modules/features/trustee/datamodelFeatureTrustee.py` +- **Neu:** + - `gateway/modules/features/trustee/mainTrustee.py` (RBAC UI/RESOURCE Katalogregistrierung) +- **Referenzen anpassen:** + - Alle Imports von `routeFeatureTrustee.py`, `interfaceDbTrustee.py`, `datamodelTrustee.py` + - `app.py` Router-Import und Registrierung + - Tests, falls vorhanden + +### Schritt 4: Chatbot in Container verschieben +- **Move:** + - `gateway/modules/routes/routeFeatureChatbot.py` -> `gateway/modules/features/chatbot/routeFeatureChatbot.py` + - `gateway/modules/interfaces/interfaceDbChatbot.py` -> `gateway/modules/features/chatbot/interfaceFeatureChatbot.py` + - `gateway/modules/datamodels/datamodelChatbot.py` -> `gateway/modules/features/chatbot/datamodelFeatureChatbot.py` +- **Keep:** + - `gateway/modules/features/chatbot/mainChatbot.py` + Helpers +- **Neu:** + - `gateway/modules/features/chatbot/mainChatbotCatalog.py` (oder in `mainChatbot.py`) +- **Referenzen anpassen:** + - Alle Imports von `routeFeatureChatbot.py`, `interfaceDbChatbot.py`, `datamodelChatbot.py` + - `app.py` Router-Import und Registrierung + +### Schritt 5: AIChat (chatworkflow) in Container verschieben +- **Move:** + - `gateway/modules/routes/routeFeatureChatDynamic.py` -> `gateway/modules/features/aichat/routeFeatureAiChat.py` + - `gateway/modules/interfaces/interfaceDbChat.py` -> `gateway/modules/features/aichat/interfaceFeatureAiChat.py` + - `gateway/modules/datamodels/datamodelChat.py` -> `gateway/modules/features/aichat/datamodelFeatureAiChat.py` +- **Keep:** + - `gateway/modules/features/workflow/mainWorkflow.py` (falls nur Workflow-Logik) +- **Neu:** + - `gateway/modules/features/aichat/mainAiChat.py` (RBAC UI/RESOURCE Katalog) +- **Referenzen anpassen:** + - Alle Imports von `routeFeatureChatDynamic.py`, `interfaceDbChat.py`, `datamodelChat.py` + - `app.py` Router-Import und Registrierung + +### Schritt 6: Neutralizer in Container verschieben und Ordner umbenennen +- **Rename:** + - `gateway/modules/features/neutralizePlayground/` -> `gateway/modules/features/neutralizer/` +- **Move:** + - `gateway/modules/routes/routeFeatureNeutralization.py` -> `gateway/modules/features/neutralizer/routeFeatureNeutralizer.py` + - `gateway/modules/datamodels/datamodelNeutralizer.py` -> `gateway/modules/features/neutralizer/datamodelFeatureNeutralizer.py` +- **Neu:** + - `gateway/modules/features/neutralizer/mainNeutralizer.py` (RBAC UI/RESOURCE Katalog) +- **Referenzen anpassen:** + - Alle Imports auf `neutralizePlayground` + - `app.py` Router-Import und Registrierung + +### Schritt 7: RealEstate in Container verschieben +- **Move:** + - `gateway/modules/routes/routeFeatureRealEstate.py` -> `gateway/modules/features/realestate/routeFeatureRealEstate.py` + - `gateway/modules/interfaces/interfaceDbRealEstate.py` -> `gateway/modules/features/realestate/interfaceFeatureRealEstate.py` + - `gateway/modules/datamodels/datamodelRealEstate.py` -> `gateway/modules/features/realestate/datamodelFeatureRealEstate.py` +- **Keep:** + - `gateway/modules/features/realEstate/mainRealEstate.py` +- **Neu:** + - `gateway/modules/features/realestate/mainRealEstateCatalog.py` (oder in mainRealEstate) +- **Referenzen anpassen:** + - Alle Imports der alten Pfade + - `app.py` Router-Import und Registrierung + +### Schritt 8: Automation als Feature-Container +- **Move:** + - `gateway/modules/routes/routeDataAutomation.py` -> `gateway/modules/features/automation/routeFeatureAutomation.py` +- **Neu:** + - `gateway/modules/features/automation/interfaceFeatureAutomation.py` + - `gateway/modules/features/automation/datamodelFeatureAutomation.py` (enthaelt `AutomationDefinition`) + - `gateway/modules/features/automation/mainAutomation.py` (RBAC UI/RESOURCE Katalog) +- **Referenzen anpassen:** + - Alle Imports von `routeDataAutomation.py` + - `app.py` Router-Import und Registrierung + +### Schritt 9: App-Routing dynamisch +- **Datei:** `gateway/app.py` +- **Aenderung:** + - Entferne direkte Imports aus `modules/routes/`. + - Importiere Router aus Feature-Containern dynamisch (Feature-Discovery). +- **Referenzen:** alle Router-Registrierungen auf neue Pfade/Discovery umstellen. + +### Schritt 10: featuresLifecycle anpassen +- **Datei:** `gateway/modules/features/featuresLifecycle.py` +- **Aenderung:** + - Feature-Registry dynamisch laden (Folder-Discovery). + - Keine direkten Imports aus alten Pfaden. +- **Referenzen:** Aufrufe in `app.py` bleiben, aber Lifecycle nutzt Feature-Liste. + +### Schritt 11: Referenzen global bereinigen +- **Ziel:** Import-Pfade konsistent. +- **Aenderungen:** + - Alle Imports von `modules/routes/routeFeature*.py` entfernen. + - Alle Imports von `modules/interfaces/interfaceDb*.py` auf `interfaceFeature*.py` umstellen. + - Alle Imports von `modules/datamodels/datamodel*.py` auf `datamodelFeature*.py` umstellen. + +### Schritt 12: RBAC Katalog Registrierungen pro Feature +- **Ziel:** jede Feature-Instanz registriert UI/RESOURCE Keys. +- **Dateien:** `main.py` je Feature-Container +- **Aenderung:** einheitliche Registrierungs-API verwenden (im Katalog-Service). + +### Schritt 13: Tests und Verifikation +- **Smoke Tests:** + - Start Gateway, pruefen dass alle Feature-Routes registriert sind. + - Entferne Feature-Ordner testweise -> Route nicht mehr vorhanden. + - RBAC Katalog liefert Objekte fuer alle Features. + - Bootstrap erzeugt nur System-RBAC bei leerer DB. diff --git a/implementation/rbac_access_umsetzungskonzept done.md b/implementation/rbac_access_umsetzungskonzept done.md new file mode 100644 index 0000000..0a4106e --- /dev/null +++ b/implementation/rbac_access_umsetzungskonzept done.md @@ -0,0 +1,463 @@ +# Umsetzungskonzept RBAC Access (separat, schrittweise) + +## Ziel +Das RBAC-Konzept aus `concept_rbac_access_pending.md` technisch umsetzen, inkl.: +- Feature-Containerisierung (plug&play) +- Bootstrap auf System-RBAC reduzieren +- Feature-Services in Container integrieren +- aicore in aichat integrieren +- dynamisches Laden von Routes und Services +- striktes Import-Regelwerk ohne Abhaengigkeiten zwischen Feature-Containern + +## Import-Regelwerk (Zielzustand) +- **Kein Import zwischen Feature-Containern.** +- **Feature-Container duerfen zentrale Module importieren (`modules/interfaces`, `modules/datamodels`, `modules/services`, `modules/aicore`, `modules/shared`, `modules/connectors`, `modules/security`, `modules/auth`).** +- **Umgekehrt darf zentraler Code keine Feature-Container importieren.** + +## Wichtige konzeptionelle und architektonische Hinweise +- Jeder Feature-Container ist vollstaendig eigenstaendig (Code, Datamodelle, Interfaces, Routes, Services), darf aber zentrale Module nutzen. +- Zentrale Pfade bleiben Plattform-Schnittstellen und duerfen von Features genutzt werden; umgekehrt keine Feature-Imports aus zentralem Code. +- System-RBAC und Feature-RBAC sind strikt getrennt: Bootstrap initialisiert nur System-RBAC, Feature-RBAC entsteht durch Feature-Container. +- Der RBAC-Katalog fuer UI/RESOURCE liegt in `gateway/modules/security/rbacCatalog/` und ist persistent; Feature-Container muessen nur registrieren, nicht speichern. +- UI/RESOURCE-Objekte werden rein im Backend definiert und persistiert, keine UI-Abhaengigkeit. +- Datenobjekte werden dynamisch abgeleitet, sind aber nicht auf Features beschraenkt (System- und Feature-Tabellen sind sichtbar). +- Strikte Importregel erzeugt harte Grenzen: Shared-Module muessen in den Feature-Container verschoben oder repliziert werden. +- Zentraler Workflow (geplant unter `gateway/modules/workflows/automation/`) darf aus Features nicht direkt importiert werden; Zugriff nur ueber feature-interne Adapter/Entry-Points oder durch Verlagerung der benoetigten Teile in die Feature-Container. + +## Schrittfolge mit Dateiaenderungen und Referenz-Checkliste + +### Schritt 1: Bootstrap auf System-RBAC reduzieren +**Datei:** `gateway/modules/interfaces/interfaceBootstrap.py` +- Entfernen in `initBootstrap()`: + - `initFeatures(db)` + - `_initFeatureTemplateRoleAccessRules(db)` +- Entfernen: + - `_initFeatureTemplateRoles(db)` (Funktion + Call-Sites) + - `_initFeatureTemplateRoleAccessRules(db)` (Funktion + Call-Sites) +- **Beibehalten:** `initRoles(db)`, `initRbacRules(db)` +- **Checkliste Referenzen:** + - Keine Feature-Initialisierung in Bootstrap + - Keine Feature-Template-Rollen + - Keine Feature-Template-AccessRules + +### Schritt 2: Feature-Container-Struktur anlegen +**Neue Ordner:** +- `gateway/modules/features/trustee/` +- `gateway/modules/features/chatbot/` (existiert) +- `gateway/modules/features/aichat/` (neu) +- `gateway/modules/features/neutralizer/` (rename) +- `gateway/modules/features/realestate/` (neu) +- `gateway/modules/features/automation/` (neu) + +**Pflichtdateien je Feature:** +- `datamodelFeatureXxx.py` +- `interfaceFeatureXxx.py` +- `routeFeatureXxx.py` +- `mainXxx.py` (RBAC UI/RESOURCE Registry) + +**Struktur-Vorgaben (Soll):** +- Jeder Container enthaelt alle benoetigten Submodule, inkl. Services und Helfer. +- Zentrale Module duerfen genutzt werden; keine Feature-zu-Feature-Imports. +- Service-Subfolder bleiben erhalten, liegen aber direkt im Container (`serviceXxx/`). + +**Calling-Referenzen (allgemein):** +- `gateway/app.py` registriert alle Feature-Router dynamisch. +- Feature-Container registrieren ihre RBAC-Objekte im Katalog (z.B. via `mainXxx.py` als Entry-Point). +- Systemcode greift nicht auf Feature-Implementierung zu, sondern nur auf deren registrierte Router/Services. + +### Schritt 3: Trustee verschieben +**Move:** +- `gateway/modules/routes/routeFeatureTrustee.py` + -> `gateway/modules/features/trustee/routeFeatureTrustee.py` +- `gateway/modules/interfaces/interfaceDbTrustee.py` + -> `gateway/modules/features/trustee/interfaceFeatureTrustee.py` +- `gateway/modules/datamodels/datamodelTrustee.py` + -> `gateway/modules/features/trustee/datamodelFeatureTrustee.py` +**Add:** +- `gateway/modules/features/trustee/mainTrustee.py` (RBAC Katalog) + +**Referenzen anpassen (Checkliste):** +- Alle Imports auf `routeFeatureTrustee.py` neu +- Alle Imports auf `interfaceDbTrustee.py` neu +- Alle Imports auf `datamodelTrustee.py` neu +- `app.py` Router-Registrierung auf neuen Pfad +**Calling-Referenzen (Suche):** +- `rg -n "routeFeatureTrustee|interfaceDbTrustee|datamodelTrustee" gateway/` + +### Schritt 4: Chatbot verschieben +**Move:** +- `gateway/modules/routes/routeFeatureChatbot.py` + -> `gateway/modules/features/chatbot/routeFeatureChatbot.py` +- `gateway/modules/interfaces/interfaceDbChatbot.py` + -> `gateway/modules/features/chatbot/interfaceFeatureChatbot.py` +- `gateway/modules/datamodels/datamodelChatbot.py` + -> `gateway/modules/features/chatbot/datamodelFeatureChatbot.py` +**Keep:** +- `gateway/modules/features/chatbot/mainChatbot.py` + Helpers +**Add:** +- `gateway/modules/features/chatbot/mainChatbotCatalog.py` (oder in mainChatbot) + +**Referenzen anpassen:** +- Imports fuer `interfaceDbChatbot`/`datamodelChatbot`/`routeFeatureChatbot` +- `app.py` Router-Registrierung +**Calling-Referenzen (Suche):** +- `rg -n "routeFeatureChatbot|interfaceDbChatbot|datamodelChatbot" gateway/` + +### Schritt 5: AIChat (chatworkflow) verschieben +**Move:** +- `gateway/modules/routes/routeFeatureChatDynamic.py` + -> `gateway/modules/features/aichat/routeFeatureAiChat.py` +- `gateway/modules/interfaces/interfaceDbChat.py` + -> `gateway/modules/features/aichat/interfaceFeatureAiChat.py` +- `gateway/modules/datamodels/datamodelChat.py` + -> `gateway/modules/features/aichat/datamodelFeatureAiChat.py` +**Add:** +- `gateway/modules/features/aichat/mainAiChat.py` (RBAC Katalog) +**Keep:** +- `gateway/modules/features/workflow/mainWorkflow.py` (falls reine Workflow-Logik) + +**Referenzen anpassen:** +- Alle Imports auf `routeFeatureChatDynamic.py` +- Alle Imports auf `interfaceDbChat.py` +- Alle Imports auf `datamodelChat.py` +- `app.py` Router-Registrierung +**Calling-Referenzen (Suche):** +- `rg -n "routeFeatureChatDynamic|interfaceDbChat|datamodelChat" gateway/` + +### Schritt 6: Neutralizer verschieben und Ordner umbenennen +**Rename:** +- `gateway/modules/features/neutralizePlayground/` + -> `gateway/modules/features/neutralizer/` +**Move:** +- `gateway/modules/routes/routeFeatureNeutralization.py` + -> `gateway/modules/features/neutralizer/routeFeatureNeutralizer.py` +- `gateway/modules/datamodels/datamodelNeutralizer.py` + -> `gateway/modules/features/neutralizer/datamodelFeatureNeutralizer.py` +**Add:** +- `gateway/modules/features/neutralizer/mainNeutralizer.py` (RBAC Katalog) + +**Referenzen anpassen:** +- Alle Imports auf `neutralizePlayground` +- Alle Imports auf `routeFeatureNeutralization.py` +- `app.py` Router-Registrierung +**Calling-Referenzen (Suche):** +- `rg -n "neutralizePlayground|routeFeatureNeutralization|datamodelNeutralizer" gateway/` + +### Schritt 7: RealEstate verschieben +**Move:** +- `gateway/modules/routes/routeFeatureRealEstate.py` + -> `gateway/modules/features/realestate/routeFeatureRealEstate.py` +- `gateway/modules/interfaces/interfaceDbRealestate.py` + -> `gateway/modules/features/realestate/interfaceFeatureRealEstate.py` +- `gateway/modules/datamodels/datamodelRealEstate.py` + -> `gateway/modules/features/realestate/datamodelFeatureRealEstate.py` +**Keep:** +- `gateway/modules/features/realEstate/mainRealEstate.py` +**Add:** +- `gateway/modules/features/realestate/mainRealEstateCatalog.py` (oder in mainRealEstate) + +**Referenzen anpassen:** +- Imports auf `interfaceDbRealestate`, `datamodelRealEstate`, `routeFeatureRealEstate` +- `app.py` Router-Registrierung +**Calling-Referenzen (Suche):** +- `rg -n "routeFeatureRealEstate|interfaceDbRealestate|datamodelRealEstate" gateway/` + +### Schritt 8: Automation Feature-Container +**Move:** +- `gateway/modules/routes/routeDataAutomation.py` + -> `gateway/modules/features/automation/routeFeatureAutomation.py` +**Add:** +- `gateway/modules/features/automation/interfaceFeatureAutomation.py` +- `gateway/modules/features/automation/datamodelFeatureAutomation.py` + - enthaelt `AutomationDefinition` +- `gateway/modules/features/automation/mainAutomation.py` (RBAC Katalog) + +**Referenzen anpassen:** +- Alle Imports von `routeDataAutomation.py` +- Alle `AutomationDefinition`-Imports auf neuen Pfad +- `app.py` Router-Registrierung +**Calling-Referenzen (Suche):** +- `rg -n "routeDataAutomation|AutomationDefinition" gateway/` + +### Schritt 9: Services in Feature-Container +**aichat:** +- Move: + - `gateway/modules/aicore/` -> `gateway/modules/features/aichat/aicore/` + - `gateway/modules/services/serviceAi/` -> `gateway/modules/features/aichat/serviceAi/` + - `gateway/modules/services/serviceExtraction/` -> `gateway/modules/features/aichat/serviceExtraction/` + - `gateway/modules/services/serviceGeneration/` -> `gateway/modules/features/aichat/serviceGeneration/` + - `gateway/modules/services/serviceWeb/` -> `gateway/modules/features/aichat/serviceWeb/` +**neutralizer:** +- Move: + - `gateway/modules/services/serviceNeutralization/` -> `gateway/modules/features/neutralizer/serviceNeutralization/` +**Shared Services:** +- bleiben in `gateway/modules/services` + +**Referenzen anpassen:** +- Alle Imports der o.g. Services auf neue Pfade +- AI-Services duerfen keine Imports ausserhalb des aichat-Containers haben +- In Feature-Folders kein Subfolder `services`; Services liegen direkt als `serviceXxx/` +**Calling-Referenzen (Suche):** +- `rg -n "modules\\.services\\.service(Ai|Extraction|Generation|Web)|modules\\.aicore" gateway/` +- `rg -n "serviceNeutralization" gateway/` + +**Code-Struktur (Soll):** +- Feature-Services importieren nur Feature-Datamodelle innerhalb des Containers. +- `aicore` wird komplett unter `features/aichat/` verschoben und ist nur dort nutzbar. + +### Schritt 10: Plug&Play Router Loading +**Datei:** `gateway/app.py` +- Ersetze statische Imports aus `modules/routes` durch dynamische Feature-Discovery. +- Nur existierende Feature-Folder werden registriert. + +**Referenzen anpassen:** +- Alle Router-Registrierungen neu +- Keine Route-Imports aus `modules/routes` + +**Konzept Router-Discovery (Soll):** +- Scanne `gateway/modules/features/*/` nach `routeFeature*.py`. +- Pro Datei wird ein `router`-Objekt erwartet und registriert. +- Fehlende Router in einem Feature duerfen den Start nicht abbrechen (log + skip). +- Feature-Status markieren: `active`, `degraded` (z.B. Router ok, Service-Load fehlschlaegt). + +### Schritt 11: Plug&Play Service Loading +**Zentraler Loader (shared):** +- Ein Service-Registry-Modul laedt pro Feature-Container definierte Services. +- Nur existierende Feature-Folder werden registriert. + +**Referenzen anpassen:** +- Alle Service-Center-Aufrufe auf neuen Loader +- Kein direkter Import aus `modules/services/serviceAi` etc. + +**Konzept Service-Discovery (Soll):** +- Pro Feature-Container werden vorhandene `service*`-Ordner als Services erkannt. +- Der Loader scannt `gateway/modules/features//service*/` und registriert diese Services zentral. +- Services werden nur geladen, wenn der Feature-Container vorhanden ist. +- Registrierungs-Contract analog `modules/services/__init__.py`: + - In jedem `service*`-Ordner existiert `mainService*.py` mit Service-Klasse. + - Loader instanziert Service und exponiert ihn via `PublicService(...)`. + +### Schritt 12: featuresLifecycle anpassen +**Datei:** `gateway/modules/features/featuresLifecycle.py` +- Import der Workflow-Logik aus neuem zentralen Pfad (`gateway/modules/workflows/automation/`) +- Keine Imports von Feature-Containern + +**Bemerkung:** +- Kein Feature-Discovery notwendig: Modul ist zentraler Scheduler fuer Workflow/Automation. +- Update der Imports: `modules.features.workflow` -> `modules.workflows.automation` (nach Auslagerung). +- Workflow-API ist zentral und stabil: Features duerfen nur explizite Entry-Points nutzen (z.B. `syncAutomationEvents`), keine direkten Imports in interne Workflow-Module. + +### Schritt 13: Globale Referenzen bereinigen +**Checkliste:** +- Keine Imports von `modules/routes/routeFeature*.py` mehr +- `interfaceDb*.py` und `datamodel*.py` bleiben fuer System-Module bestehen +- `modules/services/service*` bleiben fuer System-Services bestehen +- `modules/aicore` wird verschoben (Feature `aichat`), keine zentralen Imports mehr + +**Calling-Referenzen (Suche):** +- `rg -n "modules\\.routes\\.routeFeature" gateway/` + +### Schritt 14: Tests +- Gateway starten, Router-Registrierung pruefen +- Feature-Folder umbenennen mit z.b. prefix: Route/Service nicht mehr vorhanden +- RBAC Katalog liefert Feature-Objekte +- Bootstrap erzeugt nur System-RBAC +- RBAC Regeln bleiben erhalten, wenn Feature-Ordner entfernt/umbenannt ist (Persistenztest). +- Service-Discovery registriert Services korrekt und markiert `degraded` bei Teilfehlern. + +## Referenz-Checkliste (gesamt) +- `gateway/app.py` Router +- `gateway/modules/features/featuresLifecycle.py` +- alle `routeFeature*.py` +- alle `interfaceDb*.py` +- alle `datamodel*.py` +- alle `main*.py` in Feature-Containern +- Service-Center/Service-Loader +- Tests/Fixtures + - RBAC-Katalog Modul in `gateway/modules/security/rbacCatalog/` + - `gateway/modules/interfaces/interfaceBootstrap.py` + +## Import-Analyse (Istzustand, zu verschiebende Module) + +### Routes (aktueller Stand) +- `routeFeatureRealEstate.py`: + - imports: `modules.auth`, `modules.datamodels.*`, `modules.interfaces.interfaceDbRealestate`, `modules.features.realEstate.mainRealEstate`, `modules.connectors`, `modules.shared` +- `routeFeatureTrustee.py`: + - imports: `modules.auth`, `modules.interfaces.interfaceDbTrustee`, `modules.interfaces.interfaceDbApp`, `modules.interfaces.interfaceFeatures`, `modules.datamodels.*` +- `routeFeatureChatDynamic.py`: + - imports: `modules.auth`, `modules.interfaces.interfaceDbChat`, `modules.datamodels.datamodelChat`, `modules.features.workflow` +- `routeFeatureChatbot.py`: + - imports: `modules.auth`, `modules.interfaces.interfaceDbChat`, `modules.interfaces.interfaceRbac`, `modules.datamodels.*`, `modules.features.chatbot`, `modules.features.workflow`, `modules.shared` +- `routeFeatureNeutralization.py`: + - imports: `modules.auth`, `modules.datamodels.datamodelNeutralizer`, `modules.features.neutralizePlayground` +- `routeDataAutomation.py`: + - imports: `modules.interfaces.interfaceDbChat`, `modules.auth`, `modules.datamodels.datamodelChat`, `modules.features.workflow`, `modules.shared` + +### Interfaces (aktueller Stand) +- `interfaceDbTrustee.py`: + - imports: `modules.connectors`, `modules.shared`, `modules.interfaces.interfaceRbac`, `modules.security`, `modules.datamodels.*` +- `interfaceDbRealestate.py`: + - imports: `modules.datamodels.*`, `modules.connectors`, `modules.shared`, `modules.security`, `modules.interfaces.interfaceRbac` +- `interfaceDbChat.py` / `interfaceDbChatbot.py`: + - imports: `modules.security`, `modules.datamodels.*`, `modules.connectors`, `modules.shared`, `modules.interfaces.interfaceRbac`, `modules.interfaces.interfaceDbManagement` + +### Feature-Logik (aktueller Stand) +- `modules/features/chatbot/mainChatbot.py`: + - imports: `modules.datamodels.*`, `modules.shared`, `modules.services`, `modules.workflows`, `modules.connectors` +- `modules/features/workflow/mainWorkflow.py`: + - imports: `modules.datamodels.*`, `modules.shared`, `modules.services`, `modules.workflows` +- `modules/features/realEstate/mainRealEstate.py`: + - imports: `modules.datamodels.*`, `modules.services`, `modules.interfaces.interfaceDbRealestate`, `modules.connectors`, externe libs +- `modules/features/neutralizePlayground/mainNeutralizePlayground.py`: + - imports: `modules.datamodels.*`, `modules.services` + +### Services / aicore (aktueller Stand) +- `modules/services/serviceAi/mainServiceAi.py`: + - imports: `modules.datamodels.*`, `modules.services.serviceExtraction`, `modules.interfaces.interfaceAiObjects`, `modules.shared` +- `modules/services/serviceNeutralization/mainServiceNeutralization.py`: + - imports: `modules.datamodels.datamodelNeutralizer`, eigene subProcess-* Module +- `modules/aicore/*`: + - imports: `modules.datamodels.*`, `modules.security`, `modules.connectors` + +### Services Submodule-Analyse (Detail) + +**serviceAi/** +- `mainServiceAi.py` + - imports: `modules.datamodels.datamodelChat`, `modules.datamodels.datamodelAi`, + `modules.datamodels.datamodelExtraction`, `modules.datamodels.datamodelWorkflow`, + `modules.datamodels.datamodelDocument`, `modules.interfaces.interfaceAiObjects`, + `modules.services.serviceExtraction`, `modules.shared` +- `subStructureGeneration.py` + - imports: `modules.datamodels.datamodelExtraction`, `modules.datamodels.datamodelAi`, + `modules.workflows.processing.shared.stateTools` +- `subStructureFilling.py` + - imports: `modules.datamodels.datamodelExtraction`, `modules.datamodels.datamodelAi`, + `modules.workflows.processing.shared.stateTools` +- `subResponseParsing.py` + - imports: `modules.shared.jsonUtils`, `modules.services.serviceAi.subJsonResponseHandling`, + `modules.datamodels.datamodelAi` +- `subJsonResponseHandling.py` + - imports: `modules.shared.jsonUtils`, `modules.datamodels.datamodelAi` +- `subJsonMerger.py` + - imports: `modules.shared.jsonUtils` +- `subDocumentIntents.py` + - imports: `modules.datamodels.datamodelChat`, `modules.datamodels.datamodelExtraction`, + `modules.workflows.processing.shared.stateTools` +- `subContentExtraction.py` + - imports: `modules.datamodels.datamodelChat`, `modules.datamodels.datamodelExtraction`, + `modules.workflows.processing.shared.stateTools` +- `subAiCallLooping.py` + - imports: `modules.datamodels.datamodelAi`, `modules.datamodels.datamodelExtraction`, + `modules.services.serviceAi.subJsonResponseHandling`, + `modules.services.serviceAi.subLoopingUseCases`, + `modules.workflows.processing.shared.stateTools`, + `modules.shared.jsonContinuation`, `modules.shared.jsonUtils` +- `subLoopingUseCases.py`, `subJsonResponseHandling.py`, `subJsonMerger.py` (shared utils) + - imports: `modules.shared.*` (je nach Datei) + +**serviceExtraction/** +- `mainServiceExtraction.py` + - imports: `modules.datamodels.datamodelExtraction`, `modules.datamodels.datamodelChat`, + `modules.datamodels.datamodelAi`, `modules.aicore.aicoreModelRegistry`, + `modules.aicore.aicoreModelSelector`, `modules.shared.jsonUtils` +- `merging/*` (mergerText/mergerTable/mergerDefault) + - imports: `modules.datamodels.datamodelExtraction` +- `extractors/*` (Xml/Xlsx/Text/Sql/Pptx/Html/Image/Json/Pdf/Csv/Docx/Binary) + - imports: `modules.datamodels.datamodelExtraction` (teilweise ContentExtracted) +- `chunking/*` (chunkerText/ChunkerTable/ChunkerStructure/ChunkerImage) + - imports: `modules.datamodels.datamodelExtraction` +- `subRegistry.py` + - imports: `modules.datamodels.datamodelExtraction` +- `subPromptBuilderExtraction.py` + - imports: `modules.datamodels.datamodelAi` +- `subPipeline.py` + - imports: `modules.datamodels.datamodelExtraction` +- `subMerger.py` + - imports: `modules.datamodels.datamodelExtraction` + +**serviceGeneration/** +- `mainServiceGeneration.py` + - imports: `modules.datamodels.datamodelDocument`, `modules.datamodels.datamodelChat`, + `modules.services.serviceGeneration.subDocumentUtility` +- `renderers/*` + - imports: `modules.datamodels.datamodelDocument` (alle Renderer) + - `documentRendererBaseTemplate.py` imports zusaetzlich: + `modules.datamodels.datamodelJson`, `modules.datamodels.datamodelAi` +- `paths/*` + - `imagePath.py`: `modules.datamodels.datamodelWorkflow`, `modules.datamodels.datamodelAi` + - `documentPath.py`: `modules.datamodels.datamodelWorkflow`, `modules.datamodels.datamodelExtraction`, + `modules.datamodels.datamodelAi`, `modules.datamodels.datamodelDocument`, + `modules.workflows.processing.shared.stateTools` + - `codePath.py`: `modules.datamodels.datamodelWorkflow`, `modules.datamodels.datamodelExtraction`, + `modules.datamodels.datamodelAi`, `modules.shared.jsonUtils` +- `subStructureGenerator.py` / `subPromptBuilderGeneration.py` + - imports: `modules.datamodels.datamodelJson` +- `subContentGenerator.py` + - imports: `modules.services.serviceGeneration.subContentIntegrator`, + `modules.workflows.processing.shared.stateTools` + +**serviceWeb/** +- `mainServiceWeb.py` + - imports: `modules.datamodels.datamodelAi` (AiCallOptions, OperationTypeEnum, AiCallPrompt*) + +**serviceNeutralization/** +- `mainServiceNeutralization.py` + - imports: `modules.datamodels.datamodelNeutralizer`, + `modules.services.serviceNeutralization.subProcess*` +- `subProcessText.py` + - imports: `modules.services.serviceNeutralization.subParseString` +- `subProcessList.py` + - imports: `modules.services.serviceNeutralization.subParseString`, + `modules.services.serviceNeutralization.subPatterns` +- `subParseString.py` + - imports: `modules.services.serviceNeutralization.subPatterns` + +## Compliance-Check gegen Import-Regelwerk (Zielzustand) + +**Zielkriterium:** +- Keine Imports zwischen Feature-Containern. +- Feature-Container duerfen zentrale Module importieren. +- Zentrale Module duerfen keine Feature-Container importieren. + +**Erwarteter Zustand nach Migration:** +- Alle `interfaceFeatureXxx.py`, `routeFeatureXxx.py`, `datamodelFeatureXxx.py`, `mainXxx.py` liegen im jeweiligen Feature-Container. +- Interne Feature-Imports (z.B. `chatbot` -> `chatbot.eventManager`) sind erlaubt. +- Keine Feature-zu-Feature-Imports mehr (z.B. `chatbot` -> `workflow` entfaellt durch Auslagerung). + +**Hinweis zur Validierung:** +- Compliance-Check erfolgt nach Abschluss der Verschiebungen und Import-Anpassungen. +- Der Istzustand dient nur als Analysegrundlage und ist nicht compliance-faehig. + +## Import-Check (Zielzustand, Soll) + +**Erlaubte Imports innerhalb eines Feature-Containers:** +- Relative Imports innerhalb des Containers (z.B. `.eventManager`, `.subModule`). +- Absolute Imports innerhalb des eigenen Containers (z.B. `modules.features.chatbot.*` im Container `chatbot`). +- Standardbibliothek und externe Libraries. + +**Nicht erlaubt (muss im Zielzustand eliminiert werden):** +- Imports aus anderen Feature-Containern (`modules.features..*`). +- Zentrale Module importieren Feature-Container (`modules.features.*`). + +**Soll-Checkliste je Feature-Container:** +- Keine `modules.features..*` Imports. +- Keine Feature-zu-Feature-Abhaengigkeiten. + +**Pruefroutine (rg-Patterns, Zielzustand):** +- Finde direkte Cross-Feature-Imports: + - `rg -n "modules\\.features\\.(?!\\.)" gateway/modules/features//` +- Finde verbotene Feature-Imports aus zentralem Code: + - `rg -n "modules\\.features\\." gateway/modules/(routes|interfaces|datamodels|services|aicore|shared|connectors|security|auth)/` + +## Istzustand (nur Analyse, vor Migration) + +**Konflikte (heute):** +- Alle Routen, Interfaces, Feature-Logiken und Services importieren Module ausserhalb eines Feature-Containers. +- Es bestehen Imports zwischen Features (z.B. `routeFeatureChatbot` -> `modules.features.workflow`, im Zielzustand entfaellt da `workflow` kein Feature mehr ist). + +**Liste der Imports zwischen Features (Istzustand, Analyse):** +- `gateway/modules/interfaces/interfaceDbChatbot.py` -> `modules.features.chatbot.eventManager` + +## Offene Punkte fuer Umsetzung +- Dynamische Feature-Discovery in `app.py` und Service-Loader designen +- Schrittweise Migration je Feature mit Tests diff --git a/implementation/trustee_feature_rbac_architecture.md b/implementation/trustee_feature_rbac_architecture.md new file mode 100644 index 0000000..983fa2e --- /dev/null +++ b/implementation/trustee_feature_rbac_architecture.md @@ -0,0 +1,687 @@ +# Trustee Feature - RBAC Architektur & Gateway-Readiness + +## Übersicht + +Dieses Dokument beschreibt die **Zwei-Stufen-RBAC-Architektur** für das Trustee Feature und beantwortet die Frage, ob das Gateway bereit für die UI-Entwicklung ist. + +**Erstellungsdatum**: 2026-01-23 +**Status**: Review + +--- + +## 1. Gateway-Status + +### 1.1 Ist das Gateway bereit für UI-Arbeit? + +**Ja, das Gateway ist vollständig bereit.** + +| Komponente | Datei | Status | +|------------|-------|--------| +| **Routes** | `routeFeatureTrustee.py` | ✅ Komplett | +| **Interface** | `interfaceFeatureTrustee.py` | ✅ Komplett | +| **Datamodel** | `datamodelFeatureTrustee.py` | ✅ Komplett | +| **Feature-RBAC** | Via `TrusteeAccess` Tabelle | ✅ Implementiert | + +### 1.2 Verfügbare API-Endpoints + +Alle Endpoints verwenden das URL-Pattern: `/api/trustee/{instanceId}/...` + +| Endpoint-Gruppe | Basis-Route | CRUD | Options | +|-----------------|-------------|------|---------| +| Organisations | `/api/trustee/{instanceId}/organisations` | ✅ | ✅ | +| Roles | `/api/trustee/{instanceId}/roles` | ✅ | ✅ | +| **Access** | `/api/trustee/{instanceId}/access` | ✅ | - | +| Contracts | `/api/trustee/{instanceId}/contracts` | ✅ | ✅ | +| Documents | `/api/trustee/{instanceId}/documents` | ✅ | ✅ | +| Positions | `/api/trustee/{instanceId}/positions` | ✅ | ✅ | +| Position-Documents | `/api/trustee/{instanceId}/position-documents` | ✅ | - | +| **User Options** | `/api/users/options` | - | ✅ | + +### 1.3 Wichtige Änderung vs. UI-Spezifikation + +Die ursprüngliche UI-Spezifikation (`doc_trustee_feature_ui_specification.md`) verwendet URLs wie: +``` +/api/trustee/organisations/ +``` + +Die **aktuelle Implementierung** verwendet: +``` +/api/trustee/{instanceId}/organisations/ +``` + +**Grund**: Multi-Tenancy - mehrere Treuhandbüros (Feature-Instanzen) pro Mandate möglich. + +--- + +## 2. Architektur: Zwei-Stufen-RBAC für Features + +### 2.1 Das Problem + +Ein Treuhandbüro (Feature-Instanz) verwaltet mehrere Kunden (Organisationen). Jeder Mitarbeiter soll nur die Kunden sehen, für die er zuständig ist: + +- **User A** → Kunde 1, Kunde 2 +- **User B** → Kunde 2, Kunde 3 +- **User C** → Alle Kunden (Admin) + +Dies erfordert eine **Feature-interne Isolation**, die über das System-RBAC hinausgeht. + +### 2.2 Lösung: Zwei-Stufen-RBAC + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ STUFE 1: SYSTEM-RBAC │ +│ (Mandate + Feature Instance Zugang) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ User ──► UserMandate ──► Mandate │ +│ │ │ +│ └──► FeatureAccess ──► FeatureInstance (Treuhandbüro) │ +│ │ │ +│ └── Rollen: feature-admin, feature-user │ +│ │ +│ Frage: Hat der User überhaupt Zugang zu diesem Feature? │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ STUFE 2: FEATURE-SPEZIFISCHES RBAC │ +│ (Isolation innerhalb des Features) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ TrusteeAccess │ +│ ├── userId → Welcher User │ +│ ├── organisationId → Welcher Kunde (Isolationsobjekt) │ +│ ├── roleId → Welche Feature-Rolle (admin, operate, userreport)│ +│ └── contractId → Optional: Einschränkung auf bestimmten Vertrag │ +│ │ +│ Frage: Welche Kunden darf der User sehen? Mit welchen Rechten? │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.3 Vergleich: System-RBAC vs. Feature-RBAC + +| Aspekt | System-RBAC | Feature-RBAC (Trustee) | +|--------|-------------|------------------------| +| **Tabelle** | `AccessRule` | `TrusteeAccess` | +| **Scope** | Global / Mandate | Feature-Instanz | +| **Objekte** | Tables, UI-Items, API-Resources | Organisationen, Contracts | +| **Verwaltung** | SysAdmin | Feature-Admin | +| **Zweck** | "Was darf der User technisch?" | "Welche Daten darf der User sehen?" | + +### 2.4 Feature-spezifische Rollen + +Das Trustee-Feature definiert drei Rollen: + +| Rolle | Beschreibung | Berechtigungen | +|-------|--------------|----------------| +| **admin** | Feature-Administrator | Voller CRUD auf Organisation + alle Contracts | +| **operate** | Sachbearbeiter | CRUD für Contracts, Documents, Positions | +| **userreport** | Endbenutzer | Nur eigene erstellte Records (Documents, Positions) | + +### 2.5 Contract-Level Isolation (optional) + +`TrusteeAccess.contractId` ermöglicht feinere Isolation: + +- **Leer/NULL**: Zugriff auf alle Contracts der Organisation +- **Gesetzt**: Zugriff nur auf diesen spezifischen Contract + +``` +Beispiel: +- User A hat Zugriff auf Organisation "Kunde-X" mit contractId=NULL + → Sieht alle Contracts von Kunde-X + +- User B hat Zugriff auf Organisation "Kunde-X" mit contractId="contract-123" + → Sieht nur Contract "contract-123" von Kunde-X +``` + +--- + +## 3. Implementierung im Gateway + +### 3.1 Datenmodell: TrusteeAccess + +```python +class TrusteeAccess(BaseModel): + id: str # UUID + organisationId: str # FK zu TrusteeOrganisation + roleId: str # FK zu TrusteeRole (admin, operate, userreport) + userId: str # FK zu User + contractId: Optional[str] # Optional: FK zu TrusteeContract + mandateId: Optional[str] # System-Kontext + featureInstanceId: Optional[str] # Feature-Instanz +``` + +### 3.2 Kombinierte Berechtigungsprüfung + +Die Methode `checkCombinedPermission()` in `interfaceFeatureTrustee.py` kombiniert beide Stufen: + +```python +def checkCombinedPermission(self, modelClass, operation, organisationId, contractId, recordCreatedBy): + """ + Kombiniert System-RBAC + Feature-Level RBAC. + + 1. Prüft System-RBAC (AccessLevel: ALL, GROUP, MY, NONE) + 2. Bei ALL: Bypass Feature-Level (SysAdmin) + 3. Sonst: Prüft TrusteeAccess für User + Organisation + Contract + """ + + # Stufe 1: System-RBAC + accessLevel = self.getRbacAccessLevel(modelClass, operation) + if accessLevel == AccessLevel.NONE: + return False + if accessLevel == AccessLevel.ALL: + return True # SysAdmin bypass + + # Stufe 2: Feature-RBAC via TrusteeAccess + roles = self.getUserTrusteeRoles(self.userId, organisationId, contractId) + + if "admin" in roles: + return True + if "operate" in roles and modelClass in (TrusteeContract, TrusteeDocument, ...): + return True + if "userreport" in roles and recordCreatedBy == self.userId: + return True + + return False +``` + +### 3.3 Automatische Filterung + +Die Methode `filterRecordsByTrusteeAccess()` filtert Ergebnisse basierend auf User-Zugriff: + +```python +def filterRecordsByTrusteeAccess(self, records, modelClass): + """ + Filtert Records basierend auf TrusteeAccess des Users. + + - admin: Sieht alle Records der Organisation + - operate: Sieht alle Records der Organisation + - userreport: Sieht nur eigene Records (_createdBy = userId) + """ +``` + +--- + +## 4. Generisches Muster für andere Features + +### 4.1 Feature-Isolation-Pattern + +Jedes Feature kann sein eigenes Isolationsmodell definieren: + +``` +{Feature}Access: User → {Isolationsobjekt} → Role [→ Unter-Objekt] +``` + +### 4.2 Beispiele für andere Features + +| Feature | Access-Tabelle | Isolationsobjekt | Unter-Objekt | +|---------|----------------|------------------|--------------| +| **Trustee** | `TrusteeAccess` | Organisation (Kunde) | Contract | +| **RealEstate** | `RealEstateAccess` | Property (Liegenschaft) | Unit (Wohnung) | +| **ProjectMgmt** | `ProjectAccess` | Project | - | +| **Accounting** | `AccountingAccess` | Company | FiscalYear | + +### 4.3 Implementierungsvorlage + +Für ein neues Feature mit Isolation: + +1. **Datamodel**: `{Feature}Access` mit `userId`, `{isolationObject}Id`, `roleId` +2. **Interface**: `checkCombinedPermission()` und `filterRecordsByAccess()` +3. **Routes**: CRUD für `{Feature}Access` +4. **Roles**: Feature-spezifische Rollen definieren + +--- + +## 5. API-Referenz für UI-Entwicklung + +### 5.1 Access-Verwaltung (User-Zuweisung zu Kunden) + +``` +# Alle Access-Records abrufen +GET /api/trustee/{instanceId}/access + +# Access für bestimmte Organisation +GET /api/trustee/{instanceId}/access/organisation/{orgId} + +# Access für bestimmten User +GET /api/trustee/{instanceId}/access/user/{userId} + +# Neuen Access erstellen (User zu Kunde zuweisen) +POST /api/trustee/{instanceId}/access +Body: { + "userId": "user-uuid", + "organisationId": "kunde-id", + "roleId": "operate", + "contractId": null // Optional +} + +# Access aktualisieren +PUT /api/trustee/{instanceId}/access/{accessId} + +# Access löschen +DELETE /api/trustee/{instanceId}/access/{accessId} +``` + +### 5.2 Options-Endpoints für Dropdowns + +``` +# Organisationen für Dropdown +GET /api/trustee/{instanceId}/organisations/options +→ [{ "value": "org-id", "label": "Kunde AG" }, ...] + +# Rollen für Dropdown +GET /api/trustee/{instanceId}/roles/options +→ [{ "value": "admin", "label": "Administrator" }, ...] + +# Contracts für Dropdown (dynamisch nach Organisation) +GET /api/trustee/{instanceId}/contracts/options +→ [{ "value": "contract-uuid", "label": "Vertrag 2026" }, ...] + +# Users für Dropdown (aus aktuellem Mandate) +GET /api/users/options +→ [{ "value": "user-uuid", "label": "Max Muster" }, ...] +``` + +--- + +## 6. UI-Anforderungen + +### 6.1 Access-View (User-Verwaltung) + +Eine View zur Verwaltung der User-Zuweisungen: + +| Spalte | Typ | Quelle | +|--------|-----|--------| +| User | Select | `/api/users/options` | +| Organisation | Select | `/api/trustee/{instanceId}/organisations/options` | +| Rolle | Select | `/api/trustee/{instanceId}/roles/options` | +| Contract | Select (optional) | `/api/trustee/{instanceId}/contracts/options` | + +**Hinweis**: Das `contractId` Dropdown sollte dynamisch nach Auswahl der Organisation gefiltert werden. + +### 6.2 Berechtigungen für Access-View + +- **Sichtbar für**: Feature-Admins (`admin` Rolle in mindestens einer Organisation) +- **Create/Update/Delete**: Nur für Organisationen, in denen User Admin ist + +--- + +## 7. Offene Punkte / Entscheidungen + +### 7.1 Geklärt + +- [x] Ist das Gateway bereit? → **Ja** +- [x] Sind Feature-spezifische Rollen gleich wie System-Rollen? → **Nein, bewusst getrennt** +- [x] Ist zweistufiges RBAC nötig? → **Ja, bereits implementiert** +- [x] Existiert der Users-Options-Endpoint? → **Ja, `/api/users/options`** + +### 7.2 Offen für UI-Review + +- [ ] Soll die Access-View eine eigene Seite sein oder Tab in Organisation? +- [ ] Wie wird Contract-Dropdown bei organisationId-Änderung aktualisiert? +- [ ] Braucht es eine "Bulk-Zuweisung" (User zu mehreren Orgs gleichzeitig)? + +--- + +## 8. Zusammenfassung + +### Das Gateway bietet: + +1. **Vollständige CRUD-Routes** für alle Trustee-Entities +2. **Zwei-Stufen-RBAC** (System + Feature-Level) +3. **Automatische Filterung** basierend auf User-Access +4. **Options-Endpoints** für alle Dropdowns + +### Das UI muss implementieren: + +1. **Access-View** zur Verwaltung von User-Zuweisungen +2. **Dynamische Dropdowns** mit Abhängigkeiten (Contract nach Organisation) +3. **instanceId** in allen API-Calls verwenden + +### Architektur-Prinzip: + +``` +System-RBAC → Zugang zum Feature +Feature-RBAC → Isolation innerhalb des Features +``` + +--- + +## 9. Implementierungsplan: Fehlende Elemente + +### 9.1 Aktueller Stand (Analyse) + +| Komponente | Status | Details | +|------------|--------|---------| +| **Gateway (Backend)** | ✅ Komplett | Routes, Interface, Datamodel, RBAC | +| **API Hooks** (`useTrustee.ts`) | ✅ Komplett | CRUD-Hooks für alle Entities | +| **API Client** (`trusteeApi.ts`) | ✅ Komplett | Alle Endpoints implementiert | +| **Views (Grundgerüst)** | ⚠️ Teilweise | Tabellen existieren, aber ohne Forms | +| **Create/Edit Formulare** | ❌ Fehlt | Buttons vorhanden, aber ohne Funktion | +| **Label-Auflösung** | ❌ Fehlt | Zeigt IDs statt Labels (userId, orgId) | +| **Dynamische Dropdowns** | ❌ Fehlt | Contract abhängig von Organisation | + +### 9.2 Fehlende Implementierungen + +#### P1: Create/Edit Formulare für alle Views + +**Problem:** Die "Neu erstellen" und "Bearbeiten" Buttons existieren, aber öffnen kein Formular. + +**Betroffene Views:** +- `TrusteeOrganisationsView.tsx` +- `TrusteeRolesView.tsx` +- `TrusteeAccessView.tsx` +- `TrusteeContractsView.tsx` +- `TrusteeDocumentsView.tsx` +- `TrusteePositionsView.tsx` + +**Lösung:** Pro View ein Modal mit EditForm implementieren, das die automatisch generierten Felder aus `generateEditFieldsFromAttributes()` verwendet. + +**Beispiel-Pattern:** + +```tsx +// In TrusteeOrganisationsView.tsx +const [isModalOpen, setIsModalOpen] = useState(false); +const [editingItem, setEditingItem] = useState(null); +const { generateCreateFieldsFromAttributes, generateEditFieldsFromAttributes } = useTrusteeOrganisations(); +const { handleCreate, handleUpdate } = useTrusteeOrganisationOperations(); + +// Create-Button + + +// Edit-Button + + +// Modal mit EditForm + setIsModalOpen(false)}> + { + if (editingItem) { + await handleUpdate(editingItem.id, data); + } else { + await handleCreate(data); + } + setIsModalOpen(false); + refetch(); + }} + /> + +``` + +--- + +#### P2: Label-Auflösung für Foreign Keys + +**Problem:** `TrusteeAccessView` zeigt: +- `userId: "uuid-123"` statt `"Max Muster"` +- `organisationId: "org-1"` statt `"Kunde AG"` +- `roleId: "admin"` statt `"Administrator"` + +**Lösung:** Options aus den entsprechenden Endpoints laden und als Lookup-Map verwenden. + +**Implementierung:** + +```tsx +// In TrusteeAccessView.tsx +const [userOptions, setUserOptions] = useState<{value: string, label: string}[]>([]); +const [orgOptions, setOrgOptions] = useState<{value: string, label: string}[]>([]); +const [roleOptions, setRoleOptions] = useState<{value: string, label: string}[]>([]); + +useEffect(() => { + const loadOptions = async () => { + const [users, orgs, roles] = await Promise.all([ + api.get('/api/users/options'), + api.get(`/api/trustee/${instanceId}/organisations/options`), + api.get(`/api/trustee/${instanceId}/roles/options`) + ]); + setUserOptions(users.data); + setOrgOptions(orgs.data); + setRoleOptions(roles.data); + }; + loadOptions(); +}, [instanceId]); + +// Lookup-Funktion +const getLabel = (options: {value: string, label: string}[], value: string) => + options.find(o => o.value === value)?.label || value; + +// In der Tabelle + + + +``` + +--- + +#### P3: Dynamische Dropdown-Filterung + +**Problem:** Im AccessView/ContractView soll das Contract-Dropdown nur Contracts der ausgewählten Organisation zeigen. + +**Lösung:** `dependsOn`-Attribut im Datamodel nutzen + dynamisches Nachladen. + +**Implementierung im EditForm:** + +```tsx +// Wenn organisationId geändert wird, Contracts neu laden +const handleFieldChange = async (fieldKey: string, value: any) => { + setFormData(prev => ({ ...prev, [fieldKey]: value })); + + // Dynamische Abhängigkeiten prüfen + const dependentFields = fields.filter(f => f.dependsOn === fieldKey); + for (const depField of dependentFields) { + if (depField.optionsReference?.includes('contracts')) { + // Contracts für diese Organisation laden + const contracts = await api.get( + `/api/trustee/${instanceId}/contracts/options?organisationId=${value}` + ); + // Options aktualisieren... + } + } +}; +``` + +**Backend-Erweiterung nötig:** Der `/contracts/options` Endpoint muss einen `organisationId` Query-Parameter unterstützen. + +--- + +#### P4: Generischer Options-Hook + +**Problem:** Options-Laden ist in jeder View dupliziert. + +**Lösung:** Zentraler Hook für Trustee-Options. + +```tsx +// hooks/useTrusteeOptions.ts +export function useTrusteeOptions() { + const instanceId = useInstanceId(); + const [options, setOptions] = useState({}); + + const loadOptions = useCallback(async (entity: 'organisations' | 'roles' | 'contracts' | 'users') => { + if (entity === 'users') { + const res = await api.get('/api/users/options'); + setOptions(prev => ({ ...prev, users: res.data })); + } else { + const res = await api.get(`/api/trustee/${instanceId}/${entity}/options`); + setOptions(prev => ({ ...prev, [entity]: res.data })); + } + }, [instanceId]); + + const getLabel = useCallback((entity: string, value: string) => { + return options[entity]?.find(o => o.value === value)?.label || value; + }, [options]); + + return { options, loadOptions, getLabel }; +} +``` + +--- + +### 9.3 Implementierungsreihenfolge + +| Phase | Aufgabe | Aufwand | Priorität | +|-------|---------|---------|-----------| +| **1** | `useTrusteeOptions` Hook erstellen | 2h | Hoch | +| **2** | Label-Auflösung in allen Views | 3h | Hoch | +| **3** | Create/Edit Modal in `TrusteeOrganisationsView` | 3h | Hoch | +| **4** | Create/Edit Modal für alle anderen Views kopieren | 4h | Mittel | +| **5** | Dynamische Contract-Filterung | 2h | Mittel | +| **6** | Backend: `/contracts/options?organisationId=` | 1h | Mittel | +| **7** | Position-Document Verknüpfungs-UI | 4h | Niedrig | +| **8** | Testing & Bugfixes | 4h | Hoch | + +**Geschätzter Gesamtaufwand:** ~23h (3-4 Arbeitstage) + +--- + +### 9.4 Detailplan Phase 1-3 (MVP) + +#### Schritt 1: `useTrusteeOptions` Hook + +**Datei:** `frontend_nyla/src/hooks/useTrusteeOptions.ts` + +```typescript +/** + * Hook für Trustee-Options (Dropdowns, Label-Auflösung) + */ +import { useState, useCallback, useEffect } from 'react'; +import api from '../api'; +import { useInstanceId } from './useCurrentInstance'; + +interface Option { + value: string; + label: string; +} + +interface TrusteeOptions { + users: Option[]; + organisations: Option[]; + roles: Option[]; + contracts: Option[]; + documents: Option[]; + positions: Option[]; +} + +export function useTrusteeOptions(autoLoad: (keyof TrusteeOptions)[] = []) { + const instanceId = useInstanceId(); + const [options, setOptions] = useState>({}); + const [loading, setLoading] = useState(false); + + const loadOptions = useCallback(async ( + entities: (keyof TrusteeOptions)[], + filters?: { organisationId?: string } + ) => { + if (!instanceId) return; + setLoading(true); + + try { + const promises = entities.map(async (entity) => { + let url: string; + if (entity === 'users') { + url = '/api/users/options'; + } else { + url = `/api/trustee/${instanceId}/${entity}/options`; + if (filters?.organisationId && entity === 'contracts') { + url += `?organisationId=${filters.organisationId}`; + } + } + const res = await api.get(url); + return { entity, data: res.data }; + }); + + const results = await Promise.all(promises); + const newOptions: Partial = {}; + results.forEach(({ entity, data }) => { + newOptions[entity] = data; + }); + setOptions(prev => ({ ...prev, ...newOptions })); + } finally { + setLoading(false); + } + }, [instanceId]); + + const getLabel = useCallback((entity: keyof TrusteeOptions, value: string): string => { + return options[entity]?.find(o => o.value === value)?.label || value; + }, [options]); + + // Auto-Load bei Mount + useEffect(() => { + if (autoLoad.length > 0 && instanceId) { + loadOptions(autoLoad); + } + }, [instanceId, autoLoad.join(',')]); + + return { options, loadOptions, getLabel, loading }; +} +``` + +#### Schritt 2: TrusteeAccessView mit Labels + +**Datei:** `frontend_nyla/src/pages/views/trustee/TrusteeAccessView.tsx` + +- Import `useTrusteeOptions` +- Auto-Load: `['users', 'organisations', 'roles']` +- Tabelle: `getLabel()` für userId, organisationId, roleId + +#### Schritt 3: Create/Edit Modal + +**Ansatz:** Bestehende `EditPopup`-Komponente aus anderen Views (z.B. Prompts) wiederverwenden. + +**Dateien:** +- `TrusteeOrganisationsView.tsx` - Modal hinzufügen +- Nutzt `generateCreateFieldsFromAttributes()` für Feld-Definitionen +- `handleCreate()` / `handleUpdate()` für API-Calls + +--- + +### 9.5 Backend-Erweiterung (optional) + +Für dynamische Contract-Filterung nach Organisation: + +**Datei:** `gateway/modules/features/trustee/routeFeatureTrustee.py` + +```python +@router.get("/{instanceId}/contracts/options", response_model=List[Dict[str, Any]]) +async def getContractOptions( + request: Request, + instanceId: str = Path(...), + organisationId: Optional[str] = Query(None), # NEU + context: RequestContext = Depends(getRequestContext) +) -> List[Dict[str, Any]]: + """Get contract options, optionally filtered by organisation.""" + mandateId = await _validateInstanceAccess(instanceId, context) + interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) + + if organisationId: + contracts = interface.getContractsByOrganisation(organisationId) + else: + result = interface.getAllContracts(None) + contracts = result.items if hasattr(result, 'items') else result + + return [{"value": c.id, "label": c.label or c.name or c.id} for c in contracts] +``` + +--- + +### 9.6 Offene Entscheidungen für Review + +| # | Frage | Optionen | +|---|-------|----------| +| 1 | **Modal vs. Inline-Edit** | A) Modal-Dialog / B) Inline in Tabelle / C) Separate Seite | +| 2 | **Bulk-Operationen** | A) Ja, Multi-Select + Batch-Aktionen / B) Nein, nur einzeln | +| 3 | **Position-Document UI** | A) Separate View / B) Inline in Position-View / C) Beides | +| 4 | **Validierung** | A) Client-only / B) Server-only / C) Beides | + +--- + +**Dokumentversion**: 1.1 +**Letzte Aktualisierung**: 2026-01-23 +**Autor**: Claude (AI-Assistent) +**Status**: Zur Überprüfung
{getLabel(userOptions, access.userId)}{getLabel(orgOptions, access.organisationId)}{getLabel(roleOptions, access.roleId)}