full CRUD for all objects

This commit is contained in:
valueon 2025-03-19 00:59:45 +01:00
parent 55b4bbaa72
commit 3094007d6b
10 changed files with 627 additions and 90 deletions

View file

@ -5,5 +5,12 @@
"language": "de", "language": "de",
"mandate_id": 1, "mandate_id": 1,
"user_id": 1 "user_id": 1
},
{
"id": 2,
"name": "Mandant von test",
"language": "de",
"mandate_id": 1,
"user_id": 1
} }
] ]

View file

@ -7,7 +7,20 @@
"full_name": "Administrator", "full_name": "Administrator",
"disabled": false, "disabled": false,
"language": "de", "language": "de",
"privilege": "sysadmin",
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$tJYSAmDs/T+HEALg3Nsbww$2HnCW69CkCkk82fgRhzjYtdNu3b9G7NmSJZmbUQVDOs", "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$tJYSAmDs/T+HEALg3Nsbww$2HnCW69CkCkk82fgRhzjYtdNu3b9G7NmSJZmbUQVDOs",
"user_id": 1 "user_id": 1
},
{
"id": 2,
"mandate_id": 2,
"username": "test",
"email": "web@pamocreate.com",
"full_name": "Test User",
"disabled": false,
"language": "de",
"privilege": "user",
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$3fufM+bc+19r7V3rvVfKmQ$CrJCc6r44jO4DxMHs8E61ayupBeUi6a+65VH0Q1cGo8",
"user_id": 2
} }
] ]

View file

@ -3,9 +3,9 @@
"id": 2, "id": 2,
"mandate_id": 1, "mandate_id": 1,
"user_id": 1, "user_id": 1,
"name": "Hugo", "name": "Hugo C",
"type": "Transformation", "type": "Transformation",
"workspace_id": 1, "workspace_id": "1",
"capabilities": "ccc", "capabilities": "ccc",
"description": "ddd" "description": "ddd"
} }

View file

@ -9,6 +9,7 @@ from typing import Dict, Any
import os import os
import logging import logging
from datetime import timedelta from datetime import timedelta
import pathlib
# Import interfaces # Import interfaces
from modules.gateway_interface import get_gateway_interface from modules.gateway_interface import get_gateway_interface
@ -26,15 +27,6 @@ from auth import (
# Import models - generisch importieren # Import models - generisch importieren
import modules.gateway_model as gateway_model import modules.gateway_model as gateway_model
# Import router modules
from routes.attributes import router as attributes_router
from routes.workspace import router as workspace_router
from routes.agent import router as agent_router
from routes.file import router as file_router
from routes.prompt import router as prompt_router
from routes.workflow import router as workflow_router
from routes.mandate import router as mandate_router
# Konfiguration des Loggers # Konfiguration des Loggers
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
@ -56,14 +48,17 @@ app.add_middleware(
max_age=86400 # Erhöhung des Caching für Preflight-Anfragen max_age=86400 # Erhöhung des Caching für Preflight-Anfragen
) )
# Statischer Folder für Frontend # Statischer Folder für Frontend - mit absolutem Pfad arbeiten
os.makedirs(os.path.join(os.getcwd(), "static"), exist_ok=True) base_dir = pathlib.Path(__file__).parent
app.mount("/static", StaticFiles(directory="static"), name="static") static_folder = base_dir / "static"
os.makedirs(static_folder, exist_ok=True)
app.mount("/static", StaticFiles(directory=str(static_folder)), name="static")
# Add a specific route for favicon.ico # Add a specific route for favicon.ico
@app.get("/favicon.ico", include_in_schema=False) @app.get("/favicon.ico", include_in_schema=False)
async def favicon(): async def favicon():
return FileResponse("static/favicon.ico") favicon_path = static_folder / "favicon.ico"
return FileResponse(str(favicon_path))
# Generelle Elements # Generelle Elements
@app.get("/", tags=["General"]) @app.get("/", tags=["General"])
@ -75,7 +70,7 @@ async def root():
async def get_test(): async def get_test():
return "OK 1.1" return "OK 1.1"
@app.options("/{full_path:path}") @app.options("/{full_path:path}", tags=["General"])
async def options_route(full_path: str): async def options_route(full_path: str):
return Response(status_code=200) return Response(status_code=200)
@ -107,48 +102,36 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
return {"access_token": access_token, "token_type": "bearer"} return {"access_token": access_token, "token_type": "bearer"}
# Benutzerregistrierung
@app.post("/api/users/register", response_model=gateway_model.User, tags=["General"])
async def register_user(user_data: dict = Body(...), current_user: Dict[str, Any] = Depends(get_current_active_user)):
"""Neuen Benutzer registrieren"""
# Nur Benutzer des gleichen Mandanten dürfen erstellt werden
mandate_id, user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, user_id)
if "username" not in user_data or "password" not in user_data:
raise HTTPException(status_code=400, detail="Benutzername und Passwort erforderlich")
try:
new_user = gateway.create_user(
username=user_data["username"],
password=user_data["password"],
email=user_data.get("email"),
full_name=user_data.get("full_name"),
language=user_data.get("language", "de")
)
return new_user
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# Benutzerinfo abrufen # Benutzerinfo abrufen
@app.get("/api/users/me", response_model=Dict[str, Any], tags=["General"]) @app.get("/api/users/me", response_model=Dict[str, Any], tags=["General"])
async def read_users_me(current_user: Dict[str, Any] = Depends(get_current_active_user)): async def read_users_me(current_user: Dict[str, Any] = Depends(get_current_active_user)):
return current_user return current_user
# Alle Router einbinden # Alle Router einbinden
app.include_router(attributes_router) from routes.mandate import router as mandate_router
app.include_router(workspace_router)
app.include_router(agent_router)
app.include_router(file_router)
app.include_router(prompt_router)
app.include_router(workflow_router)
app.include_router(mandate_router) app.include_router(mandate_router)
from routes.users import router as users_router
app.include_router(users_router)
from routes.attributes import router as attributes_router
app.include_router(attributes_router)
from routes.workspace import router as workspace_router
app.include_router(workspace_router)
from routes.agent import router as agent_router
app.include_router(agent_router)
from routes.file import router as file_router
app.include_router(file_router)
from routes.prompt import router as prompt_router
app.include_router(prompt_router)
from routes.workflow import router as workflow_router
app.include_router(workflow_router)
# Event handler beim Herunterfahren # Event handler beim Herunterfahren
@app.on_event("shutdown") @app.on_event("shutdown")

