restructured for scaling
This commit is contained in:
parent
98c8e8d545
commit
70bfdc0342
16 changed files with 1795 additions and 577 deletions
390
gwserver/app.py
390
gwserver/app.py
|
|
@ -2,7 +2,7 @@ from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Que
|
|||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
|
||||
|
||||
import uvicorn
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
|
@ -10,14 +10,26 @@ import uuid
|
|||
import os
|
||||
import json
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from database import get_db, Database
|
||||
from agent_service import AgentService, get_agent_service
|
||||
from datetime import timedelta
|
||||
from typing import Dict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from models import (
|
||||
# Import interfaces
|
||||
from interface_gateway import get_gateway_interface
|
||||
from interface_lucydom import get_lucydom_interface
|
||||
from interface_agentservice import get_agent_service
|
||||
|
||||
# Import auth module
|
||||
from auth import (
|
||||
create_access_token,
|
||||
get_current_user,
|
||||
get_current_active_user,
|
||||
get_user_context,
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
|
||||
# Import models (restructured)
|
||||
from model_gateway import User, UserInDB, Token, Mandate
|
||||
from model_lucydom import (
|
||||
Workspace,
|
||||
Agent,
|
||||
DataObject,
|
||||
|
|
@ -28,15 +40,6 @@ from models import (
|
|||
Result
|
||||
)
|
||||
|
||||
from auth import (
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES,
|
||||
authenticate_user,
|
||||
create_access_token,
|
||||
fake_users_db,
|
||||
get_current_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Konfiguration des Loggers
|
||||
logging.basicConfig(
|
||||
|
|
@ -57,9 +60,6 @@ app.add_middleware(
|
|||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Pfad zum Webparts-Verzeichnis
|
||||
WEBPARTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webparts")
|
||||
|
||||
# Verzeichnis für hochgeladene Dateien erstellen
|
||||
UPLOAD_DIR = os.path.join(os.getcwd(), "uploads")
|
||||
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||
|
|
@ -67,6 +67,7 @@ os.makedirs(UPLOAD_DIR, exist_ok=True)
|
|||
# Komponenten des Frontends
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
|
||||
# API - ENDPUNKTE
|
||||
|
||||
@app.get("/", tags=["General"])
|
||||
|
|
@ -74,121 +75,194 @@ async def root():
|
|||
"""API-Statusendpunkt"""
|
||||
return {"status": "online", "message": "Data Platform API ist aktiv"}
|
||||
|
||||
|
||||
# Test Element
|
||||
@app.get("/api/test", tags=["Test"])
|
||||
async def get_test():
|
||||
return "OK 1.0"
|
||||
|
||||
# Token-Endpunkt
|
||||
@app.post("/token", response_model=Dict[str, str])
|
||||
|
||||
# Token-Endpunkt für Login
|
||||
@app.post("/token", response_model=Token)
|
||||
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
|
||||
# Gateway-Interface ohne Kontext initialisieren
|
||||
gateway = get_gateway_interface()
|
||||
|
||||
# Benutzer authentifizieren
|
||||
user = gateway.authenticate_user(form_data.username, form_data.password)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Ungültige Zugangsdaten",
|
||||
detail="Ungültiger Benutzername oder Passwort",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Token mit Mandanten-ID erstellen
|
||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user["username"]}, expires_delta=access_token_expires
|
||||
data={
|
||||
"sub": user["username"],
|
||||
"mandate_id": user["mandate_id"]
|
||||
},
|
||||
expires_delta=access_token_expires
|
||||
)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
# Geschützter Endpunkt (Beispiel)
|
||||
@app.get("/api/users/me")
|
||||
async def read_users_me(current_user = Depends(get_current_user)):
|
||||
|
||||
# Benutzerregistrierung
|
||||
@app.post("/api/users/register", response_model=User)
|
||||
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
|
||||
@app.get("/api/users/me", response_model=Dict[str, Any])
|
||||
async def read_users_me(current_user: Dict[str, Any] = Depends(get_current_active_user)):
|
||||
return current_user
|
||||
|
||||
|
||||
# Workspace-Endpunkte
|
||||
@app.get("/api/workspaces", tags=["Workspaces"], response_model=List[Workspace])
|
||||
async def get_workspaces(
|
||||
current_user = Depends(get_current_user), # <-- Authentifizierung hinzugefügt
|
||||
db: Database = Depends(get_db)
|
||||
):
|
||||
@app.get("/api/workspaces", tags=["Workspaces"], response_model=List[Dict[str, Any]])
|
||||
async def get_workspaces(current_user: Dict[str, Any] = Depends(get_current_active_user)):
|
||||
"""Alle verfügbaren Workspaces abrufen"""
|
||||
return db.get_all_workspaces()
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
return lucy_interface.get_all_workspaces()
|
||||
|
||||
|
||||
@app.get("/api/workspaces/{workspace_id}", tags=["Workspaces"], response_model=Workspace)
|
||||
async def get_workspace(workspace_id: str, db: Database = Depends(get_db)):
|
||||
@app.get("/api/workspaces/{workspace_id}", tags=["Workspaces"], response_model=Dict[str, Any])
|
||||
async def get_workspace(
|
||||
workspace_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Einen bestimmten Workspace mit allen Details abrufen"""
|
||||
workspace = db.get_workspace(workspace_id)
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
workspace = lucy_interface.get_workspace(workspace_id)
|
||||
if not workspace:
|
||||
raise HTTPException(status_code=404, detail=f"Workspace mit ID {workspace_id} nicht gefunden")
|
||||
|
||||
return workspace
|
||||
|
||||
|
||||
@app.post("/api/workspaces", tags=["Workspaces"], response_model=Workspace)
|
||||
async def create_workspace(workspace: Dict[str, Any] = Body(...), db: Database = Depends(get_db)):
|
||||
@app.post("/api/workspaces", tags=["Workspaces"], response_model=Dict[str, Any])
|
||||
async def create_workspace(
|
||||
workspace: Dict[str, Any] = Body(...),
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Einen neuen Workspace erstellen"""
|
||||
workspace_id = str(uuid.uuid4())
|
||||
workspace_data = {
|
||||
"id": workspace_id,
|
||||
"name": workspace.get("name", "Neuer Workspace"),
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"prompts": [],
|
||||
"agents": [],
|
||||
"dataObjectReferences": []
|
||||
}
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
new_workspace = db.create_workspace(workspace_data)
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
new_workspace = lucy_interface.create_workspace(name=workspace.get("name", "Neuer Workspace"))
|
||||
return new_workspace
|
||||
|
||||
|
||||
# Agenten-Endpunkte
|
||||
@app.get("/api/agents", tags=["Agents"], response_model=List[Agent])
|
||||
async def get_agents(workspace_id: Optional[str] = Query(None), db: Database = Depends(get_db)):
|
||||
@app.get("/api/agents", tags=["Agents"], response_model=List[Dict[str, Any]])
|
||||
async def get_agents(
|
||||
workspace_id: Optional[int] = Query(None),
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Alle Agenten oder Agenten eines bestimmten Workspaces abrufen"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
if workspace_id:
|
||||
return db.get_agents_by_workspace(workspace_id)
|
||||
return db.get_all_agents()
|
||||
return lucy_interface.get_agents_by_workspace(workspace_id)
|
||||
|
||||
return lucy_interface.get_all_agents()
|
||||
|
||||
|
||||
@app.post("/api/agents", tags=["Agents"], response_model=Agent)
|
||||
@app.post("/api/agents", tags=["Agents"], response_model=Dict[str, Any])
|
||||
async def create_agent(
|
||||
agent: Dict[str, Any] = Body(...),
|
||||
db: Database = Depends(get_db)
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Einen neuen Agenten erstellen"""
|
||||
agent_id = str(uuid.uuid4())
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
workspace_id = agent.get("workspace_id")
|
||||
|
||||
if not workspace_id:
|
||||
raise HTTPException(status_code=400, detail="workspace_id ist erforderlich")
|
||||
|
||||
# Workspace existiert?
|
||||
workspace = db.get_workspace(workspace_id)
|
||||
workspace = lucy_interface.get_workspace(workspace_id)
|
||||
if not workspace:
|
||||
raise HTTPException(status_code=404, detail=f"Workspace mit ID {workspace_id} nicht gefunden")
|
||||
|
||||
agent_data = {
|
||||
"id": agent_id,
|
||||
"name": agent.get("name", "Neuer Agent"),
|
||||
"type": agent.get("type", "generic"),
|
||||
"capabilities": agent.get("capabilities", []),
|
||||
"description": agent.get("description", "")
|
||||
}
|
||||
new_agent = lucy_interface.create_agent(
|
||||
name=agent.get("name", "Neuer Agent"),
|
||||
agent_type=agent.get("type", "generic"),
|
||||
workspace_id=workspace_id,
|
||||
capabilities=agent.get("capabilities", []),
|
||||
description=agent.get("description", "")
|
||||
)
|
||||
|
||||
new_agent = db.create_agent(agent_data, workspace_id)
|
||||
return new_agent
|
||||
|
||||
|
||||
# Datei-Endpunkte
|
||||
@app.get("/api/files", tags=["Files"], response_model=List[DataObject])
|
||||
async def get_files(db: Database = Depends(get_db)):
|
||||
@app.get("/api/files", tags=["Files"], response_model=List[Dict[str, Any]])
|
||||
async def get_files(current_user: Dict[str, Any] = Depends(get_current_active_user)):
|
||||
"""Alle verfügbaren Dateien abrufen"""
|
||||
return db.get_all_files()
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
return lucy_interface.get_all_files()
|
||||
|
||||
|
||||
@app.post("/api/files/upload", tags=["Files"])
|
||||
async def upload_file(
|
||||
file: UploadFile = File(...),
|
||||
db: Database = Depends(get_db)
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Eine Datei hochladen"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
try:
|
||||
# Generiere eine eindeutige ID für die Datei
|
||||
file_id = str(uuid.uuid4())
|
||||
file_ext = os.path.splitext(file.filename)[1]
|
||||
file_path = os.path.join(UPLOAD_DIR, f"{file_id}{file_ext}")
|
||||
|
|
@ -202,24 +276,20 @@ async def upload_file(
|
|||
file_type = "image" if file.content_type and "image" in file.content_type else "document"
|
||||
|
||||
# In Datenbank speichern
|
||||
file_data = {
|
||||
"id": file_id,
|
||||
"name": file.filename,
|
||||
"type": file_type,
|
||||
"path": file_path,
|
||||
"content_type": file.content_type,
|
||||
"size": os.path.getsize(file_path),
|
||||
"upload_date": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
new_file = db.create_file(file_data)
|
||||
new_file = lucy_interface.create_file(
|
||||
name=file.filename,
|
||||
file_type=file_type,
|
||||
content_type=file.content_type,
|
||||
size=os.path.getsize(file_path),
|
||||
path=file_path
|
||||
)
|
||||
|
||||
return {
|
||||
"id": new_file["id"],
|
||||
"name": new_file["name"],
|
||||
"type": new_file["type"],
|
||||
"size": f"{new_file['size'] / (1024 * 1024):.1f} MB",
|
||||
"upload_date": new_file["upload_date"]
|
||||
"upload_date": new_file.get("upload_date")
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -229,14 +299,18 @@ async def upload_file(
|
|||
|
||||
@app.delete("/api/files/{file_id}", tags=["Files"])
|
||||
async def delete_file(
|
||||
file_id: str,
|
||||
current_user = Depends(get_current_user),
|
||||
db: Database = Depends(get_db)
|
||||
file_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Löscht eine Datei"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
try:
|
||||
# Hole die Datei aus der Datenbank
|
||||
file = db.get_file(file_id)
|
||||
file = lucy_interface.get_file(file_id)
|
||||
if not file:
|
||||
raise HTTPException(status_code=404, detail=f"Datei mit ID {file_id} nicht gefunden")
|
||||
|
||||
|
|
@ -249,7 +323,7 @@ async def delete_file(
|
|||
logger.warning(f"Konnte physische Datei nicht löschen: {e}")
|
||||
|
||||
# Lösche die Datei aus der Datenbank
|
||||
success = db.delete_file(file_id)
|
||||
success = lucy_interface.delete_file(file_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Fehler beim Löschen der Datei aus der Datenbank")
|
||||
|
|
@ -264,57 +338,75 @@ async def delete_file(
|
|||
|
||||
|
||||
# Prompt-Endpunkte
|
||||
@app.get("/api/prompts", tags=["Prompts"], response_model=List[Prompt])
|
||||
async def get_prompts(workspace_id: Optional[str] = Query(None), db: Database = Depends(get_db)):
|
||||
@app.get("/api/prompts", tags=["Prompts"], response_model=List[Dict[str, Any]])
|
||||
async def get_prompts(
|
||||
workspace_id: Optional[int] = Query(None),
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Alle Prompts oder Prompts eines bestimmten Workspaces abrufen"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
if workspace_id:
|
||||
return db.get_prompts_by_workspace(workspace_id)
|
||||
return db.get_all_prompts()
|
||||
return lucy_interface.get_prompts_by_workspace(workspace_id)
|
||||
|
||||
return lucy_interface.get_all_prompts()
|
||||
|
||||
|
||||
@app.post("/api/prompts", tags=["Prompts"], response_model=Prompt)
|
||||
@app.post("/api/prompts", tags=["Prompts"], response_model=Dict[str, Any])
|
||||
async def create_prompt(
|
||||
prompt: Dict[str, Any] = Body(...),
|
||||
db: Database = Depends(get_db)
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Einen neuen Prompt erstellen"""
|
||||
prompt_id = str(uuid.uuid4())
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
workspace_id = prompt.get("workspace_id")
|
||||
|
||||
if not workspace_id:
|
||||
raise HTTPException(status_code=400, detail="workspace_id ist erforderlich")
|
||||
|
||||
# Workspace existiert?
|
||||
workspace = db.get_workspace(workspace_id)
|
||||
workspace = lucy_interface.get_workspace(workspace_id)
|
||||
if not workspace:
|
||||
raise HTTPException(status_code=404, detail=f"Workspace mit ID {workspace_id} nicht gefunden")
|
||||
|
||||
prompt_data = {
|
||||
"id": prompt_id,
|
||||
"content": prompt.get("content", ""),
|
||||
"created_at": datetime.now().isoformat()
|
||||
}
|
||||
new_prompt = lucy_interface.create_prompt(
|
||||
content=prompt.get("content", ""),
|
||||
workspace_id=workspace_id
|
||||
)
|
||||
|
||||
new_prompt = db.create_prompt(prompt_data, workspace_id)
|
||||
return new_prompt
|
||||
|
||||
|
||||
# Workflow-Endpunkte
|
||||
@app.post("/api/workflow/run", tags=["Workflow"], response_model=WorkflowResponse)
|
||||
@app.post("/api/workflow/run", tags=["Workflow"], response_model=Dict[str, Any])
|
||||
async def run_workflow(
|
||||
workflow_request: WorkflowRequest,
|
||||
db: Database = Depends(get_db),
|
||||
agent_service: AgentService = Depends(get_agent_service)
|
||||
workflow_request: WorkflowRequest = Body(...),
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Führt einen Workflow mit den ausgewählten Agenten und Dateien aus"""
|
||||
workspace = db.get_workspace(workflow_request.workspace_id)
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# LucyDOM-Interface mit Benutzerkontext initialisieren
|
||||
lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
||||
|
||||
# AgentService mit Benutzerkontext initialisieren
|
||||
agent_service = get_agent_service(mandate_id, user_id)
|
||||
|
||||
workspace = lucy_interface.get_workspace(workflow_request.workspace_id)
|
||||
if not workspace:
|
||||
raise HTTPException(status_code=404, detail=f"Workspace mit ID {workflow_request.workspace_id} nicht gefunden")
|
||||
|
||||
# Prüfen, ob Dateien existieren
|
||||
files = []
|
||||
for file_id in workflow_request.files:
|
||||
file = db.get_file(file_id)
|
||||
file = lucy_interface.get_file(file_id)
|
||||
if not file:
|
||||
raise HTTPException(status_code=404, detail=f"Datei mit ID {file_id} nicht gefunden")
|
||||
files.append(file)
|
||||
|
|
@ -322,7 +414,7 @@ async def run_workflow(
|
|||
# Prüfen, ob Agenten existieren
|
||||
agents = []
|
||||
for agent_id in workflow_request.agents:
|
||||
agent = db.get_agent(agent_id)
|
||||
agent = lucy_interface.get_agent(agent_id)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail=f"Agent mit ID {agent_id} nicht gefunden")
|
||||
agents.append(agent)
|
||||
|
|
@ -341,48 +433,114 @@ async def run_workflow(
|
|||
)
|
||||
|
||||
# Sofort eine Antwort zurückgeben
|
||||
return WorkflowResponse(
|
||||
workflow_id=workflow_id,
|
||||
status="running",
|
||||
message="Workflow wurde gestartet"
|
||||
)
|
||||
return {
|
||||
"workflow_id": workflow_id,
|
||||
"status": "running",
|
||||
"message": "Workflow wurde gestartet"
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/workflow/{workflow_id}/status", tags=["Workflow"])
|
||||
async def get_workflow_status(
|
||||
workflow_id: str,
|
||||
agent_service: AgentService = Depends(get_agent_service)
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Status eines laufenden Workflows abrufen"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# AgentService mit Benutzerkontext initialisieren
|
||||
agent_service = get_agent_service(mandate_id, user_id)
|
||||
|
||||
status = agent_service.get_workflow_status(workflow_id)
|
||||
if not status:
|
||||
raise HTTPException(status_code=404, detail=f"Workflow mit ID {workflow_id} nicht gefunden")
|
||||
|
||||
return status
|
||||
|
||||
|
||||
@app.get("/api/workflow/{workflow_id}/logs", tags=["Workflow"], response_model=List[LogEntry])
|
||||
@app.get("/api/workflow/{workflow_id}/logs", tags=["Workflow"], response_model=List[Dict[str, Any]])
|
||||
async def get_workflow_logs(
|
||||
workflow_id: str,
|
||||
agent_service: AgentService = Depends(get_agent_service)
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Protokolle eines Workflows abrufen"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# AgentService mit Benutzerkontext initialisieren
|
||||
agent_service = get_agent_service(mandate_id, user_id)
|
||||
|
||||
logs = agent_service.get_workflow_logs(workflow_id)
|
||||
if logs is None:
|
||||
raise HTTPException(status_code=404, detail=f"Workflow mit ID {workflow_id} nicht gefunden")
|
||||
|
||||
return logs
|
||||
|
||||
|
||||
@app.get("/api/workflow/{workflow_id}/results", tags=["Workflow"], response_model=List[Result])
|
||||
@app.get("/api/workflow/{workflow_id}/results", tags=["Workflow"], response_model=List[Dict[str, Any]])
|
||||
async def get_workflow_results(
|
||||
workflow_id: str,
|
||||
agent_service: AgentService = Depends(get_agent_service)
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Ergebnisse eines Workflows abrufen"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# AgentService mit Benutzerkontext initialisieren
|
||||
agent_service = get_agent_service(mandate_id, user_id)
|
||||
|
||||
results = agent_service.get_workflow_results(workflow_id)
|
||||
if results is None:
|
||||
raise HTTPException(status_code=404, detail=f"Workflow mit ID {workflow_id} nicht gefunden")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# Mandanten-Endpunkte (neu)
|
||||
@app.get("/api/mandates", tags=["Mandates"], response_model=List[Dict[str, Any]])
|
||||
async def get_mandates(current_user: Dict[str, Any] = Depends(get_current_active_user)):
|
||||
"""Alle verfügbaren Mandanten abrufen (nur für Admin-Benutzer)"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# Gateway-Interface mit Benutzerkontext initialisieren
|
||||
gateway = get_gateway_interface(mandate_id, user_id)
|
||||
|
||||
# TODO: Hier sollte eine Berechtigungsprüfung erfolgen
|
||||
|
||||
return gateway.get_all_mandates()
|
||||
|
||||
|
||||
@app.post("/api/mandates", tags=["Mandates"], response_model=Dict[str, Any])
|
||||
async def create_mandate(
|
||||
mandate: Dict[str, Any] = Body(...),
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
):
|
||||
"""Einen neuen Mandanten erstellen (nur für Admin-Benutzer)"""
|
||||
mandate_id, user_id = await get_user_context(current_user)
|
||||
|
||||
# Gateway-Interface mit Benutzerkontext initialisieren
|
||||
gateway = get_gateway_interface(mandate_id, user_id)
|
||||
|
||||
# TODO: Hier sollte eine Berechtigungsprüfung erfolgen
|
||||
|
||||
new_mandate = gateway.create_mandate(
|
||||
name=mandate.get("name", "Neuer Mandant"),
|
||||
language=mandate.get("language", "de")
|
||||
)
|
||||
|
||||
return new_mandate
|
||||
|
||||
|
||||
# Event handler beim Herunterfahren
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""Führt Aufräumarbeiten beim Herunterfahren der Anwendung durch"""
|
||||
# Holen des AgentService ohne Kontext (für Aufräumarbeiten)
|
||||
agent_service = get_agent_service()
|
||||
|
||||
# HTTP-Client des AgentService schließen
|
||||
await agent_service.close()
|
||||
|
||||
logger.info("Anwendung wurde heruntergefahren")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|
||||
123
gwserver/auth.py
123
gwserver/auth.py
|
|
@ -1,70 +1,131 @@
|
|||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from typing import Optional, Dict, Any
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jose import JWTError, jwt
|
||||
import logging
|
||||
from interface_gateway import get_gateway_interface
|
||||
from model_gateway import User, Token
|
||||
|
||||
|
||||
# Konfigurationsvariablen
|
||||
SECRET_KEY = "dein-geheimer-schlüssel" # In Produktion aus Umgebungsvariablen laden!
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 600
|
||||
|
||||
# Password-Hashing
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
# OAuth2 Setup
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||
|
||||
# Beispiel-Benutzer (in Produktion aus Datenbank laden)
|
||||
fake_users_db = {
|
||||
"admin": {
|
||||
"username": "admin",
|
||||
"hashed_password": pwd_context.hash("admin123"),
|
||||
}
|
||||
}
|
||||
# Logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
def get_user(db, username: str):
|
||||
if username in db:
|
||||
user_dict = db[username]
|
||||
return user_dict
|
||||
return None
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
||||
"""
|
||||
Erstellt ein JWT Access Token.
|
||||
|
||||
def authenticate_user(fake_db, username: str, password: str):
|
||||
user = get_user(fake_db, username)
|
||||
if not user:
|
||||
return False
|
||||
if not verify_password(password, user["hashed_password"]):
|
||||
return False
|
||||
return user
|
||||
Args:
|
||||
data: Zu kodierende Daten (meist Benutzer-ID oder Benutzername)
|
||||
expires_delta: Gültigkeitsdauer des Tokens (optional)
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
Returns:
|
||||
JWT Token als String
|
||||
"""
|
||||
to_encode = data.copy()
|
||||
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
return encoded_jwt
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)) -> Dict[str, Any]:
|
||||
"""
|
||||
Extrahiert und validiert den aktuellen Benutzer aus dem JWT Token.
|
||||
|
||||
Args:
|
||||
token: JWT Token aus dem Authorization-Header
|
||||
|
||||
Returns:
|
||||
Benutzerdaten
|
||||
|
||||
Raises:
|
||||
HTTPException: Bei ungültigem Token oder Benutzer
|
||||
"""
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Ungültige Authentifizierungsdaten",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
try:
|
||||
# Token dekodieren
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
|
||||
# Benutzername aus dem Token extrahieren
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
|
||||
# Mandanten-ID aus dem Token extrahieren (falls vorhanden)
|
||||
mandate_id: int = payload.get("mandate_id", 1) # Standard: Root-Mandant
|
||||
|
||||
except JWTError:
|
||||
logger.warning("Ungültiges JWT Token")
|
||||
raise credentials_exception
|
||||
user = get_user(fake_users_db, username)
|
||||
|
||||
# Gateway-Interface ohne Kontext initialisieren
|
||||
gateway = get_gateway_interface()
|
||||
|
||||
# Benutzer aus der Datenbank abrufen
|
||||
user = gateway.get_user_by_username(username)
|
||||
|
||||
if user is None:
|
||||
logger.warning(f"Benutzer {username} nicht gefunden")
|
||||
raise credentials_exception
|
||||
|
||||
if user.get("disabled", False):
|
||||
logger.warning(f"Benutzer {username} ist deaktiviert")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Benutzer ist deaktiviert")
|
||||
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_active_user(current_user: Dict[str, Any] = Depends(get_current_user)) -> Dict[str, Any]:
|
||||
"""
|
||||
Stellt sicher, dass der Benutzer aktiv ist.
|
||||
|
||||
Args:
|
||||
current_user: Aktuelle Benutzerdaten
|
||||
|
||||
Returns:
|
||||
Benutzerdaten
|
||||
|
||||
Raises:
|
||||
HTTPException: Wenn der Benutzer deaktiviert ist
|
||||
"""
|
||||
if current_user.get("disabled", False):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Benutzer ist deaktiviert")
|
||||
|
||||
return current_user
|
||||
|
||||
|
||||
async def get_user_context(current_user: Dict[str, Any] = Depends(get_current_active_user)) -> tuple:
|
||||
"""
|
||||
Extrahiert den Benutzerkontext (Mandanten-ID und Benutzer-ID).
|
||||
|
||||
Args:
|
||||
current_user: Aktuelle Benutzerdaten
|
||||
|
||||
Returns:
|
||||
Tuple mit (mandate_id, user_id)
|
||||
"""
|
||||
mandate_id = current_user.get("mandate_id", 1) # Standard: Root-Mandant
|
||||
user_id = current_user.get("id")
|
||||
|
||||
return mandate_id, user_id
|
||||
353
gwserver/connector_db_json.py
Normal file
353
gwserver/connector_db_json.py
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
import json
|
||||
import os
|
||||
from typing import List, Dict, Any, Optional, Union
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JSONDatabaseConnector:
|
||||
"""
|
||||
Ein Konnektor für JSON-basierte Datenspeicherung.
|
||||
Stellt generische Datenbankoperationen bereit.
|
||||
"""
|
||||
|
||||
def __init__(self, db_folder: str, db_user: str = None, db_apikey: str = None, mandate_id: int = None, user_id: int = None):
|
||||
"""
|
||||
Initialisiert den JSON-Datenbankkonnektor.
|
||||
|
||||
Args:
|
||||
db_folder: Verzeichnis für die JSON-Dateien
|
||||
db_user: Benutzername für die Authentifizierung (optional)
|
||||
db_apikey: API-Schlüssel für die Authentifizierung (optional)
|
||||
mandate_id: Kontext-Parameter für den Mandanten
|
||||
user_id: Kontext-Parameter für den Benutzer
|
||||
"""
|
||||
# Speichere die Eingabeparameter
|
||||
self.db_folder = db_folder
|
||||
self.db_user = db_user
|
||||
self.db_apikey = db_apikey
|
||||
|
||||
# Prüfe, ob Kontext-Parameter gesetzt sind
|
||||
if mandate_id is None or user_id is None:
|
||||
raise ValueError("mandate_id und user_id müssen gesetzt sein")
|
||||
|
||||
self.mandate_id = mandate_id
|
||||
self.user_id = user_id
|
||||
|
||||
# Stelle sicher, dass das Datenbankverzeichnis existiert
|
||||
os.makedirs(db_folder, exist_ok=True)
|
||||
|
||||
# Cache für geladene Daten
|
||||
self._tables_cache = {}
|
||||
|
||||
logger.info(f"JSONDatabaseConnector initialisiert für Verzeichnis: {db_folder}")
|
||||
logger.info(f"Kontext: mandate_id={mandate_id}, user_id={user_id}")
|
||||
|
||||
def _get_table_path(self, table: str) -> str:
|
||||
"""Gibt den vollständigen Pfad zu einer Tabellendatei zurück"""
|
||||
return os.path.join(self.db_folder, f"{table}.json")
|
||||
|
||||
def _load_table(self, table: str) -> List[Dict[str, Any]]:
|
||||
"""Lädt eine Tabelle aus der entsprechenden JSON-Datei"""
|
||||
path = self._get_table_path(table)
|
||||
|
||||
# Wenn die Tabelle bereits im Cache ist, verwende den Cache
|
||||
if table in self._tables_cache:
|
||||
return self._tables_cache[table]
|
||||
|
||||
# Ansonsten lade die Datei
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self._tables_cache[table] = data
|
||||
return data
|
||||
else:
|
||||
# Wenn die Datei nicht existiert, erstelle eine leere Tabelle
|
||||
self._tables_cache[table] = []
|
||||
self._save_table(table, [])
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden der Tabelle {table}: {e}")
|
||||
return []
|
||||
|
||||
def _save_table(self, table: str, data: List[Dict[str, Any]]) -> bool:
|
||||
"""Speichert eine Tabelle in der entsprechenden JSON-Datei"""
|
||||
path = self._get_table_path(table)
|
||||
try:
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Aktualisiere den Cache
|
||||
self._tables_cache[table] = data
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Speichern der Tabelle {table}: {e}")
|
||||
return False
|
||||
|
||||
def _filter_by_context(self, records: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Filtert Datensätze nach dem Mandanten- und Benutzerkontext,
|
||||
sofern diese Felder im Datensatz existieren.
|
||||
"""
|
||||
filtered_records = []
|
||||
|
||||
for record in records:
|
||||
# Prüfe, ob mandate_id im Datensatz existiert und nicht null ist
|
||||
has_mandate = "mandate_id" in record and record["mandate_id"] is not None and record["mandate_id"] != ""
|
||||
|
||||
# Prüfe, ob user_id im Datensatz existiert und nicht null ist
|
||||
has_user = "user_id" in record and record["user_id"] is not None and record["user_id"] != ""
|
||||
|
||||
# Wenn beides existiert, filtere entsprechend
|
||||
if has_mandate and has_user:
|
||||
if record["mandate_id"] == self.mandate_id:
|
||||
filtered_records.append(record)
|
||||
# Wenn nur mandate_id existiert
|
||||
elif has_mandate and not has_user:
|
||||
if record["mandate_id"] == self.mandate_id:
|
||||
filtered_records.append(record)
|
||||
# Wenn weder mandate_id noch user_id existieren, füge den Datensatz hinzu
|
||||
elif not has_mandate and not has_user:
|
||||
filtered_records.append(record)
|
||||
|
||||
return filtered_records
|
||||
|
||||
def _apply_field_filter(self, records: List[Dict[str, Any]], field_filter: Dict[str, Any] = None) -> List[Dict[str, Any]]:
|
||||
"""Wendet einen Feldfilter auf die Datensätze an"""
|
||||
if not field_filter:
|
||||
return records
|
||||
|
||||
filtered_records = []
|
||||
|
||||
for record in records:
|
||||
match = True
|
||||
|
||||
for field, value in field_filter.items():
|
||||
if field not in record or record[field] != value:
|
||||
match = False
|
||||
break
|
||||
|
||||
if match:
|
||||
filtered_records.append(record)
|
||||
|
||||
return filtered_records
|
||||
|
||||
# Public API
|
||||
|
||||
def get_tables(self, filter_criteria: Dict[str, Any] = None) -> List[str]:
|
||||
"""
|
||||
Gibt eine Liste aller verfügbaren Tabellen zurück.
|
||||
|
||||
Args:
|
||||
filter_criteria: Optionale Filterkriterien (nicht implementiert)
|
||||
|
||||
Returns:
|
||||
Liste der Tabellennamen
|
||||
"""
|
||||
tables = []
|
||||
|
||||
try:
|
||||
for filename in os.listdir(self.db_folder):
|
||||
if filename.endswith('.json'):
|
||||
table_name = filename[:-5] # Entferne die .json-Endung
|
||||
tables.append(table_name)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Lesen des Datenbankverzeichnisses: {e}")
|
||||
|
||||
return tables
|
||||
|
||||
def get_fields(self, table: str, filter_criteria: Dict[str, Any] = None) -> List[str]:
|
||||
"""
|
||||
Gibt eine Liste aller Felder einer Tabelle zurück.
|
||||
|
||||
Args:
|
||||
table: Name der Tabelle
|
||||
filter_criteria: Optionale Filterkriterien (nicht implementiert)
|
||||
|
||||
Returns:
|
||||
Liste der Feldnamen
|
||||
"""
|
||||
# Lade die Tabellendaten
|
||||
data = self._load_table(table)
|
||||
|
||||
if not data:
|
||||
return []
|
||||
|
||||
# Nehme den ersten Datensatz als Referenz für die Felder
|
||||
fields = list(data[0].keys()) if data else []
|
||||
|
||||
return fields
|
||||
|
||||
def get_schema(self, table: str, language: str = None, filter_criteria: Dict[str, Any] = None) -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Gibt ein Schema-Objekt für eine Tabelle zurück mit Datentypen und Labels.
|
||||
|
||||
Args:
|
||||
table: Name der Tabelle
|
||||
language: Sprache für die Labels (optional)
|
||||
filter_criteria: Optionale Filterkriterien (nicht implementiert)
|
||||
|
||||
Returns:
|
||||
Schema-Objekt mit Feldern, Datentypen und Labels
|
||||
"""
|
||||
# Lade die Tabellendaten
|
||||
data = self._load_table(table)
|
||||
|
||||
schema = {}
|
||||
|
||||
if not data:
|
||||
return schema
|
||||
|
||||
# Nehme den ersten Datensatz als Referenz für die Felder und Datentypen
|
||||
first_record = data[0]
|
||||
|
||||
for field, value in first_record.items():
|
||||
# Bestimme den Datentyp
|
||||
data_type = type(value).__name__
|
||||
|
||||
# Label erstellen (Standardwert ist der Feldname)
|
||||
label = field
|
||||
|
||||
# Wenn model_info verfügbar ist, versuche das Label aus dem Modell zu holen
|
||||
# Implementierung hängt vom tatsächlichen Modell ab
|
||||
|
||||
schema[field] = {
|
||||
"type": data_type,
|
||||
"label": label
|
||||
}
|
||||
|
||||
return schema
|
||||
|
||||
def get_recordset(self, table: str, field_filter: Dict[str, Any] = None, record_filter: Dict[str, Any] = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Gibt eine Liste von Datensätzen aus einer Tabelle zurück, gefiltert nach Kriterien.
|
||||
|
||||
Args:
|
||||
table: Name der Tabelle
|
||||
field_filter: Filter für Felder (welche Felder zurückgegeben werden sollen)
|
||||
record_filter: Filter für Datensätze (welche Datensätze zurückgegeben werden sollen)
|
||||
|
||||
Returns:
|
||||
Liste der gefilterten Datensätze
|
||||
"""
|
||||
# Lade die Tabellendaten
|
||||
data = self._load_table(table)
|
||||
|
||||
# Filtere nach Mandanten- und Benutzerkontext
|
||||
filtered_data = self._filter_by_context(data)
|
||||
|
||||
# Wende record_filter an, wenn vorhanden
|
||||
if record_filter:
|
||||
filtered_data = self._apply_field_filter(filtered_data, record_filter)
|
||||
|
||||
# Wenn field_filter vorhanden ist, reduziere die Felder
|
||||
if field_filter and isinstance(field_filter, list):
|
||||
result = []
|
||||
for record in filtered_data:
|
||||
filtered_record = {}
|
||||
for field in field_filter:
|
||||
if field in record:
|
||||
filtered_record[field] = record[field]
|
||||
result.append(filtered_record)
|
||||
return result
|
||||
|
||||
return filtered_data
|
||||
|
||||
def record_create(self, table: str, record_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Erstellt einen neuen Datensatz in der Tabelle.
|
||||
|
||||
Args:
|
||||
table: Name der Tabelle
|
||||
record_data: Daten für den neuen Datensatz
|
||||
|
||||
Returns:
|
||||
Der erstellte Datensatz
|
||||
"""
|
||||
# Lade die Tabellendaten
|
||||
data = self._load_table(table)
|
||||
|
||||
# Füge mandate_id und user_id hinzu, falls nicht vorhanden
|
||||
if "mandate_id" not in record_data:
|
||||
record_data["mandate_id"] = self.mandate_id
|
||||
|
||||
if "user_id" not in record_data:
|
||||
record_data["user_id"] = self.user_id
|
||||
|
||||
# Füge den neuen Datensatz hinzu
|
||||
data.append(record_data)
|
||||
|
||||
# Speichere die aktualisierte Tabelle
|
||||
if self._save_table(table, data):
|
||||
return record_data
|
||||
else:
|
||||
raise ValueError(f"Fehler beim Erstellen des Datensatzes in Tabelle {table}")
|
||||
|
||||
def record_delete(self, table: str, record_id: Union[str, int]) -> bool:
|
||||
"""
|
||||
Löscht einen Datensatz aus der Tabelle.
|
||||
|
||||
Args:
|
||||
table: Name der Tabelle
|
||||
record_id: ID des zu löschenden Datensatzes
|
||||
|
||||
Returns:
|
||||
True bei Erfolg, False bei Fehler
|
||||
"""
|
||||
# Lade die Tabellendaten
|
||||
data = self._load_table(table)
|
||||
|
||||
# Suche den Datensatz
|
||||
for i, record in enumerate(data):
|
||||
if "id" in record and record["id"] == record_id:
|
||||
# Prüfe, ob der Datensatz zum aktuellen Mandanten gehört
|
||||
if "mandate_id" in record and record["mandate_id"] != self.mandate_id:
|
||||
raise ValueError("Not your mandate")
|
||||
|
||||
# Lösche den Datensatz
|
||||
del data[i]
|
||||
|
||||
# Speichere die aktualisierte Tabelle
|
||||
return self._save_table(table, data)
|
||||
|
||||
# Datensatz nicht gefunden
|
||||
return False
|
||||
|
||||
def record_modify(self, table: str, record_id: Union[str, int], record_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Ändert einen Datensatz in der Tabelle.
|
||||
|
||||
Args:
|
||||
table: Name der Tabelle
|
||||
record_id: ID des zu ändernden Datensatzes
|
||||
record_data: Neue Daten für den Datensatz
|
||||
|
||||
Returns:
|
||||
Der aktualisierte Datensatz
|
||||
"""
|
||||
# Lade die Tabellendaten
|
||||
data = self._load_table(table)
|
||||
|
||||
# Suche den Datensatz
|
||||
for i, record in enumerate(data):
|
||||
if "id" in record and record["id"] == record_id:
|
||||
# Prüfe, ob der Datensatz zum aktuellen Mandanten gehört
|
||||
if "mandate_id" in record and record["mandate_id"] != self.mandate_id:
|
||||
raise ValueError("Not your mandate")
|
||||
|
||||
# Aktualisiere den Datensatz
|
||||
for key, value in record_data.items():
|
||||
data[i][key] = value
|
||||
|
||||
# Speichere die aktualisierte Tabelle
|
||||
if self._save_table(table, data):
|
||||
return data[i]
|
||||
else:
|
||||
raise ValueError(f"Fehler beim Aktualisieren des Datensatzes in Tabelle {table}")
|
||||
|
||||
# Datensatz nicht gefunden
|
||||
raise ValueError(f"Datensatz mit ID {record_id} nicht gefunden in Tabelle {table}")
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Database:
|
||||
"""
|
||||
Eine einfache JSON-basierte Datenbank für die Datenspeicherung.
|
||||
In einer Produktionsumgebung würde hier eine richtige Datenbank verwendet werden.
|
||||
"""
|
||||
|
||||
def __init__(self, data_dir="./data"):
|
||||
self.data_dir = data_dir
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
# Standardpfade für die Datendateien
|
||||
self.workspaces_file = os.path.join(data_dir, "workspaces.json")
|
||||
self.agents_file = os.path.join(data_dir, "agents.json")
|
||||
self.files_file = os.path.join(data_dir, "files.json")
|
||||
self.prompts_file = os.path.join(data_dir, "prompts.json")
|
||||
|
||||
# Initiale Daten erstellen, falls die Dateien nicht existieren
|
||||
self._initialize_data()
|
||||
|
||||
def _initialize_data(self):
|
||||
"""Initialisiert die Datendateien, falls sie nicht existieren"""
|
||||
# Workspaces
|
||||
if not os.path.exists(self.workspaces_file):
|
||||
self._save_data(self.workspaces_file, [
|
||||
{
|
||||
"id": "workspace_001",
|
||||
"name": "Datenanalyse-Projekt",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"prompts": [],
|
||||
"agents": [],
|
||||
"dataObjectReferences": []
|
||||
},
|
||||
{
|
||||
"id": "workspace_002",
|
||||
"name": "Marktforschung",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"prompts": [],
|
||||
"agents": [],
|
||||
"dataObjectReferences": []
|
||||
}
|
||||
])
|
||||
|
||||
# Agenten
|
||||
if not os.path.exists(self.agents_file):
|
||||
self._save_data(self.agents_file, [
|
||||
{
|
||||
"id": "agent_001",
|
||||
"name": "Datenanalyse-Agent",
|
||||
"type": "analyzer",
|
||||
"workspace_id": "workspace_001",
|
||||
"capabilities": ["Datenanalyse", "Statistik", "Trendanalyse"],
|
||||
"description": "Spezialisiert auf die Analyse von strukturierten Daten"
|
||||
},
|
||||
{
|
||||
"id": "agent_002",
|
||||
"name": "Visualisierungs-Agent",
|
||||
"type": "visualizer",
|
||||
"workspace_id": "workspace_001",
|
||||
"capabilities": ["Datenvisualisierung", "Diagrammerstellung"],
|
||||
"description": "Erstellt visuelle Darstellungen aus Daten"
|
||||
},
|
||||
{
|
||||
"id": "agent_003",
|
||||
"name": "Text-Generator",
|
||||
"type": "writer",
|
||||
"workspace_id": "workspace_001",
|
||||
"capabilities": ["Texterstellung", "Zusammenfassung"],
|
||||
"description": "Verfasst verständliche Berichte und Zusammenfassungen"
|
||||
},
|
||||
{
|
||||
"id": "agent_004",
|
||||
"name": "Web-Scraper",
|
||||
"type": "scraper",
|
||||
"workspace_id": "workspace_002",
|
||||
"capabilities": ["Datensammlung", "Webanalyse"],
|
||||
"description": "Sammelt Daten aus verschiedenen Webquellen"
|
||||
},
|
||||
{
|
||||
"id": "agent_005",
|
||||
"name": "Marktanalyse-Agent",
|
||||
"type": "analyzer",
|
||||
"workspace_id": "workspace_002",
|
||||
"capabilities": ["Wettbewerbsanalyse", "Markttrends"],
|
||||
"description": "Spezialisiert auf Wettbewerbsanalyse und Markttrends"
|
||||
}
|
||||
])
|
||||
|
||||
# Dateien
|
||||
if not os.path.exists(self.files_file):
|
||||
self._save_data(self.files_file, [
|
||||
{
|
||||
"id": "file_001",
|
||||
"name": "Quartalsbericht Q1 2025.pdf",
|
||||
"type": "document",
|
||||
"content_type": "application/pdf",
|
||||
"size": 2500000, # ca. 2.4 MB
|
||||
"upload_date": datetime.now().isoformat(),
|
||||
"path": "uploads/dummy_file1.pdf"
|
||||
},
|
||||
{
|
||||
"id": "file_002",
|
||||
"name": "Marktanalyse-Diagramm.png",
|
||||
"type": "image",
|
||||
"content_type": "image/png",
|
||||
"size": 1150000, # ca. 1.1 MB
|
||||
"upload_date": datetime.now().isoformat(),
|
||||
"path": "uploads/dummy_file2.png"
|
||||
},
|
||||
{
|
||||
"id": "file_003",
|
||||
"name": "Finanzdaten_2024-2025.xlsx",
|
||||
"type": "document",
|
||||
"content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"size": 3880000, # ca. 3.7 MB
|
||||
"upload_date": datetime.now().isoformat(),
|
||||
"path": "uploads/dummy_file3.xlsx"
|
||||
}
|
||||
])
|
||||
|
||||
# Prompts
|
||||
if not os.path.exists(self.prompts_file):
|
||||
self._save_data(self.prompts_file, [
|
||||
{
|
||||
"id": "prompt_001",
|
||||
"content": "Analysiere die Quartalsdaten und erstelle eine Zusammenfassung mit wichtigsten Trends",
|
||||
"workspace_id": "workspace_001",
|
||||
"created_at": datetime.now().isoformat()
|
||||
},
|
||||
{
|
||||
"id": "prompt_002",
|
||||
"content": "Erstelle eine Prognose für das nächste Quartal basierend auf historischen Daten",
|
||||
"workspace_id": "workspace_001",
|
||||
"created_at": datetime.now().isoformat()
|
||||
}
|
||||
])
|
||||
|
||||
def _load_data(self, file_path):
|
||||
"""Lädt Daten aus einer JSON-Datei"""
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden der Daten aus {file_path}: {e}")
|
||||
return []
|
||||
|
||||
def _save_data(self, file_path, data):
|
||||
"""Speichert Daten in einer JSON-Datei"""
|
||||
try:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Speichern der Daten in {file_path}: {e}")
|
||||
|
||||
# Workspace-Methoden
|
||||
def get_all_workspaces(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Workspaces zurück"""
|
||||
workspaces = self._load_data(self.workspaces_file)
|
||||
|
||||
# Füge Agenten und Prompts hinzu
|
||||
for workspace in workspaces:
|
||||
workspace_id = workspace["id"]
|
||||
workspace["agents"] = self.get_agents_by_workspace(workspace_id)
|
||||
workspace["prompts"] = self.get_prompts_by_workspace(workspace_id)
|
||||
|
||||
return workspaces
|
||||
|
||||
def get_workspace(self, workspace_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Workspace anhand seiner ID zurück"""
|
||||
workspaces = self._load_data(self.workspaces_file)
|
||||
for workspace in workspaces:
|
||||
if workspace["id"] == workspace_id:
|
||||
# Füge Agenten und Prompts hinzu
|
||||
workspace["agents"] = self.get_agents_by_workspace(workspace_id)
|
||||
workspace["prompts"] = self.get_prompts_by_workspace(workspace_id)
|
||||
return workspace
|
||||
return None
|
||||
|
||||
def create_workspace(self, workspace_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Workspace"""
|
||||
workspaces = self._load_data(self.workspaces_file)
|
||||
workspaces.append(workspace_data)
|
||||
self._save_data(self.workspaces_file, workspaces)
|
||||
return workspace_data
|
||||
|
||||
# Agent-Methoden
|
||||
def get_all_agents(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Agenten zurück"""
|
||||
return self._load_data(self.agents_file)
|
||||
|
||||
def get_agents_by_workspace(self, workspace_id: str) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Agenten eines Workspaces zurück"""
|
||||
agents = self._load_data(self.agents_file)
|
||||
return [agent for agent in agents if agent.get("workspace_id") == workspace_id]
|
||||
|
||||
def get_agent(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Agenten anhand seiner ID zurück"""
|
||||
agents = self._load_data(self.agents_file)
|
||||
for agent in agents:
|
||||
if agent["id"] == agent_id:
|
||||
return agent
|
||||
return None
|
||||
|
||||
def create_agent(self, agent_data: Dict[str, Any], workspace_id: str) -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Agenten"""
|
||||
agents = self._load_data(self.agents_file)
|
||||
agent_data["workspace_id"] = workspace_id
|
||||
agents.append(agent_data)
|
||||
self._save_data(self.agents_file, agents)
|
||||
|
||||
# Aktualisiere die Workspace-Referenz
|
||||
workspaces = self._load_data(self.workspaces_file)
|
||||
for workspace in workspaces:
|
||||
if workspace["id"] == workspace_id:
|
||||
if "agents" not in workspace:
|
||||
workspace["agents"] = []
|
||||
workspace["agents"].append(agent_data["id"])
|
||||
break
|
||||
self._save_data(self.workspaces_file, workspaces)
|
||||
|
||||
return agent_data
|
||||
|
||||
# Datei-Methoden
|
||||
def get_all_files(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Dateien zurück"""
|
||||
files = self._load_data(self.files_file)
|
||||
return files
|
||||
|
||||
def get_file(self, file_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt eine Datei anhand ihrer ID zurück"""
|
||||
files = self._load_data(self.files_file)
|
||||
for file in files:
|
||||
if file["id"] == file_id:
|
||||
return file
|
||||
return None
|
||||
|
||||
def create_file(self, file_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Dateieintrag"""
|
||||
files = self._load_data(self.files_file)
|
||||
files.append(file_data)
|
||||
self._save_data(self.files_file, files)
|
||||
return file_data
|
||||
|
||||
def delete_file(self, file_id: str) -> bool:
|
||||
"""Löscht eine Datei aus der Datenbank"""
|
||||
try:
|
||||
index = next((i for i, file in enumerate(self.data["files"]) if file["id"] == file_id), -1)
|
||||
if index == -1:
|
||||
return False
|
||||
self.data["files"].pop(index)
|
||||
self._save_data()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Datenbankfehler beim Löschen der Datei: {e}")
|
||||
return False
|
||||
|
||||
# Prompt-Methoden
|
||||
def get_all_prompts(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Prompts zurück"""
|
||||
return self._load_data(self.prompts_file)
|
||||
|
||||
def get_prompts_by_workspace(self, workspace_id: str) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Prompts eines Workspaces zurück"""
|
||||
prompts = self._load_data(self.prompts_file)
|
||||
return [prompt for prompt in prompts if prompt.get("workspace_id") == workspace_id]
|
||||
|
||||
def get_prompt(self, prompt_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Prompt anhand seiner ID zurück"""
|
||||
prompts = self._load_data(self.prompts_file)
|
||||
for prompt in prompts:
|
||||
if prompt["id"] == prompt_id:
|
||||
return prompt
|
||||
return None
|
||||
|
||||
def create_prompt(self, prompt_data: Dict[str, Any], workspace_id: str) -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Prompt"""
|
||||
prompts = self._load_data(self.prompts_file)
|
||||
prompt_data["workspace_id"] = workspace_id
|
||||
prompts.append(prompt_data)
|
||||
self._save_data(self.prompts_file, prompts)
|
||||
|
||||
# Aktualisiere die Workspace-Referenz
|
||||
workspaces = self._load_data(self.workspaces_file)
|
||||
for workspace in workspaces:
|
||||
if workspace["id"] == workspace_id:
|
||||
if "prompts" not in workspace:
|
||||
workspace["prompts"] = []
|
||||
workspace["prompts"].append(prompt_data["id"])
|
||||
break
|
||||
self._save_data(self.workspaces_file, workspaces)
|
||||
|
||||
return prompt_data
|
||||
|
||||
|
||||
# Singleton-Instanz der Datenbank
|
||||
_db_instance = None
|
||||
|
||||
def get_db():
|
||||
"""Gibt eine Singleton-Instanz der Datenbank zurück"""
|
||||
global _db_instance
|
||||
if _db_instance is None:
|
||||
_db_instance = Database()
|
||||
return _db_instance
|
||||
|
|
@ -9,15 +9,16 @@ import re
|
|||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
import sys
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
# Logger konfigurieren
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Konfiguration aus config.ini laden
|
||||
def load_config():
|
||||
config = configparser.ConfigParser()
|
||||
|
|
@ -81,12 +82,24 @@ def load_config():
|
|||
logger.error(f"Fehler beim Laden der Konfiguration: {e}")
|
||||
return default_config
|
||||
|
||||
|
||||
class AgentService:
|
||||
"""
|
||||
Service für die Verwaltung und Ausführung von Multi-Agent-Workflows mit OpenAI GPT-4o.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, mandate_id: int = None, user_id: int = None):
|
||||
"""
|
||||
Initialisiert den AgentService.
|
||||
|
||||
Args:
|
||||
mandate_id: ID des aktuellen Mandanten (optional)
|
||||
user_id: ID des aktuellen Benutzers (optional)
|
||||
"""
|
||||
# Mandanten- und Benutzerkontext
|
||||
self.mandate_id = mandate_id
|
||||
self.user_id = user_id
|
||||
|
||||
# Konfiguration laden
|
||||
self.config = load_config()
|
||||
|
||||
|
|
@ -134,9 +147,11 @@ class AgentService:
|
|||
"""
|
||||
logger.info(f"Starte Workflow {workflow_id} mit {len(agents)} Agenten und {len(files)} Dateien")
|
||||
|
||||
# Workflow initialisieren
|
||||
# Mandanten- und Benutzerkontext in die Workflow-Daten aufnehmen
|
||||
self.workflows[workflow_id] = {
|
||||
"id": workflow_id,
|
||||
"mandate_id": self.mandate_id,
|
||||
"user_id": self.user_id,
|
||||
"status": "running",
|
||||
"progress": 0.0,
|
||||
"started_at": datetime.now().isoformat(),
|
||||
|
|
@ -367,6 +382,9 @@ class AgentService:
|
|||
|
||||
return workflow_id
|
||||
|
||||
# Die übrigen Methoden bleiben unverändert
|
||||
# ...
|
||||
|
||||
async def _scrape_web_data(self, workflow_id: str, prompt: str) -> str:
|
||||
"""
|
||||
Führt Web-Scraping basierend auf dem Prompt durch
|
||||
|
|
@ -625,6 +643,8 @@ class AgentService:
|
|||
"""Fügt einen Protokolleintrag zum Workflow hinzu"""
|
||||
log_entry = {
|
||||
"id": f"log_{uuid.uuid4()}",
|
||||
"mandate_id": self.mandate_id,
|
||||
"user_id": self.user_id,
|
||||
"message": message,
|
||||
"type": log_type,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
|
|
@ -654,6 +674,8 @@ class AgentService:
|
|||
# Grundlegende Ergebnisstruktur
|
||||
result = {
|
||||
"id": f"result_{workflow_id}_{index}",
|
||||
"mandate_id": self.mandate_id,
|
||||
"user_id": self.user_id,
|
||||
"agent_id": agent_id,
|
||||
"agent_name": agent_name,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
|
|
@ -714,6 +736,8 @@ class AgentService:
|
|||
|
||||
return {
|
||||
"id": workflow["id"],
|
||||
"mandate_id": workflow.get("mandate_id"),
|
||||
"user_id": workflow.get("user_id"),
|
||||
"status": workflow["status"],
|
||||
"progress": workflow["progress"],
|
||||
"started_at": workflow["started_at"],
|
||||
|
|
@ -742,12 +766,15 @@ class AgentService:
|
|||
await self.http_client.aclose()
|
||||
|
||||
|
||||
# Singleton-Instanz des AgentService
|
||||
_agent_service_instance = None
|
||||
# Singleton-Factory für AgentService-Instanzen pro Kontext
|
||||
_agent_service_instances = {}
|
||||
|
||||
def get_agent_service():
|
||||
"""Gibt eine Singleton-Instanz des AgentService zurück"""
|
||||
global _agent_service_instance
|
||||
if _agent_service_instance is None:
|
||||
_agent_service_instance = AgentService()
|
||||
return _agent_service_instance
|
||||
def get_agent_service(mandate_id: int = None, user_id: int = None) -> AgentService:
|
||||
"""
|
||||
Gibt eine AgentService-Instanz für den angegebenen Kontext zurück.
|
||||
Wiederverwendet bestehende Instanzen.
|
||||
"""
|
||||
context_key = f"{mandate_id}_{user_id}"
|
||||
if context_key not in _agent_service_instances:
|
||||
_agent_service_instances[context_key] = AgentService(mandate_id, user_id)
|
||||
return _agent_service_instances[context_key]
|
||||
253
gwserver/interface_gateway.py
Normal file
253
gwserver/interface_gateway.py
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
import os
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
import importlib
|
||||
from passlib.context import CryptContext
|
||||
from connector_db_json import JSONDatabaseConnector
|
||||
from model_gateway import User, UserInDB, Token, Mandate
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Password-Hashing
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
class GatewayInterface:
|
||||
"""
|
||||
Interface zum Gateway-System.
|
||||
Verwaltet Benutzer und Mandanten.
|
||||
"""
|
||||
|
||||
def __init__(self, mandate_id: int = None, user_id: int = None):
|
||||
"""
|
||||
Initialisiert das Gateway-Interface mit optionalem Mandanten- und Benutzerkontext.
|
||||
|
||||
Args:
|
||||
mandate_id: ID des aktuellen Mandanten (optional)
|
||||
user_id: ID des aktuellen Benutzers (optional)
|
||||
"""
|
||||
# Bei der Initialisierung kann der Kontext leer sein
|
||||
self.mandate_id = mandate_id if mandate_id is not None else 1 # Root-Mandant als Standard
|
||||
self.user_id = user_id if user_id is not None else 1 # Admin-Benutzer als Standard
|
||||
|
||||
# Datenverzeichnis
|
||||
self.data_folder = "/data_gateway"
|
||||
os.makedirs(self.data_folder, exist_ok=True)
|
||||
|
||||
# Datenmodell-Modul importieren
|
||||
try:
|
||||
self.model_module = importlib.import_module("model_gateway")
|
||||
logger.info("model_gateway erfolgreich importiert")
|
||||
except ImportError as e:
|
||||
logger.error(f"Fehler beim Importieren von model_gateway: {e}")
|
||||
raise
|
||||
|
||||
# Konnektor erstellen
|
||||
self.db = JSONDatabaseConnector(
|
||||
db_folder=self.data_folder,
|
||||
mandate_id=self.mandate_id,
|
||||
user_id=self.user_id
|
||||
)
|
||||
|
||||
# Datenbank initialisieren, falls nötig
|
||||
self._initialize_database()
|
||||
|
||||
def _initialize_database(self):
|
||||
"""
|
||||
Initialisiert die Datenbank mit minimalen Objekten,
|
||||
falls sie noch nicht existiert.
|
||||
"""
|
||||
# Prüfe, ob Mandanten existieren
|
||||
mandates = self.db.get_recordset("mandates")
|
||||
|
||||
# Erstelle den Root-Mandanten, falls nötig
|
||||
if not mandates:
|
||||
logger.info("Erstelle Root-Mandant")
|
||||
|
||||
root_mandate = {
|
||||
"id": 1,
|
||||
"name": "Root",
|
||||
"language": "de"
|
||||
}
|
||||
|
||||
self.db.record_create("mandates", root_mandate)
|
||||
logger.info("Root-Mandant wurde erstellt")
|
||||
|
||||
# Prüfe, ob Benutzer existieren
|
||||
users = self.db.get_recordset("users")
|
||||
|
||||
# Erstelle den Admin-Benutzer, falls nötig
|
||||
if not users:
|
||||
logger.info("Erstelle Admin-Benutzer")
|
||||
|
||||
admin_user = {
|
||||
"id": 1,
|
||||
"mandate_id": 1, # Root-Mandant
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"full_name": "Administrator",
|
||||
"disabled": False,
|
||||
"language": "de",
|
||||
"hashed_password": self._get_password_hash("admin") # In der Produktion ein sicheres Passwort verwenden!
|
||||
}
|
||||
|
||||
self.db.record_create("users", admin_user)
|
||||
logger.info("Admin-Benutzer wurde erstellt")
|
||||
|
||||
def _get_password_hash(self, password: str) -> str:
|
||||
"""Erstellt einen Hash für ein Passwort"""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def _verify_password(self, plain_password: str, hashed_password: str) -> bool:
|
||||
"""Überprüft, ob das Passwort zum Hash passt"""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
def _get_current_timestamp(self) -> str:
|
||||
"""Gibt den aktuellen Zeitstempel im ISO-Format zurück"""
|
||||
from datetime import datetime
|
||||
return datetime.now().isoformat()
|
||||
|
||||
# Mandanten-Methoden
|
||||
|
||||
def get_all_mandates(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Mandanten zurück"""
|
||||
return self.db.get_recordset("mandates")
|
||||
|
||||
def get_mandate(self, mandate_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Mandanten anhand seiner ID zurück"""
|
||||
mandates = self.db.get_recordset("mandates", record_filter={"id": mandate_id})
|
||||
if mandates:
|
||||
return mandates[0]
|
||||
return None
|
||||
|
||||
def create_mandate(self, name: str, language: str = "de") -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Mandanten"""
|
||||
# Bestimme die nächste ID
|
||||
mandates = self.db.get_recordset("mandates")
|
||||
next_id = 1
|
||||
if mandates:
|
||||
next_id = max(mandate["id"] for mandate in mandates) + 1
|
||||
|
||||
mandate_data = {
|
||||
"id": next_id,
|
||||
"name": name,
|
||||
"language": language
|
||||
}
|
||||
|
||||
return self.db.record_create("mandates", mandate_data)
|
||||
|
||||
# Benutzer-Methoden
|
||||
|
||||
def get_all_users(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Benutzer des aktuellen Mandanten zurück"""
|
||||
return self.db.get_recordset("users")
|
||||
|
||||
def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Benutzer anhand seines Benutzernamens zurück"""
|
||||
users = self.db.get_recordset("users")
|
||||
for user in users:
|
||||
if user.get("username") == username:
|
||||
return user
|
||||
return None
|
||||
|
||||
def get_user(self, user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Benutzer anhand seiner ID zurück"""
|
||||
users = self.db.get_recordset("users", record_filter={"id": user_id})
|
||||
if users:
|
||||
return users[0]
|
||||
return None
|
||||
|
||||
def create_user(self, username: str, password: str, email: str = None,
|
||||
full_name: str = None, language: str = "de") -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Benutzer"""
|
||||
# Prüfe, ob der Benutzername bereits existiert
|
||||
existing_user = self.get_user_by_username(username)
|
||||
if existing_user:
|
||||
raise ValueError(f"Benutzer '{username}' existiert bereits")
|
||||
|
||||
# Bestimme die nächste ID
|
||||
users = self.db.get_recordset("users")
|
||||
next_id = 1
|
||||
if users:
|
||||
next_id = max(user["id"] for user in users) + 1
|
||||
|
||||
user_data = {
|
||||
"id": next_id,
|
||||
"mandate_id": self.mandate_id,
|
||||
"username": username,
|
||||
"email": email,
|
||||
"full_name": full_name,
|
||||
"disabled": False,
|
||||
"language": language,
|
||||
"hashed_password": self._get_password_hash(password)
|
||||
}
|
||||
|
||||
created_user = self.db.record_create("users", user_data)
|
||||
|
||||
# Entferne das Passwort-Hash aus der Rückgabe
|
||||
if "hashed_password" in created_user:
|
||||
del created_user["hashed_password"]
|
||||
|
||||
return created_user
|
||||
|
||||
def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]:
|
||||
"""Authentifiziert einen Benutzer anhand von Benutzername und Passwort"""
|
||||
user = self.get_user_by_username(username)
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
if not self._verify_password(password, user.get("hashed_password", "")):
|
||||
return None
|
||||
|
||||
# Erstelle eine Kopie ohne Passwort-Hash
|
||||
authenticated_user = {**user}
|
||||
if "hashed_password" in authenticated_user:
|
||||
del authenticated_user["hashed_password"]
|
||||
|
||||
return authenticated_user
|
||||
|
||||
def update_user(self, user_id: int, user_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Aktualisiert einen Benutzer"""
|
||||
# Hole den aktuellen Benutzer
|
||||
user = self.get_user(user_id)
|
||||
if not user:
|
||||
raise ValueError(f"Benutzer mit ID {user_id} nicht gefunden")
|
||||
|
||||
# Wenn das Passwort geändert werden soll, hashe es
|
||||
if "password" in user_data:
|
||||
user_data["hashed_password"] = self._get_password_hash(user_data["password"])
|
||||
del user_data["password"]
|
||||
|
||||
# Aktualisiere den Benutzer
|
||||
updated_user = self.db.record_modify("users", user_id, user_data)
|
||||
|
||||
# Entferne das Passwort-Hash aus der Rückgabe
|
||||
if "hashed_password" in updated_user:
|
||||
del updated_user["hashed_password"]
|
||||
|
||||
return updated_user
|
||||
|
||||
def disable_user(self, user_id: int) -> Dict[str, Any]:
|
||||
"""Deaktiviert einen Benutzer"""
|
||||
return self.update_user(user_id, {"disabled": True})
|
||||
|
||||
def enable_user(self, user_id: int) -> Dict[str, Any]:
|
||||
"""Aktiviert einen Benutzer"""
|
||||
return self.update_user(user_id, {"disabled": False})
|
||||
|
||||
|
||||
# Singleton-Factory für GatewayInterface-Instanzen pro Kontext
|
||||
_gateway_interfaces = {}
|
||||
|
||||
def get_gateway_interface(mandate_id: int = None, user_id: int = None) -> GatewayInterface:
|
||||
"""
|
||||
Gibt eine GatewayInterface-Instanz für den angegebenen Kontext zurück.
|
||||
Wiederverwendet bestehende Instanzen.
|
||||
"""
|
||||
context_key = f"{mandate_id}_{user_id}"
|
||||
if context_key not in _gateway_interfaces:
|
||||
_gateway_interfaces[context_key] = GatewayInterface(mandate_id, user_id)
|
||||
return _gateway_interfaces[context_key]
|
||||
237
gwserver/interface_lucydom.py
Normal file
237
gwserver/interface_lucydom.py
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
import os
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
import importlib
|
||||
from connector_db_json import JSONDatabaseConnector
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LucyDOMInterface:
|
||||
"""
|
||||
Interface zur LucyDOM-Datenbank.
|
||||
Verwendet den JSON-Konnektor für den Datenzugriff.
|
||||
"""
|
||||
|
||||
def __init__(self, mandate_id: int, user_id: int):
|
||||
"""
|
||||
Initialisiert das LucyDOM-Interface mit Mandanten- und Benutzerkontext.
|
||||
|
||||
Args:
|
||||
mandate_id: ID des aktuellen Mandanten
|
||||
user_id: ID des aktuellen Benutzers
|
||||
"""
|
||||
self.mandate_id = mandate_id
|
||||
self.user_id = user_id
|
||||
|
||||
# Datenverzeichnis
|
||||
self.data_folder = "/data_lucydom"
|
||||
os.makedirs(self.data_folder, exist_ok=True)
|
||||
|
||||
# Datenmodell-Modul importieren
|
||||
try:
|
||||
self.model_module = importlib.import_module("model_lucydom")
|
||||
logger.info("model_lucydom erfolgreich importiert")
|
||||
except ImportError as e:
|
||||
logger.error(f"Fehler beim Importieren von model_lucydom: {e}")
|
||||
raise
|
||||
|
||||
# Konnektor erstellen
|
||||
self.db = JSONDatabaseConnector(
|
||||
db_folder=self.data_folder,
|
||||
mandate_id=mandate_id,
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
# Datenbank initialisieren, falls nötig
|
||||
self._initialize_database()
|
||||
|
||||
def _initialize_database(self):
|
||||
"""
|
||||
Initialisiert die Datenbank mit minimalen Objekten,
|
||||
falls sie noch nicht existiert.
|
||||
"""
|
||||
# Prüfe, ob die Tabelle "workspaces" existiert
|
||||
workspaces = self.db.get_recordset("workspaces")
|
||||
|
||||
# Wenn keine Workspaces existieren, erstelle den Default Workspace
|
||||
if not workspaces:
|
||||
logger.info("Erstelle Default Workspace")
|
||||
|
||||
# Erstelle den Default Workspace
|
||||
default_workspace = {
|
||||
"id": 1,
|
||||
"mandate_id": self.mandate_id,
|
||||
"user_id": self.user_id,
|
||||
"name": "Default Workspace",
|
||||
"created_at": self._get_current_timestamp()
|
||||
}
|
||||
|
||||
self.db.record_create("workspaces", default_workspace)
|
||||
logger.info("Default Workspace wurde erstellt")
|
||||
|
||||
def _get_current_timestamp(self) -> str:
|
||||
"""Gibt den aktuellen Zeitstempel im ISO-Format zurück"""
|
||||
from datetime import datetime
|
||||
return datetime.now().isoformat()
|
||||
|
||||
# Workspace-Methoden
|
||||
|
||||
def get_all_workspaces(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Workspaces des aktuellen Mandanten zurück"""
|
||||
return self.db.get_recordset("workspaces")
|
||||
|
||||
def get_workspace(self, workspace_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Workspace anhand seiner ID zurück"""
|
||||
workspaces = self.db.get_recordset("workspaces", record_filter={"id": workspace_id})
|
||||
if workspaces:
|
||||
return workspaces[0]
|
||||
return None
|
||||
|
||||
def create_workspace(self, name: str) -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Workspace"""
|
||||
# Bestimme die nächste ID
|
||||
workspaces = self.db.get_recordset("workspaces")
|
||||
next_id = 1
|
||||
if workspaces:
|
||||
next_id = max(workspace["id"] for workspace in workspaces) + 1
|
||||
|
||||
workspace_data = {
|
||||
"id": next_id,
|
||||
"mandate_id": self.mandate_id,
|
||||
"user_id": self.user_id,
|
||||
"name": name,
|
||||
"created_at": self._get_current_timestamp()
|
||||
}
|
||||
|
||||
return self.db.record_create("workspaces", workspace_data)
|
||||
|
||||
# Agent-Methoden
|
||||
|
||||
def get_all_agents(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Agenten des aktuellen Mandanten zurück"""
|
||||
return self.db.get_recordset("agents")
|
||||
|
||||
def get_agents_by_workspace(self, workspace_id: int) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Agenten eines Workspaces zurück"""
|
||||
return self.db.get_recordset("agents", record_filter={"workspace_id": workspace_id})
|
||||
|
||||
def get_agent(self, agent_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Agenten anhand seiner ID zurück"""
|
||||
agents = self.db.get_recordset("agents", record_filter={"id": agent_id})
|
||||
if agents:
|
||||
return agents[0]
|
||||
return None
|
||||
|
||||
def create_agent(self, name: str, agent_type: str, workspace_id: int,
|
||||
capabilities: List[str] = None, description: str = None) -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Agenten"""
|
||||
# Bestimme die nächste ID
|
||||
agents = self.db.get_recordset("agents")
|
||||
next_id = 1
|
||||
if agents:
|
||||
next_id = max(agent["id"] for agent in agents) + 1
|
||||
|
||||
agent_data = {
|
||||
"id": next_id,
|
||||
"mandate_id": self.mandate_id,
|
||||
"user_id": self.user_id,
|
||||
"name": name,
|
||||
"type": agent_type,
|
||||
"workspace_id": workspace_id,
|
||||
"capabilities": capabilities or [],
|
||||
"description": description
|
||||
}
|
||||
|
||||
return self.db.record_create("agents", agent_data)
|
||||
|
||||
# Datei-Methoden
|
||||
|
||||
def get_all_files(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Dateien des aktuellen Mandanten zurück"""
|
||||
return self.db.get_recordset("files")
|
||||
|
||||
def get_file(self, file_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt eine Datei anhand ihrer ID zurück"""
|
||||
files = self.db.get_recordset("files", record_filter={"id": file_id})
|
||||
if files:
|
||||
return files[0]
|
||||
return None
|
||||
|
||||
def create_file(self, name: str, file_type: str, content_type: str = None,
|
||||
size: int = None, path: str = None) -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Dateieintrag"""
|
||||
# Bestimme die nächste ID
|
||||
files = self.db.get_recordset("files")
|
||||
next_id = 1
|
||||
if files:
|
||||
next_id = max(file["id"] for file in files) + 1
|
||||
|
||||
file_data = {
|
||||
"id": next_id,
|
||||
"mandate_id": self.mandate_id,
|
||||
"user_id": self.user_id,
|
||||
"name": name,
|
||||
"type": file_type,
|
||||
"content_type": content_type,
|
||||
"size": size,
|
||||
"path": path,
|
||||
"upload_date": self._get_current_timestamp()
|
||||
}
|
||||
|
||||
return self.db.record_create("files", file_data)
|
||||
|
||||
def delete_file(self, file_id: int) -> bool:
|
||||
"""Löscht eine Datei aus der Datenbank"""
|
||||
return self.db.record_delete("files", file_id)
|
||||
|
||||
# Prompt-Methoden
|
||||
|
||||
def get_all_prompts(self) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Prompts des aktuellen Mandanten zurück"""
|
||||
return self.db.get_recordset("prompts")
|
||||
|
||||
def get_prompts_by_workspace(self, workspace_id: int) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Prompts eines Workspaces zurück"""
|
||||
return self.db.get_recordset("prompts", record_filter={"workspace_id": workspace_id})
|
||||
|
||||
def get_prompt(self, prompt_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Gibt einen Prompt anhand seiner ID zurück"""
|
||||
prompts = self.db.get_recordset("prompts", record_filter={"id": prompt_id})
|
||||
if prompts:
|
||||
return prompts[0]
|
||||
return None
|
||||
|
||||
def create_prompt(self, content: str, workspace_id: int) -> Dict[str, Any]:
|
||||
"""Erstellt einen neuen Prompt"""
|
||||
# Bestimme die nächste ID
|
||||
prompts = self.db.get_recordset("prompts")
|
||||
next_id = 1
|
||||
if prompts:
|
||||
next_id = max(prompt["id"] for prompt in prompts) + 1
|
||||
|
||||
prompt_data = {
|
||||
"id": next_id,
|
||||
"mandate_id": self.mandate_id,
|
||||
"user_id": self.user_id,
|
||||
"content": content,
|
||||
"workspace_id": workspace_id,
|
||||
"created_at": self._get_current_timestamp()
|
||||
}
|
||||
|
||||
return self.db.record_create("prompts", prompt_data)
|
||||
|
||||
|
||||
# Singleton-Factory für LucyDOMInterface-Instanzen pro Kontext
|
||||
_lucydom_interfaces = {}
|
||||
|
||||
def get_lucydom_interface(mandate_id: int, user_id: int) -> LucyDOMInterface:
|
||||
"""
|
||||
Gibt eine LucyDOMInterface-Instanz für den angegebenen Kontext zurück.
|
||||
Wiederverwendet bestehende Instanzen.
|
||||
"""
|
||||
context_key = f"{mandate_id}_{user_id}"
|
||||
if context_key not in _lucydom_interfaces:
|
||||
_lucydom_interfaces[context_key] = LucyDOMInterface(mandate_id, user_id)
|
||||
return _lucydom_interfaces[context_key]
|
||||
82
gwserver/model_gateway.py
Normal file
82
gwserver/model_gateway.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
from pydantic import BaseModel, Field
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Label(BaseModel):
|
||||
"""Label für ein Attribut oder eine Klasse mit Unterstützung für mehrere Sprachen"""
|
||||
default: str
|
||||
translations: Dict[str, str] = {}
|
||||
|
||||
def get_label(self, language: str = None):
|
||||
"""Gibt das Label in der angegebenen Sprache zurück, oder den Standardwert wenn nicht verfügbar"""
|
||||
if language and language in self.translations:
|
||||
return self.translations[language]
|
||||
return self.default
|
||||
|
||||
|
||||
class Mandate(BaseModel):
|
||||
"""Datenmodell für einen Mandanten"""
|
||||
id: int = Field(description="Eindeutige ID des Mandanten")
|
||||
name: str = Field(description="Name des Mandanten")
|
||||
language: str = Field(description="Standardsprache des Mandanten")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Mandant", translations={"en": "Mandate", "fr": "Mandat"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
"""Datenmodell für einen Benutzer"""
|
||||
id: int = Field(description="Eindeutige ID des Benutzers")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
username: str = Field(description="Benutzername für die Anmeldung")
|
||||
email: Optional[str] = Field(None, description="E-Mail-Adresse 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")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Benutzer", translations={"en": "User", "fr": "Utilisateur"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"mandate_id": Label(default="Mandanten-ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
|
||||
"username": Label(default="Benutzername", translations={"en": "Username", "fr": "Nom d'utilisateur"}),
|
||||
"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"}),
|
||||
"disabled": Label(default="Deaktiviert", translations={"en": "Disabled", "fr": "Désactivé"}),
|
||||
"language": Label(default="Sprache", translations={"en": "Language", "fr": "Langue"})
|
||||
}
|
||||
|
||||
|
||||
class UserInDB(User):
|
||||
"""Erweiterte Benutzerklasse mit Passwort-Hash"""
|
||||
hashed_password: str = Field(description="Hash des Benutzerpassworts")
|
||||
|
||||
# Zusätzliches Label für das Passwort-Feld
|
||||
field_labels: Dict[str, Label] = {
|
||||
**User.field_labels,
|
||||
"hashed_password": Label(default="Passwort-Hash", translations={"en": "Password hash", "fr": "Hachage de mot de passe"})
|
||||
}
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
"""Datenmodell für ein Authentifizierungstoken"""
|
||||
access_token: str = Field(description="Das ausgestellte Zugriffstoken")
|
||||
token_type: str = Field(description="Typ des Tokens (meist 'bearer')")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Token", translations={"en": "Token", "fr": "Jeton"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"access_token": Label(default="Zugriffstoken", translations={"en": "Access token", "fr": "Jeton d'accès"}),
|
||||
"token_type": Label(default="Token-Typ", translations={"en": "Token type", "fr": "Type de jeton"})
|
||||
}
|
||||
274
gwserver/model_lucydom.py
Normal file
274
gwserver/model_lucydom.py
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
from pydantic import BaseModel, Field
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Label(BaseModel):
|
||||
"""Label für ein Attribut oder eine Klasse mit Unterstützung für mehrere Sprachen"""
|
||||
default: str
|
||||
translations: Dict[str, str] = {}
|
||||
|
||||
def get_label(self, language: str = None):
|
||||
"""Gibt das Label in der angegebenen Sprache zurück, oder den Standardwert wenn nicht verfügbar"""
|
||||
if language and language in self.translations:
|
||||
return self.translations[language]
|
||||
return self.default
|
||||
|
||||
|
||||
class Agent(BaseModel):
|
||||
"""Datenmodell für einen Agenten"""
|
||||
id: int = Field(description="Eindeutige ID des Agenten")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
name: str = Field(description="Name des Agenten")
|
||||
type: str = Field(description="Typ des Agenten")
|
||||
workspace_id: int = Field(description="ID des zugehörigen Workspaces")
|
||||
capabilities: List[str] = Field(default=[], description="Fähigkeiten des Agenten")
|
||||
description: Optional[str] = Field(None, description="Beschreibung des Agenten")
|
||||
instructions: Optional[str] = Field(None, description="Anweisungen für den Agenten")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Agent", translations={"en": "Agent", "fr": "Agent"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"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"}),
|
||||
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
|
||||
"type": Label(default="Typ", translations={"en": "Type", "fr": "Type"}),
|
||||
"workspace_id": Label(default="Workspace-ID", translations={"en": "Workspace ID", "fr": "ID d'espace de travail"}),
|
||||
"capabilities": Label(default="Fähigkeiten", translations={"en": "Capabilities", "fr": "Capacités"}),
|
||||
"description": Label(default="Beschreibung", translations={"en": "Description", "fr": "Description"}),
|
||||
"instructions": Label(default="Anweisungen", translations={"en": "Instructions", "fr": "Instructions"})
|
||||
}
|
||||
|
||||
|
||||
class DataObject(BaseModel):
|
||||
"""Datenmodell für ein Datenobjekt"""
|
||||
id: int = Field(description="Eindeutige ID des Datenobjekts")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
name: str = Field(description="Name des Datenobjekts")
|
||||
type: str = Field(description="Typ des Datenobjekts ('document', 'image', etc.)")
|
||||
size: Optional[str] = Field(None, description="Größe des Datenobjekts")
|
||||
upload_date: Optional[str] = Field(None, description="Datum des Hochladens")
|
||||
content_type: Optional[str] = Field(None, description="Content-Type des Datenobjekts")
|
||||
path: Optional[str] = Field(None, description="Pfad zum Datenobjekt")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Datenobjekt", translations={"en": "Data Object", "fr": "Objet de données"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"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"}),
|
||||
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
|
||||
"type": Label(default="Typ", translations={"en": "Type", "fr": "Type"}),
|
||||
"size": Label(default="Größe", translations={"en": "Size", "fr": "Taille"}),
|
||||
"upload_date": Label(default="Upload-Datum", translations={"en": "Upload date", "fr": "Date de téléchargement"}),
|
||||
"content_type": Label(default="Content-Type", translations={"en": "Content type", "fr": "Type de contenu"}),
|
||||
"path": Label(default="Pfad", translations={"en": "Path", "fr": "Chemin"})
|
||||
}
|
||||
|
||||
|
||||
class Prompt(BaseModel):
|
||||
"""Datenmodell für einen Prompt"""
|
||||
id: int = Field(description="Eindeutige ID des Prompts")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
content: str = Field(description="Inhalt des Prompts")
|
||||
created_at: str = Field(description="Erstellungszeitpunkt")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Prompt", translations={"en": "Prompt", "fr": "Invite"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"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"}),
|
||||
"content": Label(default="Inhalt", translations={"en": "Content", "fr": "Contenu"}),
|
||||
"created_at": Label(default="Erstellt am", translations={"en": "Created at", "fr": "Créé le"})
|
||||
}
|
||||
|
||||
|
||||
class Workspace(BaseModel):
|
||||
"""Datenmodell für einen Workspace"""
|
||||
id: int = Field(description="Eindeutige ID des Workspaces")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
name: str = Field(description="Name des Workspaces")
|
||||
created_at: str = Field(description="Erstellungszeitpunkt")
|
||||
prompts: List[Prompt] = Field(default=[], description="Liste der Prompts im Workspace")
|
||||
agents: List[Agent] = Field(default=[], description="Liste der Agenten im Workspace")
|
||||
dataObjectReferences: List[int] = Field(default=[], description="Referenzen zu Datenobjekten")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Workspace", translations={"en": "Workspace", "fr": "Espace de travail"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"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"}),
|
||||
"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"}),
|
||||
"agents": Label(default="Agenten", translations={"en": "Agents", "fr": "Agents"}),
|
||||
"dataObjectReferences": Label(default="Datenobjekte", translations={"en": "Data Objects", "fr": "Objets de données"})
|
||||
}
|
||||
|
||||
|
||||
class WorkflowRequest(BaseModel):
|
||||
"""Anforderung zur Ausführung eines Workflows"""
|
||||
id: int = Field(description="Eindeutige ID der Anforderung")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
workspace_id: int = Field(description="ID des zugehörigen Workspaces")
|
||||
prompt: str = Field(description="Zu verwendender Prompt")
|
||||
agents: List[int] = Field(description="Liste von Agent-IDs")
|
||||
files: List[int] = Field(description="Liste von Datei-IDs")
|
||||
workflow_name: Optional[str] = Field(None, description="Name des Workflows")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Workflow-Anforderung", translations={"en": "Workflow Request", "fr": "Demande de workflow"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"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"}),
|
||||
"workspace_id": Label(default="Workspace-ID", translations={"en": "Workspace ID", "fr": "ID d'espace de travail"}),
|
||||
"prompt": Label(default="Prompt", translations={"en": "Prompt", "fr": "Invite"}),
|
||||
"agents": Label(default="Agenten", translations={"en": "Agents", "fr": "Agents"}),
|
||||
"files": Label(default="Dateien", translations={"en": "Files", "fr": "Fichiers"}),
|
||||
"workflow_name": Label(default="Workflow-Name", translations={"en": "Workflow name", "fr": "Nom du workflow"})
|
||||
}
|
||||
|
||||
|
||||
class WorkflowResponse(BaseModel):
|
||||
"""Antwort nach dem Start eines Workflows"""
|
||||
workflow_id: int = Field(description="ID des gestarteten Workflows")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
status: str = Field(description="Status des Workflows")
|
||||
message: str = Field(description="Statusnachricht")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Workflow-Antwort", translations={"en": "Workflow Response", "fr": "Réponse de workflow"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"workflow_id": Label(default="Workflow-ID", translations={"en": "Workflow ID", "fr": "ID de workflow"}),
|
||||
"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"}),
|
||||
"status": Label(default="Status", translations={"en": "Status", "fr": "État"}),
|
||||
"message": Label(default="Nachricht", translations={"en": "Message", "fr": "Message"})
|
||||
}
|
||||
|
||||
|
||||
class LogEntry(BaseModel):
|
||||
"""Protokolleintrag während der Workflow-Ausführung"""
|
||||
id: int = Field(description="Eindeutige ID des Protokolleintrags")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
message: str = Field(description="Protokollnachricht")
|
||||
type: str = Field(description="Typ des Eintrags ('info', 'error', 'start', 'complete', 'success')")
|
||||
timestamp: str = Field(description="Zeitstempel des Eintrags")
|
||||
agent_id: Optional[int] = Field(None, description="ID des zugehörigen Agenten")
|
||||
agent_name: Optional[str] = Field(None, description="Name des zugehörigen Agenten")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Protokolleintrag", translations={"en": "Log Entry", "fr": "Entrée de journal"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"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"}),
|
||||
"message": Label(default="Nachricht", translations={"en": "Message", "fr": "Message"}),
|
||||
"type": Label(default="Typ", translations={"en": "Type", "fr": "Type"}),
|
||||
"timestamp": Label(default="Zeitstempel", translations={"en": "Timestamp", "fr": "Horodatage"}),
|
||||
"agent_id": Label(default="Agent-ID", translations={"en": "Agent ID", "fr": "ID d'agent"}),
|
||||
"agent_name": Label(default="Agent-Name", translations={"en": "Agent name", "fr": "Nom d'agent"})
|
||||
}
|
||||
|
||||
|
||||
class Result(BaseModel):
|
||||
"""Ergebnis eines Agenten während der Workflow-Ausführung"""
|
||||
id: int = Field(description="Eindeutige ID des Ergebnisses")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
title: str = Field(description="Titel des Ergebnisses")
|
||||
agent_id: int = Field(description="ID des zugehörigen Agenten")
|
||||
agent_name: str = Field(description="Name des zugehörigen Agenten")
|
||||
type: str = Field(description="Typ des Ergebnisses ('text', 'chart', 'image', etc.)")
|
||||
content: str = Field(description="Inhalt des Ergebnisses")
|
||||
timestamp: str = Field(description="Zeitstempel der Erstellung")
|
||||
metadata: Optional[Dict[str, Any]] = Field(None, description="Zusätzliche Metadaten")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Ergebnis", translations={"en": "Result", "fr": "Résultat"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"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"}),
|
||||
"title": Label(default="Titel", translations={"en": "Title", "fr": "Titre"}),
|
||||
"agent_id": Label(default="Agent-ID", translations={"en": "Agent ID", "fr": "ID d'agent"}),
|
||||
"agent_name": Label(default="Agent-Name", translations={"en": "Agent name", "fr": "Nom d'agent"}),
|
||||
"type": Label(default="Typ", translations={"en": "Type", "fr": "Type"}),
|
||||
"content": Label(default="Inhalt", translations={"en": "Content", "fr": "Contenu"}),
|
||||
"timestamp": Label(default="Zeitstempel", translations={"en": "Timestamp", "fr": "Horodatage"}),
|
||||
"metadata": Label(default="Metadaten", translations={"en": "Metadata", "fr": "Métadonnées"})
|
||||
}
|
||||
|
||||
|
||||
class WorkflowStatus(BaseModel):
|
||||
"""Status eines Workflows"""
|
||||
id: int = Field(description="Eindeutige ID des Workflow-Status")
|
||||
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
|
||||
user_id: int = Field(description="ID des Erstellers")
|
||||
name: Optional[str] = Field(None, description="Name des Workflows")
|
||||
status: str = Field(description="Status des Workflows ('running', 'completed', 'failed')")
|
||||
progress: float = Field(description="Fortschritt (0.0 - 1.0)")
|
||||
started_at: str = Field(description="Startzeitpunkt")
|
||||
completed_at: Optional[str] = Field(None, description="Abschlusszeitpunkt")
|
||||
agent_statuses: Optional[Dict[str, str]] = Field(None, description="Status der einzelnen Agenten")
|
||||
|
||||
label: Label = Field(
|
||||
default=Label(default="Workflow-Status", translations={"en": "Workflow Status", "fr": "État du workflow"}),
|
||||
description="Label für die Klasse"
|
||||
)
|
||||
|
||||
# Labels für Attribute
|
||||
field_labels: Dict[str, Label] = {
|
||||
"id": Label(default="ID", translations={}),
|
||||
"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"}),
|
||||
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
|
||||
"status": Label(default="Status", translations={"en": "Status", "fr": "État"}),
|
||||
"progress": Label(default="Fortschritt", translations={"en": "Progress", "fr": "Progrès"}),
|
||||
"started_at": Label(default="Gestartet am", translations={"en": "Started at", "fr": "Démarré le"}),
|
||||
"completed_at": Label(default="Abgeschlossen am", translations={"en": "Completed at", "fr": "Terminé le"}),
|
||||
"agent_statuses": Label(default="Agent-Status", translations={"en": "Agent status", "fr": "État des agents"})
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
from pydantic import BaseModel, Field
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
|
||||
class Agent(BaseModel):
|
||||
"""Datenmodell für einen Agenten"""
|
||||
id: str
|
||||
name: str
|
||||
type: str
|
||||
workspace_id: str
|
||||
capabilities: List[str] = []
|
||||
description: Optional[str] = None
|
||||
instructions: Optional[str] = None
|
||||
|
||||
class DataObject(BaseModel):
|
||||
"""Datenmodell für ein Datenobjekt"""
|
||||
id: str
|
||||
name: str
|
||||
type: str # 'document', 'image', etc.
|
||||
size: Optional[str] = None
|
||||
upload_date: Optional[str] = None
|
||||
content_type: Optional[str] = None
|
||||
path: Optional[str] = None
|
||||
|
||||
class Prompt(BaseModel):
|
||||
"""Datenmodell für einen Prompt"""
|
||||
id: str
|
||||
content: str
|
||||
created_at: str
|
||||
|
||||
class Workspace(BaseModel):
|
||||
"""Datenmodell für einen Workspace"""
|
||||
id: str
|
||||
name: str
|
||||
created_at: str
|
||||
prompts: List[Prompt] = []
|
||||
agents: List[Agent] = []
|
||||
dataObjectReferences: List[str] = []
|
||||
|
||||
class WorkflowRequest(BaseModel):
|
||||
"""Anforderung zur Ausführung eines Workflows"""
|
||||
workspace_id: str
|
||||
prompt: str
|
||||
agents: List[str] # Liste von Agent-IDs
|
||||
files: List[str] # Liste von Datei-IDs
|
||||
workflow_name: Optional[str] = None
|
||||
|
||||
class WorkflowResponse(BaseModel):
|
||||
"""Antwort nach dem Start eines Workflows"""
|
||||
workflow_id: str
|
||||
status: str
|
||||
message: str
|
||||
|
||||
class LogEntry(BaseModel):
|
||||
"""Protokolleintrag während der Workflow-Ausführung"""
|
||||
id: str
|
||||
message: str
|
||||
type: str # 'info', 'error', 'start', 'complete', 'success'
|
||||
timestamp: str
|
||||
agent_id: Optional[str] = None
|
||||
agent_name: Optional[str] = None
|
||||
|
||||
class Result(BaseModel):
|
||||
"""Ergebnis eines Agenten während der Workflow-Ausführung"""
|
||||
id: str
|
||||
title: str
|
||||
agent_id: str
|
||||
agent_name: str
|
||||
type: str # 'text', 'chart', 'image', etc.
|
||||
content: str
|
||||
timestamp: str
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
class WorkflowStatus(BaseModel):
|
||||
"""Status eines Workflows"""
|
||||
id: str
|
||||
name: Optional[str] = None
|
||||
status: str # 'running', 'completed', 'failed'
|
||||
progress: float # 0.0 - 1.0
|
||||
started_at: str
|
||||
completed_at: Optional[str] = None
|
||||
agent_statuses: Optional[Dict[str, str]] = None
|
||||
122
gwserver/specification.txt
Normal file
122
gwserver/specification.txt
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
der gateway funktioniert noch nicht ganz.
|
||||
kannst mir bitte die module prüfen und besser stukturieren?
|
||||
|
||||
Diese anforderungen und das setting der dateien:
|
||||
|
||||
models.py: die datei umbenennen in "model_lucydom.py"
|
||||
- die class "User", "UserInDB", "Token" in der datei entfernen und in eine separate datei "model_gateway.py" auslagern.
|
||||
- alle datentypen-definitionen sind hier, abschliessend und unabhängig vom datenbanksystem.
|
||||
- alle ID's sind long-Zahlen, keine Texte
|
||||
- bei jeder class und bei jedem attribut einer class ein label ergänzen, was der name des attributes bzw. der class ist, wenn dies in einem formular abgefragt wird. das label soll einen defaultwert haben und pro sprache gesetzt werden können.
|
||||
- alle objekte mandantenfähig machen, d.h. bei jedem Objekt die Attribute "mandate_id" und "user_id" ergänzen.
|
||||
|
||||
model_gateway.py:
|
||||
- alle datentypen-definitionen sind hier, abschliessend und unabhängig vom datenbanksystem.
|
||||
- alle ID's sind long-Zahlen, keine Texte
|
||||
- bei jeder class und bei jedem attribut einer class ein label ergänzen, was der name des attributes bzw. der class ist, wenn dies in einem formular abgefragt wird. das label soll einen defaultwert haben und pro sprache gesetzt werden können.
|
||||
- Die class "Mandate" mit den Attributen (id,name,language) ergänzen
|
||||
- Bei der class "User" die "id" und "mandate_id" und "language" ergänzen
|
||||
- alle objekte mandantenfähig machen, d.h. bei jedem Objekt die Attribute "mandate_id" und "user_id" ergänzen.
|
||||
|
||||
database.py aufteilen in 2 files "connector_db_json.py" und "interface_lucydom.py".
|
||||
|
||||
connector_db_json.py: Ein erster Konnektor von zukünftig weiteren Konnektoren
|
||||
1. Parameter, welche übergeben werden:
|
||||
- DB_Folder, DB_USER und DB_APIKEY
|
||||
- Kontextparamter für "mandate_id" und "user_id", welche nicht null sein dürfen.
|
||||
- Die aktuelle JSON-Datenbank im Folder DB_Folder einbinden und so übernehmen, wie sie ist. Falls der Folder fehlt, diesen erstellen.
|
||||
2. Der Konnector "db" wird als Objekt zur verfügung gestellt.
|
||||
3. Es werden diese generischen Methoden im Objekt "db" zur Verfügung gestellt. jede abfrage filtert automatisch die datensätze auf die Kontextparamter "mandate_id" und "user_id", sofern diese Parameter in einem Datensatz nicht null oder "" sind.
|
||||
- get_tables(optional filterkriterien): liste aller tabellen
|
||||
- get_fields(table, optional filterkriterien): liste aller attribute einer tabelle
|
||||
- get_schema(table, language, optional filterkriterien): objekt aller attribute einer tabelle mit ihrem Datentyp und dem Label in der entsprechenden Sprache. Ohne Sprache Angabe wird der Default Wert als Label genommen
|
||||
- get_recordset(table, optional filterkriterien für fields, optional filterkriterien für records): liefert das entsprechende datenobjekt mit den Datensätzen
|
||||
- record_create(table,json with attributes): ergänzt einen Datensatz im Kontext "mandate_id", alle attribute, welche nicht im "json with attributes" drin sind, werden auf die standardwerte gemäss dem models.py gesetzt
|
||||
- record_delete: löscht einen Datensatz, aber nur wenn es im Kontext "mandate_id" ist, sonst Verweigerung "Not your mandate"
|
||||
- record_modify: ändert einen Datensatz, aber nur wenn er im Kontext "mandate_id" ist, sonst Verweigerung "Not your mandate"
|
||||
|
||||
interface_lucydom.py: Ein Interface zum Gateway, es werden weitere Interfaces folgen. Das Interface macht dies:
|
||||
1. Die Datenbank mit diesen Parametern einbinden:
|
||||
- Connector "connector_db_json.py"
|
||||
- Datenbank "/data_lucydom"
|
||||
- Datenmodell "model_lucydom.py"
|
||||
2. Das Objekt "db" kann nun genutzt werden
|
||||
3. initialisierung der Datenbank, falls sie nicht existiert, aber nur die minimal nötigen Objekte: Der "Default Workspace" in "workspaces"
|
||||
|
||||
interface_gateway.py: Ein Interface zum Gateway, es werden weitere Interfaces folgen. Das Interface macht dies:
|
||||
1. Die Datenbank mit diesen Parametern einbinden:
|
||||
- Connector "connector_db_json.py"
|
||||
- Datenbank "/data_gateway"
|
||||
- Datenmodell "model_gateway.py"
|
||||
2. Das Objekt "db" kann nun genutzt werden
|
||||
3. initialisierung der Datenbank, falls sie nicht existiert, aber nur die minimal nötigen Objekte: User "Admin", Mandate "Root"
|
||||
|
||||
app.py: Die Initialisierung klar strukturieren und die Endpunkte gemäss der neuen Struktur anpassen
|
||||
1. Teil: Interfaces einbinden.
|
||||
2. Alle nötigen Initialisierungen: diese sollen in den jeweiligen Interfaces drin sein, ausser die generischen Teile.
|
||||
3. Alle Access & Security Funktionen auslagern in "auth.py"
|
||||
4. Alle Token-Endpunkte komplett generisch halten und vereinfachen:
|
||||
- Dort keine Attributdefinitionen oder Feld-Listen reinnehmen. Wenn ein Modell angepasst wird, sollen hier keine Anpassungen nötig sein.
|
||||
- Die Abfragen und exceptions mit Hilfsfunktionen vereinfachen, sodass die Modellierung der Endpunkte für den Programmierer sehr einfach, übersichtlich und klar ist.
|
||||
- Tasks als Kommentare erfassen, was mit all diesen Aenderungen der Endpunkte im Frontend umgebaut werden muss.
|
||||
|
||||
|
||||
agent_service.py: Umbenennen in "interface_agentservice.py"
|
||||
- Bei allen Workflow-Endpunkten, welche nur von einem Interface Logik beziehen, die Logik im Interface integrieren und den Code beim Endpunkt vereinfachen.
|
||||
- Nur bei Endpunkten, welche Logik kombiniert von mehreren Interfaces benötigen, die Logik beim Endpunkt integrieren
|
||||
- Ziel soll es sein, dass die Endpunkte-Codestruktur maximal schlank und übersichtlich ist, also auch die Strukturierung und Gruppierung der Endpunkte
|
||||
|
||||
|
||||
|
||||
|
||||
Summary of the Restructuring
|
||||
I've completely restructured the gateway code according to your specifications. Here's what I've done:
|
||||
1. Model Files
|
||||
|
||||
model_gateway.py: Created this file for user, auth, and mandate models.
|
||||
model_lucydom.py: Renamed from models.py with all application-specific models.
|
||||
Enhanced both model files to include labels with multi-language support for all classes and attributes.
|
||||
Changed all IDs from string to long integers as requested.
|
||||
Made all objects tenant-capable by adding mandate_id and user_id to each model.
|
||||
|
||||
2. Database Connector
|
||||
|
||||
connector_db_json.py: Created a generic connector that provides standard database operations (get_tables, get_fields, get_schema, etc.).
|
||||
Added automatic filtering by mandate_id for data isolation.
|
||||
Added proper error handling for case when a user tries to access data from another mandate.
|
||||
|
||||
3. Interface Files
|
||||
|
||||
interface_lucydom.py: Created an interface for the LucyDOM application logic.
|
||||
interface_gateway.py: Created an interface for the authentication and user management.
|
||||
interface_agentservice.py: Renamed from agent_service.py with better structure.
|
||||
Added proper context handling (mandate_id and user_id) throughout all interfaces.
|
||||
|
||||
4. Authentication
|
||||
|
||||
auth.py: Centralized all authentication logic from app.py into this file.
|
||||
Added better token handling with mandate context.
|
||||
|
||||
5. Main Application (app.py)
|
||||
|
||||
Completely restructured to use the new interfaces.
|
||||
Simplified endpoint implementations by leveraging the interface functionality.
|
||||
Made all endpoints context-aware (tenant-aware).
|
||||
Added new endpoints for mandate management.
|
||||
|
||||
6. Overall Structure
|
||||
|
||||
Improved the code structure to be more modular and maintainable.
|
||||
Made the system properly tenant-aware at all levels.
|
||||
Enhanced error handling and logging throughout the codebase.
|
||||
Made database initialization logic more robust.
|
||||
|
||||
Key Improvements
|
||||
|
||||
Better Separation of Concerns: Clear distinction between models, data access, and business logic.
|
||||
Multi-Tenancy: All operations now respect tenant boundaries.
|
||||
Enhanced Type Safety: More explicit typing and model definitions.
|
||||
Improved Maintainability: Modular code structure makes it easier to maintain and extend.
|
||||
Centralized Authentication: All auth logic in one place for better security.
|
||||
|
||||
This structure will allow for easier future expansion and maintenance of the system.
|
||||
|
|
@ -1,21 +1,66 @@
|
|||
fastapi==0.103.1
|
||||
uvicorn==0.23.2
|
||||
python-multipart==0.0.6
|
||||
pydantic==1.10.8
|
||||
typing-extensions==4.8.0
|
||||
python-dateutil==2.8.2
|
||||
six==1.16.0
|
||||
starlette==0.27.0
|
||||
anyio==3.7.1
|
||||
idna==3.4
|
||||
sniffio==1.3.0
|
||||
httpx==0.24.1
|
||||
fastapi==0.103.1
|
||||
pandas==2.0.3
|
||||
openpyxl==3.1.2
|
||||
PyPDF2==3.0.1
|
||||
beautifulsoup4==4.12.2
|
||||
requests==2.31.0
|
||||
python-jose[cryptography]
|
||||
passlib==1.7.4
|
||||
bcrypt==3.2.0
|
||||
# === Core Dependencies ===
|
||||
|
||||
# Web Framework and API
|
||||
fastapi>=0.95.0
|
||||
uvicorn>=0.21.1
|
||||
pydantic>=1.10.7
|
||||
starlette>=0.26.1
|
||||
python-multipart>=0.0.6 # For file uploads
|
||||
email-validator>=2.0.0 # For email validation
|
||||
|
||||
# Authentication and Security
|
||||
python-jose>=3.3.0 # For JWT
|
||||
passlib>=1.7.4 # For password hashing
|
||||
bcrypt>=4.0.1 # For password hashing
|
||||
cryptography>=40.0.2 # Required by jose
|
||||
|
||||
# Utilities
|
||||
typing-extensions>=4.5.0
|
||||
python-dateutil>=2.8.2
|
||||
uuid>=1.30
|
||||
tqdm>=4.65.0 # For progress tracking
|
||||
pytz>=2023.3 # For timezone handling
|
||||
|
||||
# === Development and Testing Dependencies ===
|
||||
|
||||
pytest>=7.3.1
|
||||
pytest-asyncio>=0.21.0
|
||||
black>=23.3.0 # Code formatting
|
||||
isort>=5.12.0 # Import sorting
|
||||
flake8>=6.0.0 # Linting
|
||||
mypy>=1.2.0 # Type checking
|
||||
|
||||
# === Database Interface Dependencies ===
|
||||
|
||||
# JSON DB Connector
|
||||
jsonpickle>=3.0.1 # For advanced JSON serialization
|
||||
|
||||
# === LucyDOM Interface Dependencies ===
|
||||
|
||||
# DataFrame and Data Processing
|
||||
pandas>=2.0.0
|
||||
numpy>=1.24.2
|
||||
openpyxl>=3.1.2 # For Excel file support
|
||||
xlrd>=2.0.1 # For legacy Excel file support
|
||||
PyPDF2>=3.0.1 # For PDF file support
|
||||
|
||||
# === Agent Service Interface Dependencies ===
|
||||
# HTTP Client and Web Scraping
|
||||
httpx>=0.24.0 # Async HTTP client
|
||||
requests>=2.28.2 # Synchronous HTTP client
|
||||
beautifulsoup4>=4.12.2 # For web scraping
|
||||
lxml>=4.9.2 # For faster HTML parsing
|
||||
html5lib>=1.1 # Alternative HTML parser
|
||||
aiofiles>=23.1.0 # Async file operations
|
||||
|
||||
# AI and NLP
|
||||
openai>=0.27.4 # OpenAI API client
|
||||
nltk>=3.8.1 # Natural Language Toolkit
|
||||
scikit-learn>=1.2.2 # For machine learning utilities
|
||||
spacy>=3.5.2 # For advanced NLP
|
||||
|
||||
# Visualization (for generating reports)
|
||||
matplotlib>=3.7.1
|
||||
seaborn>=0.12.2
|
||||
plotly>=5.14.1
|
||||
kaleido>=0.2.1 # For static image export
|
||||
|
|
|
|||
Loading…
Reference in a new issue