upd
This commit is contained in:
parent
b1e63d758e
commit
8fa4c870c7
7 changed files with 2581 additions and 847 deletions
|
|
@ -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 `<table>` |
|
||||
| `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 (
|
||||
<div className={styles.playgroundContainer}>
|
||||
<div className={styles.chatSection}>
|
||||
<div className={styles.chatColumns}>
|
||||
{/* Links - Chat Messages (dynamische Breite) */}
|
||||
<div
|
||||
className={styles.chatLeft}
|
||||
style={{ width: `${leftWidth}%` }}
|
||||
>
|
||||
<UnifiedContentArea />
|
||||
</div>
|
||||
|
||||
{/* Resizable Divider */}
|
||||
<div
|
||||
className={`${styles.resizeDivider} ${isDragging ? styles.dragging : ''}`}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
<div className={styles.dividerHandle} />
|
||||
</div>
|
||||
|
||||
{/* Rechts - Dashboard (dynamische Breite) */}
|
||||
<div
|
||||
className={styles.chatRight}
|
||||
style={{ width: `${100 - leftWidth}%` }}
|
||||
>
|
||||
<WorkflowDashboard />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.workflowFooter}>
|
||||
<UserInputArea />
|
||||
<DataStatistics />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 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<number>(() => {
|
||||
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<HTMLElement | null>(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<number, {
|
||||
operations: Map<string, Operation>;
|
||||
rootOperations: string[];
|
||||
expanded: boolean;
|
||||
isCompleted: boolean;
|
||||
}>;
|
||||
operations: Map<string, {
|
||||
logs: Map<string, LogEntry>;
|
||||
parentId: string | null;
|
||||
expanded: boolean;
|
||||
latestProgress: number | null;
|
||||
latestStatus: string | null;
|
||||
roundNumber: number;
|
||||
}>;
|
||||
logExpandedStates: Map<string, boolean>;
|
||||
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<WorkflowResponse>('/api/workflows/');
|
||||
|
||||
// Attributes vom Backend
|
||||
const { data: attributesResponse } = useApi<AttributesResponse>('/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<Workflow>) => { /* ... */ },
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
#### 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 (
|
||||
<div className={styles.page}>
|
||||
<PageHeader title="Workflows" onRefresh={refetch} />
|
||||
<FormGeneratorTable
|
||||
data={workflows}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
pagination={true}
|
||||
actionButtons={[
|
||||
{ type: 'edit', onAction: handleContinue },
|
||||
{ type: 'delete' },
|
||||
]}
|
||||
customActions={[
|
||||
{
|
||||
id: 'continue',
|
||||
icon: <FaPlay />,
|
||||
onClick: handleContinue,
|
||||
title: 'Workflow fortsetzen',
|
||||
}
|
||||
]}
|
||||
onDelete={handleDelete}
|
||||
hookData={{
|
||||
refetch,
|
||||
permissions,
|
||||
pagination,
|
||||
handleDelete,
|
||||
handleInlineUpdate,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 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<Entity | null>(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<Entity>) => {
|
||||
const result = await handleCreate(data);
|
||||
if (result.success) {
|
||||
setShowCreateModal(false);
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSubmit = async (data: Partial<Entity>) => {
|
||||
if (!editingItem) return;
|
||||
const result = await handleUpdate(editingItem.id, data);
|
||||
if (result.success) {
|
||||
setEditingItem(null);
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
// Render
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<PageHeader
|
||||
title="Entities"
|
||||
onRefresh={refetch}
|
||||
onCreate={canCreate ? () => setShowCreateModal(true) : undefined}
|
||||
/>
|
||||
|
||||
<FormGeneratorTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
pagination={true}
|
||||
actionButtons={[
|
||||
...(canUpdate ? [{ type: 'edit', onAction: handleEditClick }] : []),
|
||||
...(canDelete ? [{ type: 'delete' }] : []),
|
||||
]}
|
||||
onDelete={handleDelete}
|
||||
hookData={{
|
||||
refetch,
|
||||
permissions,
|
||||
pagination,
|
||||
handleDelete,
|
||||
handleInlineUpdate,
|
||||
updateOptimistically,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Create Modal */}
|
||||
{showCreateModal && (
|
||||
<Modal onClose={() => setShowCreateModal(false)}>
|
||||
<FormGeneratorForm
|
||||
attributes={formAttributes}
|
||||
mode="create"
|
||||
onSubmit={handleCreateSubmit}
|
||||
onCancel={() => setShowCreateModal(false)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* Edit Modal */}
|
||||
{editingItem && (
|
||||
<Modal onClose={() => setEditingItem(null)}>
|
||||
<FormGeneratorForm
|
||||
attributes={formAttributes}
|
||||
data={editingItem}
|
||||
mode="edit"
|
||||
onSubmit={handleEditSubmit}
|
||||
onCancel={() => setEditingItem(null)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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*
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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*
|
||||
|
|
@ -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)
|
||||
579
implementation/rbac_access_concept_done.md
Normal file
579
implementation/rbac_access_concept_done.md
Normal file
|
|
@ -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.<table>`
|
||||
- `data.table.<table>.<field>`
|
||||
- `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.<table>`
|
||||
- `data.table.<table>.<field>`
|
||||
- UI:
|
||||
- `ui.feature.<featureCode>.<area>.<element>`
|
||||
- Beispiel: `ui.feature.trustee.contracts.tab.documents`
|
||||
- RESOURCE:
|
||||
- `resource.feature.<featureCode>.<endpoint>`
|
||||
- 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.<featureCode>.routeFeature<FeatureCode> import router as <featureRouter>`
|
||||
- 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/<featureCode>/`
|
||||
- Pflicht-Module:
|
||||
- `datamodelFeatureXxx.py`
|
||||
- `interfaceFeatureXxx.py`
|
||||
- `routeFeatureXxx.py`
|
||||
- `main<Feature>.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<Feature>.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.
|
||||
463
implementation/rbac_access_umsetzungskonzept done.md
Normal file
463
implementation/rbac_access_umsetzungskonzept done.md
Normal file
|
|
@ -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/<feature>/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.<anderesFeature>.*`).
|
||||
- Zentrale Module importieren Feature-Container (`modules.features.*`).
|
||||
|
||||
**Soll-Checkliste je Feature-Container:**
|
||||
- Keine `modules.features.<anderesFeature>.*` Imports.
|
||||
- Keine Feature-zu-Feature-Abhaengigkeiten.
|
||||
|
||||
**Pruefroutine (rg-Patterns, Zielzustand):**
|
||||
- Finde direkte Cross-Feature-Imports:
|
||||
- `rg -n "modules\\.features\\.(?!<feature>\\.)" gateway/modules/features/<feature>/`
|
||||
- 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
|
||||
687
implementation/trustee_feature_rbac_architecture.md
Normal file
687
implementation/trustee_feature_rbac_architecture.md
Normal file
|
|
@ -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<TrusteeOrganisation | null>(null);
|
||||
const { generateCreateFieldsFromAttributes, generateEditFieldsFromAttributes } = useTrusteeOrganisations();
|
||||
const { handleCreate, handleUpdate } = useTrusteeOrganisationOperations();
|
||||
|
||||
// Create-Button
|
||||
<button onClick={() => { setEditingItem(null); setIsModalOpen(true); }}>
|
||||
+ Neue Organisation
|
||||
</button>
|
||||
|
||||
// Edit-Button
|
||||
<button onClick={() => { setEditingItem(org); setIsModalOpen(true); }}>
|
||||
✏️
|
||||
</button>
|
||||
|
||||
// Modal mit EditForm
|
||||
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
|
||||
<EditForm
|
||||
fields={editingItem ? generateEditFieldsFromAttributes() : generateCreateFieldsFromAttributes()}
|
||||
initialData={editingItem || {}}
|
||||
onSave={async (data) => {
|
||||
if (editingItem) {
|
||||
await handleUpdate(editingItem.id, data);
|
||||
} else {
|
||||
await handleCreate(data);
|
||||
}
|
||||
setIsModalOpen(false);
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 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
|
||||
<td>{getLabel(userOptions, access.userId)}</td>
|
||||
<td>{getLabel(orgOptions, access.organisationId)}</td>
|
||||
<td>{getLabel(roleOptions, access.roleId)}</td>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 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<TrusteeOptionsMap>({});
|
||||
|
||||
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<Partial<TrusteeOptions>>({});
|
||||
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<TrusteeOptions> = {};
|
||||
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
|
||||
Loading…
Reference in a new issue