View file

@ -91,6 +91,7 @@ class GatewayInterface:
"full_name": "Administrator", "full_name": "Administrator",
"disabled": False, "disabled": False,
"language": "de", "language": "de",
"privilege": "sysadmin", # SysAdmin-Berechtigung
"hashed_password": self._get_password_hash("admin") # In der Produktion ein sicheres Passwort verwenden! "hashed_password": self._get_password_hash("admin") # In der Produktion ein sicheres Passwort verwenden!
} }
@ -139,11 +140,94 @@ class GatewayInterface:
return self.db.record_create("mandates", mandate_data) return self.db.record_create("mandates", mandate_data)
def update_mandate(self, mandate_id: int, mandate_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Aktualisiert einen bestehenden Mandanten
Args:
mandate_id: Die ID des zu aktualisierenden Mandanten
mandate_data: Die zu aktualisierenden Mandantendaten
Returns:
Dict[str, Any]: Die aktualisierten Mandantendaten
Raises:
ValueError: Wenn der Mandant nicht gefunden wurde
"""
# Prüfe, ob der Mandant existiert
mandate = self.get_mandate(mandate_id)
if not mandate:
raise ValueError(f"Mandant mit ID {mandate_id} nicht gefunden")
# Aktualisiere den Mandanten
updated_mandate = self.db.record_modify("mandates", mandate_id, mandate_data)
return updated_mandate
def delete_mandate(self, mandate_id: int) -> bool:
"""
Löscht einen Mandanten und alle damit verbundenen Benutzer und Daten
Args:
mandate_id: Die ID des zu löschenden Mandanten
Returns:
bool: True, wenn der Mandant erfolgreich gelöscht wurde, sonst False
"""
# Prüfe, ob der Mandant existiert
mandate = self.get_mandate(mandate_id)
if not mandate:
return False
# Root-Mandant darf nicht gelöscht werden
if mandate_id == 1:
logger.warning("Versuch, den Root-Mandanten zu löschen, wurde verhindert")
return False
# Finde alle Benutzer des Mandanten
users = self.get_users_by_mandate(mandate_id)
# Lösche alle Benutzer des Mandanten und ihre zugehörigen Daten
for user in users:
self.delete_user(user["id"])
# Lösche den Mandanten
success = self.db.record_delete("mandates", mandate_id)
if success:
logger.info(f"Mandant mit ID {mandate_id} wurde erfolgreich gelöscht")
else:
logger.error(f"Fehler beim Löschen des Mandanten mit ID {mandate_id}")
return success
# Benutzer-Methoden # Benutzer-Methoden
def get_all_users(self) -> List[Dict[str, Any]]: def get_all_users(self) -> List[Dict[str, Any]]:
"""Gibt alle Benutzer des aktuellen Mandanten zurück""" """Gibt alle Benutzer zurück"""
return self.db.get_recordset("users") users = self.db.get_recordset("users")
# Entferne die Passwort-Hashes aus der Rückgabe
for user in users:
if "hashed_password" in user:
del user["hashed_password"]
return users
def get_users_by_mandate(self, mandate_id: int) -> List[Dict[str, Any]]:
"""
Gibt alle Benutzer eines bestimmten Mandanten zurück
Args:
mandate_id: Die ID des Mandanten
Returns:
List[Dict[str, Any]]: Liste der Benutzer des Mandanten
"""
users = self.db.get_recordset("users", record_filter={"mandate_id": mandate_id})
# Entferne die Passwort-Hashes aus der Rückgabe
for user in users:
if "hashed_password" in user:
del user["hashed_password"]
return users
def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]: def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]:
"""Gibt einen Benutzer anhand seines Benutzernamens zurück""" """Gibt einen Benutzer anhand seines Benutzernamens zurück"""
@ -157,12 +241,37 @@ class GatewayInterface:
"""Gibt einen Benutzer anhand seiner ID zurück""" """Gibt einen Benutzer anhand seiner ID zurück"""
users = self.db.get_recordset("users", record_filter={"id": user_id}) users = self.db.get_recordset("users", record_filter={"id": user_id})
if users: if users:
return users[0] user = users[0]
# Entferne das Passwort-Hash aus der Rückgabe für die API
if "hashed_password" in user:
user_copy = user.copy()
del user_copy["hashed_password"]
return user_copy
return user
return None return None
def create_user(self, username: str, password: str, email: str = None, def create_user(self, username: str, password: str, email: str = None,
full_name: str = None, language: str = "de") -> Dict[str, Any]: full_name: str = None, language: str = "de", mandate_id: int = None,
"""Erstellt einen neuen Benutzer""" disabled: bool = False, privilege: str = "user") -> Dict[str, Any]:
"""
Erstellt einen neuen Benutzer
Args:
username: Der Benutzername
password: Das Passwort
email: Die E-Mail-Adresse (optional)
full_name: Der vollständige Name (optional)
language: Die bevorzugte Sprache (Standard: "de")
mandate_id: Die ID des Mandanten (optional)
disabled: Ob der Benutzer deaktiviert ist (Standard: False)
privilege: Die Berechtigungsstufe (Standard: "user")
Returns:
Dict[str, Any]: Die erstellten Benutzerdaten
Raises:
ValueError: Wenn der Benutzername bereits existiert
"""
# Prüfe, ob der Benutzername bereits existiert # Prüfe, ob der Benutzername bereits existiert
existing_user = self.get_user_by_username(username) existing_user = self.get_user_by_username(username)
if existing_user: if existing_user:
@ -174,14 +283,18 @@ class GatewayInterface:
if users: if users:
next_id = max(user["id"] for user in users) + 1 next_id = max(user["id"] for user in users) + 1
# Verwende den übergebenen mandate_id oder den aktuellen Kontext
user_mandate_id = mandate_id if mandate_id is not None else self.mandate_id
user_data = { user_data = {
"id": next_id, "id": next_id,
"mandate_id": self.mandate_id, "mandate_id": user_mandate_id,
"username": username, "username": username,
"email": email, "email": email,
"full_name": full_name, "full_name": full_name,
"disabled": False, "disabled": disabled,
"language": language, "language": language,
"privilege": privilege, # Neue Eigenschaft
"hashed_password": self._get_password_hash(password) "hashed_password": self._get_password_hash(password)
} }
@ -194,7 +307,16 @@ class GatewayInterface:
return created_user return created_user
def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]: def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]:
"""Authentifiziert einen Benutzer anhand von Benutzername und Passwort""" """
Authentifiziert einen Benutzer anhand von Benutzername und Passwort
Args:
username: Der Benutzername
password: Das Passwort
Returns:
Optional[Dict[str, Any]]: Die Benutzerdaten oder None, wenn die Authentifizierung fehlschlägt
"""
user = self.get_user_by_username(username) user = self.get_user_by_username(username)
if not user: if not user:
@ -203,6 +325,10 @@ class GatewayInterface:
if not self._verify_password(password, user.get("hashed_password", "")): if not self._verify_password(password, user.get("hashed_password", "")):
return None return None
# Prüfe, ob der Benutzer deaktiviert ist
if user.get("disabled", False):
return None
# Erstelle eine Kopie ohne Passwort-Hash # Erstelle eine Kopie ohne Passwort-Hash
authenticated_user = {**user} authenticated_user = {**user}
if "hashed_password" in authenticated_user: if "hashed_password" in authenticated_user:
@ -211,12 +337,26 @@ class GatewayInterface:
return authenticated_user return authenticated_user
def update_user(self, user_id: int, user_data: Dict[str, Any]) -> Dict[str, Any]: def update_user(self, user_id: int, user_data: Dict[str, Any]) -> Dict[str, Any]:
"""Aktualisiert einen Benutzer""" """
# Hole den aktuellen Benutzer Aktualisiert einen Benutzer
user = self.get_user(user_id)
if not user: Args:
user_id: Die ID des zu aktualisierenden Benutzers
user_data: Die zu aktualisierenden Benutzerdaten
Returns:
Dict[str, Any]: Die aktualisierten Benutzerdaten
Raises:
ValueError: Wenn der Benutzer nicht gefunden wurde
"""
# Hole den aktuellen Benutzer mit Hash-Passwort (direkt aus der DB)
users = self.db.get_recordset("users", record_filter={"id": user_id})
if not users:
raise ValueError(f"Benutzer mit ID {user_id} nicht gefunden") raise ValueError(f"Benutzer mit ID {user_id} nicht gefunden")
user = users[0]
# Wenn das Passwort geändert werden soll, hashe es # Wenn das Passwort geändert werden soll, hashe es
if "password" in user_data: if "password" in user_data:
user_data["hashed_password"] = self._get_password_hash(user_data["password"]) user_data["hashed_password"] = self._get_password_hash(user_data["password"])
@ -238,6 +378,92 @@ class GatewayInterface:
def enable_user(self, user_id: int) -> Dict[str, Any]: def enable_user(self, user_id: int) -> Dict[str, Any]:
"""Aktiviert einen Benutzer""" """Aktiviert einen Benutzer"""
return self.update_user(user_id, {"disabled": False}) return self.update_user(user_id, {"disabled": False})
def _delete_user_referenced_data(self, user_id: int) -> None:
"""
Löscht alle Daten, die mit einem Benutzer verbunden sind
Args:
user_id: Die ID des Benutzers
"""
# Hier werden alle Tabellen durchsucht und alle Einträge gelöscht,
# die auf diesen Benutzer verweisen
# Workspaces des Benutzers finden und löschen
try:
workspaces = self.db.get_recordset("workspaces", record_filter={"user_id": user_id})
for workspace in workspaces:
# Agenten in diesem Workspace löschen
agents = self.db.get_recordset("agents", record_filter={"workspace_id": workspace["id"]})
for agent in agents:
# Hier könnten weitere abhängige Objekte gelöscht werden
self.db.record_delete("agents", agent["id"])
# Dateien in diesem Workspace löschen
files = self.db.get_recordset("files", record_filter={"workspace_id": workspace["id"]})
for file in files:
self.db.record_delete("files", file["id"])
# Prompts in diesem Workspace löschen
prompts = self.db.get_recordset("prompts", record_filter={"workspace_id": workspace["id"]})
for prompt in prompts:
self.db.record_delete("prompts", prompt["id"])
# Workflows in diesem Workspace löschen
workflows = self.db.get_recordset("workflows", record_filter={"workspace_id": workspace["id"]})
for workflow in workflows:
self.db.record_delete("workflows", workflow["id"])
# Den Workspace selbst löschen
self.db.record_delete("workspaces", workspace["id"])
except Exception as e:
logger.error(f"Fehler beim Löschen der Workspaces für Benutzer {user_id}: {e}")
# Attribute des Benutzers löschen
try:
attributes = self.db.get_recordset("attributes", record_filter={"user_id": user_id})
for attribute in attributes:
self.db.record_delete("attributes", attribute["id"])
except Exception as e:
logger.error(f"Fehler beim Löschen der Attribute für Benutzer {user_id}: {e}")
# Weitere Tabellen, die auf den Benutzer verweisen könnten
# (Je nach Datenbankstruktur der Anwendung)
logger.info(f"Alle referenzierten Daten für Benutzer {user_id} wurden gelöscht")
def delete_user(self, user_id: int) -> bool:
"""
Löscht einen Benutzer und alle damit verbundenen Daten
Args:
user_id: Die ID des zu löschenden Benutzers
Returns:
bool: True, wenn der Benutzer erfolgreich gelöscht wurde, sonst False
"""
# Prüfe, ob der Benutzer existiert
users = self.db.get_recordset("users", record_filter={"id": user_id})
if not users:
return False
# Root-Admin (ID 1) darf nicht gelöscht werden
if user_id == 1:
logger.warning("Versuch, den Root-Admin zu löschen, wurde verhindert")
return False
# Lösche alle mit dem Benutzer verbundenen Daten
self._delete_user_referenced_data(user_id)
# Lösche den Benutzer
success = self.db.record_delete("users", user_id)
if success:
logger.info(f"Benutzer mit ID {user_id} wurde erfolgreich gelöscht")
else:
logger.error(f"Fehler beim Löschen des Benutzers mit ID {user_id}")
return success
# Singleton-Factory für GatewayInterface-Instanzen pro Kontext # Singleton-Factory für GatewayInterface-Instanzen pro Kontext
@ -253,6 +479,5 @@ def get_gateway_interface(mandate_id: int = None, user_id: int = None) -> Gatewa
_gateway_interfaces[context_key] = GatewayInterface(mandate_id, user_id) _gateway_interfaces[context_key] = GatewayInterface(mandate_id, user_id)
return _gateway_interfaces[context_key] return _gateway_interfaces[context_key]
#Init # Init
get_gateway_interface() get_gateway_interface()

View file

@ -40,8 +40,9 @@ class User(BaseModel):
username: str = Field(description="Benutzername für die Anmeldung") username: str = Field(description="Benutzername für die Anmeldung")
email: Optional[str] = Field(None, description="E-Mail-Adresse des Benutzers") email: Optional[str] = Field(None, description="E-Mail-Adresse des Benutzers")
full_name: Optional[str] = Field(None, description="Vollständiger Name des Benutzers") full_name: Optional[str] = Field(None, description="Vollständiger Name des Benutzers")
disabled: Optional[bool] = Field(False, description="Gibt an, ob der Benutzer deaktiviert ist")
language: str = Field(description="Bevorzugte Sprache des Benutzers") language: str = Field(description="Bevorzugte Sprache des Benutzers")
disabled: Optional[bool] = Field(False, description="Gibt an, ob der Benutzer deaktiviert ist")
privilege: str = Field(description="Berechtigungsstufe") #sysadmin,admin,user
label: Label = Field( label: Label = Field(
default=Label(default="Benutzer", translations={"en": "User", "fr": "Utilisateur"}), default=Label(default="Benutzer", translations={"en": "User", "fr": "Utilisateur"}),
@ -55,8 +56,9 @@ class User(BaseModel):
"username": Label(default="Benutzername", translations={"en": "Username", "fr": "Nom d'utilisateur"}), "username": Label(default="Benutzername", translations={"en": "Username", "fr": "Nom d'utilisateur"}),
"email": Label(default="E-Mail", translations={"en": "Email", "fr": "E-mail"}), "email": Label(default="E-Mail", translations={"en": "Email", "fr": "E-mail"}),
"full_name": Label(default="Vollständiger Name", translations={"en": "Full name", "fr": "Nom complet"}), "full_name": Label(default="Vollständiger Name", translations={"en": "Full name", "fr": "Nom complet"}),
"language": Label(default="Sprache", translations={"en": "Language", "fr": "Langue"}),
"disabled": Label(default="Deaktiviert", translations={"en": "Disabled", "fr": "Désactivé"}), "disabled": Label(default="Deaktiviert", translations={"en": "Disabled", "fr": "Désactivé"}),
"language": Label(default="Sprache", translations={"en": "Language", "fr": "Langue"}) "privilege": Label(default="Berechtigungsstufe", translations={"en": "Access level", "fr": "Niveau d'accès"}),
} }

View file

@ -83,7 +83,6 @@ class Prompt(BaseModel):
mandate_id: int = Field(description="ID des zugehörigen Mandanten") mandate_id: int = Field(description="ID des zugehörigen Mandanten")
user_id: int = Field(description="ID des Erstellers") user_id: int = Field(description="ID des Erstellers")
content: str = Field(description="Inhalt des Prompts") content: str = Field(description="Inhalt des Prompts")
created_at: str = Field(description="Erstellungszeitpunkt")
label: Label = Field( label: Label = Field(
default=Label(default="Prompt", translations={"en": "Prompt", "fr": "Invite"}), default=Label(default="Prompt", translations={"en": "Prompt", "fr": "Invite"}),
@ -96,7 +95,6 @@ class Prompt(BaseModel):
"mandate_id": Label(default="Mandanten-ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}), "mandate_id": Label(default="Mandanten-ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"user_id": Label(default="Benutzer-ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}), "user_id": Label(default="Benutzer-ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}),
"content": Label(default="Inhalt", translations={"en": "Content", "fr": "Contenu"}), "content": Label(default="Inhalt", translations={"en": "Content", "fr": "Contenu"}),
"created_at": Label(default="Erstellt am", translations={"en": "Created at", "fr": "Créé le"})
} }
@ -106,7 +104,6 @@ class Workspace(BaseModel):
mandate_id: int = Field(description="ID des zugehörigen Mandanten") mandate_id: int = Field(description="ID des zugehörigen Mandanten")
user_id: int = Field(description="ID des Erstellers") user_id: int = Field(description="ID des Erstellers")
name: str = Field(description="Name des Workspaces") name: str = Field(description="Name des Workspaces")
created_at: str = Field(description="Erstellungszeitpunkt")
prompts: List[Prompt] = Field(default=[], description="Liste der Prompts im Workspace") prompts: List[Prompt] = Field(default=[], description="Liste der Prompts im Workspace")
agents: List[Agent] = Field(default=[], description="Liste der Agenten im Workspace") agents: List[Agent] = Field(default=[], description="Liste der Agenten im Workspace")
dataObjectReferences: List[int] = Field(default=[], description="Referenzen zu Datenobjekten") dataObjectReferences: List[int] = Field(default=[], description="Referenzen zu Datenobjekten")
@ -122,7 +119,6 @@ class Workspace(BaseModel):
"mandate_id": Label(default="Mandanten-ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}), "mandate_id": Label(default="Mandanten-ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"user_id": Label(default="Benutzer-ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}), "user_id": Label(default="Benutzer-ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}),
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}), "name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
"created_at": Label(default="Erstellt am", translations={"en": "Created at", "fr": "Créé le"}),
"prompts": Label(default="Prompts", translations={"en": "Prompts", "fr": "Invites"}), "prompts": Label(default="Prompts", translations={"en": "Prompts", "fr": "Invites"}),
"agents": Label(default="Agenten", translations={"en": "Agents", "fr": "Agents"}), "agents": Label(default="Agenten", translations={"en": "Agents", "fr": "Agents"}),
"dataObjectReferences": Label(default="Datenobjekte", translations={"en": "Data Objects", "fr": "Objets de données"}) "dataObjectReferences": Label(default="Datenobjekte", translations={"en": "Data Objects", "fr": "Objets de données"})

View file

@ -33,16 +33,6 @@ router = APIRouter(
responses={404: {"description": "Not found"}} responses={404: {"description": "Not found"}}
) )
@router.get("/test", response_model=str)
async def get_entity_test(s:str):
return s+s
@router.options("/{entity_type}")
async def options_entity_attributes(
entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)")
):
return Response(status_code=200)
@router.get("/{entity_type}", response_model=List[AttributeDefinition]) @router.get("/{entity_type}", response_model=List[AttributeDefinition])
async def get_entity_attributes( async def get_entity_attributes(
entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)"), entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)"),
@ -108,4 +98,12 @@ async def get_entity_attributes(
for agent in agents for agent in agents
] ]
return visible_attributes return visible_attributes
@router.options("/{entity_type}")
async def options_entity_attributes(
entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)")
):
return Response(status_code=200)

View file

@ -1,4 +1,4 @@
from fastapi import APIRouter, HTTPException, Depends, Body from fastapi import APIRouter, HTTPException, Depends, Body, Path
from typing import List, Dict, Any from typing import List, Dict, Any
from fastapi import status from fastapi import status
@ -17,13 +17,18 @@ router = APIRouter(
@router.get("", response_model=List[Dict[str, Any]]) @router.get("", response_model=List[Dict[str, Any]])
async def get_mandates(current_user: Dict[str, Any] = Depends(get_current_active_user)): async def get_mandates(current_user: Dict[str, Any] = Depends(get_current_active_user)):
"""Alle verfügbaren Mandanten abrufen (nur für Admin-Benutzer)""" """Alle verfügbaren Mandanten abrufen (nur für SysAdmin-Benutzer)"""
mandate_id, user_id = await get_user_context(current_user) mandate_id, user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren # Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, user_id) gateway = get_gateway_interface(mandate_id, user_id)
# TODO: Hier sollte eine Berechtigungsprüfung erfolgen # Berechtigungsprüfung
if current_user.get("privilege") != "sysadmin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Nur System-Administratoren können alle Mandanten abrufen"
)
return gateway.get_all_mandates() return gateway.get_all_mandates()
@ -33,13 +38,18 @@ async def create_mandate(
mandate: Dict[str, Any] = Body(...), mandate: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user) current_user: Dict[str, Any] = Depends(get_current_active_user)
): ):
"""Einen neuen Mandanten erstellen (nur für Admin-Benutzer)""" """Einen neuen Mandanten erstellen (nur für SysAdmin-Benutzer)"""
mandate_id, user_id = await get_user_context(current_user) mandate_id, user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren # Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, user_id) gateway = get_gateway_interface(mandate_id, user_id)
# TODO: Hier sollte eine Berechtigungsprüfung erfolgen # Berechtigungsprüfung
if current_user.get("privilege") != "sysadmin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Nur System-Administratoren können Mandanten erstellen"
)
new_mandate = gateway.create_mandate( new_mandate = gateway.create_mandate(
name=mandate.get("name", "Neuer Mandant"), name=mandate.get("name", "Neuer Mandant"),
@ -60,8 +70,17 @@ async def get_mandate(
# Gateway-Interface mit Benutzerkontext initialisieren # Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(user_mandate_id, user_id) gateway = get_gateway_interface(user_mandate_id, user_id)
# TODO: Hier sollte eine Berechtigungsprüfung erfolgen # Berechtigungsprüfung
# Nur Admins oder Benutzer des gleichen Mandanten dürfen den Mandanten sehen # Admin darf nur seinen eigenen Mandanten sehen, SysAdmin alle
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
is_own_mandate = user_mandate_id == mandate_id
if (is_admin and not is_own_mandate) and not is_sysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Abrufen dieses Mandanten"
)
mandate = gateway.get_mandate(mandate_id) mandate = gateway.get_mandate(mandate_id)
if not mandate: if not mandate:
@ -70,4 +89,84 @@ async def get_mandate(
detail=f"Mandant mit ID {mandate_id} nicht gefunden" detail=f"Mandant mit ID {mandate_id} nicht gefunden"
) )
return mandate return mandate
@router.post("/{mandate_id}", response_model=Dict[str, Any])
async def update_mandate(
mandate_id: int = Path(..., description="ID des zu aktualisierenden Mandanten"),
mandate_data: Dict[str, Any] = Body(..., description="Aktualisierte Mandantendaten"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen bestehenden Mandanten aktualisieren"""
user_mandate_id, user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(user_mandate_id, user_id)
# Mandant existiert?
mandate = gateway.get_mandate(mandate_id)
if not mandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandant mit ID {mandate_id} nicht gefunden"
)
# Berechtigungsprüfung
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
is_own_mandate = user_mandate_id == mandate_id
if (is_admin and not is_own_mandate) and not is_sysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Aktualisieren dieses Mandanten"
)
# Mandant aktualisieren
updated_mandate = gateway.update_mandate(
mandate_id=mandate_id,
mandate_data=mandate_data
)
return updated_mandate
@router.delete("/{mandate_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_mandate(
mandate_id: int = Path(..., description="ID des zu löschenden Mandanten"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen Mandanten löschen, inklusive aller zugehörigen Benutzer und referenzierten Objekte"""
user_mandate_id, user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(user_mandate_id, user_id)
# Mandant existiert?
mandate = gateway.get_mandate(mandate_id)
if not mandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandant mit ID {mandate_id} nicht gefunden"
)
# Berechtigungsprüfung
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
is_own_mandate = user_mandate_id == mandate_id
if (is_admin and not is_own_mandate) and not is_sysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Löschen dieses Mandanten"
)
# Mandant löschen
success = gateway.delete_mandate(mandate_id)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Löschen des Mandanten mit ID {mandate_id}"
)
# Kein Inhalt zurückgeben bei erfolgreichem Löschen
return None

214
gwserver/routes/users.py Normal file
View file

@ -0,0 +1,214 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Path
from typing import List, Dict, Any, Optional
from fastapi import status
# Import auth module
from auth import get_current_active_user, get_user_context
# Import interfaces
from modules.gateway_interface import get_gateway_interface
# Router für Benutzer-Endpunkte erstellen
router = APIRouter(
prefix="/api/users",
tags=["Users"],
responses={404: {"description": "Not found"}}
)
@router.get("", response_model=List[Dict[str, Any]])
async def get_users(current_user: Dict[str, Any] = Depends(get_current_active_user)):
"""Alle verfügbaren Benutzer abrufen (nur für Admin/SysAdmin-Benutzer)"""
mandate_id, user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, user_id)
# Berechtigungsprüfung
if current_user.get("privilege") not in ["admin", "sysadmin"]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Abrufen der Benutzerliste"
)
# Admin sieht nur Benutzer des eigenen Mandanten, SysAdmin sieht alle
if current_user.get("privilege") == "admin":
return gateway.get_users_by_mandate(mandate_id)
else: # sysadmin
return gateway.get_all_users()
@router.post("/register", response_model=Dict[str, Any])
async def register_user(user_data: dict = Body(...)):
"""Neuen Benutzer registrieren"""
# Bei der Registrierung keinen Benutzerkontext verwenden
gateway = get_gateway_interface()
if "username" not in user_data or "password" not in user_data:
raise HTTPException(status_code=400, detail="Benutzername und Passwort erforderlich")
try:
new_mandate = gateway.create_mandate(
name=f"Mandant von {user_data['username']}",
language=user_data.get("language", "de")
)
new_user = gateway.create_user(
username=user_data["username"],
password=user_data["password"],
email=user_data.get("email"),
full_name=user_data.get("full_name"),
language=user_data.get("language", "de"),
mandate_id=new_mandate["id"],
disabled=False,
privilege="user"
)
return new_user
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/{user_id}", response_model=Dict[str, Any])
async def get_user(
user_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen bestimmten Benutzer abrufen"""
mandate_id, current_user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, current_user_id)
# Berechtigungsprüfung
# Benutzer darf nur sich selbst abrufen, Admin nur Benutzer des eigenen Mandanten, SysAdmin alle
user_to_get = gateway.get_user(user_id)
if not user_to_get:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Benutzer mit ID {user_id} nicht gefunden"
)
if user_id == current_user_id:
# Benutzer darf sich selbst abrufen
pass
elif current_user.get("privilege") == "admin" and user_to_get.get("mandate_id") == mandate_id:
# Admin darf Benutzer des eigenen Mandanten abrufen
pass
elif current_user.get("privilege") == "sysadmin":
# SysAdmin darf alle Benutzer abrufen
pass
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Abrufen dieses Benutzers"
)
return user_to_get
@router.post("/{user_id}", response_model=Dict[str, Any])
async def update_user(
user_id: int = Path(..., description="ID des zu aktualisierenden Benutzers"),
user_data: Dict[str, Any] = Body(..., description="Aktualisierte Benutzerdaten"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen bestehenden Benutzer aktualisieren"""
mandate_id, current_user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, current_user_id)
# Benutzer existiert?
user_to_update = gateway.get_user(user_id)
if not user_to_update:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Benutzer mit ID {user_id} nicht gefunden"
)
# Berechtigungsprüfung
is_self_update = user_id == current_user_id
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
same_mandate = user_to_update.get("mandate_id") == mandate_id
# Filtere erlaubte Felder je nach Berechtigungsstufe
allowed_fields = {"username", "email", "full_name", "language"}
sensitive_fields = {"mandate_id", "disabled", "privilege"}
# Prüfe, ob sensitive Felder geändert werden sollen
sensitive_update = any(field in user_data for field in sensitive_fields)
if is_self_update and sensitive_update:
# Normale Benutzer dürfen ihre sensitiven Daten nicht ändern
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Ändern sensitiver Benutzerdaten"
)
elif is_admin and sensitive_update and not same_mandate:
# Admins dürfen sensitive Daten nur für Benutzer des eigenen Mandanten ändern
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Ändern sensitiver Daten für Benutzer anderer Mandanten"
)
elif not (is_self_update or (is_admin and same_mandate) or is_sysadmin):
# Keine Berechtigung für andere Fälle
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Aktualisieren dieses Benutzers"
)
# Entferne nicht erlaubte Felder für normale Benutzer
if not (is_admin or is_sysadmin):
user_data = {k: v for k, v in user_data.items() if k in allowed_fields}
# User-Daten aktualisieren
updated_user = gateway.update_user(user_id, user_data)
return updated_user
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(
user_id: int = Path(..., description="ID des zu löschenden Benutzers"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen Benutzer löschen"""
mandate_id, current_user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, current_user_id)
# Benutzer existiert?
user_to_delete = gateway.get_user(user_id)
if not user_to_delete:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Benutzer mit ID {user_id} nicht gefunden"
)
# Berechtigungsprüfung
is_self_delete = user_id == current_user_id
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
same_mandate = user_to_delete.get("mandate_id") == mandate_id
if is_self_delete:
# Benutzer darf sich selbst löschen
pass
elif is_admin and same_mandate:
# Admin darf Benutzer des eigenen Mandanten löschen
pass
elif is_sysadmin:
# SysAdmin darf alle Benutzer löschen
pass
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Keine Berechtigung zum Löschen dieses Benutzers"
)
# Benutzer und alle referenzierten Objekte löschen
success = gateway.delete_user(user_id)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Löschen des Benutzers mit ID {user_id}"
)
# Kein Inhalt zurückgeben bei erfolgreichem Löschen
return None