diff --git a/gwserver/app.py b/gwserver/app.py index c181521e..e114ba64 100644 --- a/gwserver/app.py +++ b/gwserver/app.py @@ -60,28 +60,22 @@ app.add_middleware( allow_headers=["*"], ) -# Verzeichnis für hochgeladene Dateien erstellen -UPLOAD_DIR = os.path.join(os.getcwd(), "uploads") -os.makedirs(UPLOAD_DIR, exist_ok=True) - -# Komponenten des Frontends -app.mount("/static", StaticFiles(directory="static"), name="static") - - # API - ENDPUNKTE +# Statischer Folder für Frontend +os.makedirs(os.path.join(os.getcwd(), "static"), exist_ok=True) +app.mount("/static", StaticFiles(directory="static"), name="static") + +# Generelle Elements @app.get("/", tags=["General"]) async def root(): """API-Statusendpunkt""" return {"status": "online", "message": "Data Platform API ist aktiv"} - -# Test Element -@app.get("/api/test", tags=["Test"]) +@app.get("/api/test", tags=["General"]) async def get_test(): return "OK 1.0" - # Token-Endpunkt für Login @app.post("/token", response_model=Token) async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): @@ -265,7 +259,7 @@ async def upload_file( # 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}") + file_path = os.path.join(get_agent_service(mandate_id, user_id).upload_dir, f"{file_id}{file_ext}") # Datei speichern with open(file_path, "wb") as f: diff --git a/gwserver/auth.py b/gwserver/auth.py index 1b97d0af..8f032a4f 100644 --- a/gwserver/auth.py +++ b/gwserver/auth.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Optional, Dict, Any from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer @@ -6,12 +6,15 @@ from jose import JWTError, jwt import logging from interface_gateway import get_gateway_interface from model_gateway import User, Token +import configload -# Konfigurationsvariablen -SECRET_KEY = "dein-geheimer-schlüssel" # In Produktion aus Umgebungsvariablen laden! -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 600 +# Get Config Data +config=configload.load_config() +SECRET_KEY = config.get('Access', 'SECRET_KEY') +ALGORITHM = config.get('Access', 'ALGORITHM') +ACCESS_TOKEN_EXPIRE_MINUTES = int(config.get('Access', 'ACCESS_TOKEN_EXPIRE_MINUTES')) + # OAuth2 Setup oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -34,9 +37,9 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) - to_encode = data.copy() if expires_delta: - expire = datetime.utcnow() + expires_delta + expire = datetime.now(timezone.utc) + expires_delta else: - expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) diff --git a/gwserver/config.ini b/gwserver/config.ini index 90ffd97e..9c98fc39 100644 --- a/gwserver/config.ini +++ b/gwserver/config.ini @@ -1,13 +1,18 @@ -[OpenAI] -API_KEY = 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 -API_URL = https://api.openai.com/v1/chat/completions -MODEL_NAME = gpt-4o - [Application] DEBUG = True UPLOAD_DIR = ./uploads RESULTS_DIR = ./results +[Access] +SECRET_KEY = dein-geheimer-schlüssel +ALGORITHM = HS256 +ACCESS_TOKEN_EXPIRE_MINUTES = 300 + +[OpenAI] +API_KEY = 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 +API_URL = https://api.openai.com/v1/chat/completions +MODEL_NAME = gpt-4o + [WebScraping] TIMEOUT = 10 MAX_URLS = 3 diff --git a/gwserver/configload.py b/gwserver/configload.py new file mode 100644 index 00000000..74792011 --- /dev/null +++ b/gwserver/configload.py @@ -0,0 +1,11 @@ +import configparser +import os + +# Konfiguration aus config.ini laden +def load_config(): + config = configparser.ConfigParser() + config_path = os.path.join(os.path.dirname(__file__), 'config.ini') + if not os.path.exists(config_path): + exit("No config file") + config.read(config_path) + return config diff --git a/gwserver/connector_db_json.py b/gwserver/connector_db_json.py index 864a95e4..a152029c 100644 --- a/gwserver/connector_db_json.py +++ b/gwserver/connector_db_json.py @@ -56,17 +56,20 @@ class JSONDatabaseConnector: # Wenn die Tabelle bereits im Cache ist, verwende den Cache if table in self._tables_cache: + logger.info(f"Lade Tabelle {table} aus Cache") return self._tables_cache[table] # Ansonsten lade die Datei try: if os.path.exists(path): + logger.info(f"Lade Tabelle {table} aus JSON {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 + logger.info(f"Neue Tabelle {table}") self._tables_cache[table] = [] self._save_table(table, []) return [] diff --git a/gwserver/data_gateway/mandates.json b/gwserver/data_gateway/mandates.json new file mode 100644 index 00000000..d251ec2c --- /dev/null +++ b/gwserver/data_gateway/mandates.json @@ -0,0 +1,9 @@ +[ + { + "id": 1, + "name": "Root", + "language": "de", + "mandate_id": 1, + "user_id": 1 + } +] \ No newline at end of file diff --git a/gwserver/data_gateway/users.json b/gwserver/data_gateway/users.json new file mode 100644 index 00000000..9d8e710a --- /dev/null +++ b/gwserver/data_gateway/users.json @@ -0,0 +1,13 @@ +[ + { + "id": 1, + "mandate_id": 1, + "username": "admin", + "email": "admin@example.com", + "full_name": "Administrator", + "disabled": false, + "language": "de", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$tJYSAmDs/T+HEALg3Nsbww$2HnCW69CkCkk82fgRhzjYtdNu3b9G7NmSJZmbUQVDOs", + "user_id": 1 + } +] \ No newline at end of file diff --git a/gwserver/interface_agentservice.py b/gwserver/interface_agentservice.py index f5f1f51f..77656f8c 100644 --- a/gwserver/interface_agentservice.py +++ b/gwserver/interface_agentservice.py @@ -3,84 +3,47 @@ import uuid import os import json import logging -import time -import configparser 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 +import configload as configload # Logger konfigurieren logger = logging.getLogger(__name__) -# Konfiguration aus config.ini laden -def load_config(): - config = configparser.ConfigParser() - config_path = os.path.join(os.path.dirname(__file__), 'config.ini') - - # Standardkonfiguration - default_config = { +# Konfigurationsdaten laden +def load_config_data(): + config=configload.load_config() + result = { "openai": { - "api_key": "VOKEY", - "api_url": "https://api.openai.com/v1/chat/completions", - "model_name": "gpt-4o" + "api_key": config.get('OpenAI', 'API_KEY'), + "api_url": config.get('OpenAI', 'API_URL'), + "model_name": config.get('OpenAI', 'MODEL_NAME') }, "application": { - "debug": "True", - "upload_dir": "./uploads", - "results_dir": "./results" + "debug": config.get('Application', 'DEBUG'), + "upload_dir": config.get('Application', 'UPLOAD_DIR'), + "results_dir": config.get('Application', 'RESULTS_DIR') }, "webscraping": { - "timeout": "10", - "max_urls": "3", - "max_content_length": "3000", - "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + "timeout": config.get('WebScraping', 'TIMEOUT'), + "max_urls": config.get('WebScraping', 'MAX_URLS'), + "max_content_length": config.get('WebScraping', 'MAX_CONTENT_LENGTH'), + "user_agent": config.get('WebScraping', 'USER_AGENT') } } - - if not os.path.exists(config_path): - logger.warning(f"Konfigurationsdatei nicht gefunden: {config_path}") - return default_config - - try: - config.read(config_path) - - # Konfiguration aus der Datei lesen - result = { - "openai": { - "api_key": config.get('OpenAI', 'API_KEY', fallback=default_config["openai"]["api_key"]), - "api_url": config.get('OpenAI', 'API_URL', fallback=default_config["openai"]["api_url"]), - "model_name": config.get('OpenAI', 'MODEL_NAME', fallback=default_config["openai"]["model_name"]) - }, - "application": { - "debug": config.get('Application', 'DEBUG', fallback=default_config["application"]["debug"]), - "upload_dir": config.get('Application', 'UPLOAD_DIR', fallback=default_config["application"]["upload_dir"]), - "results_dir": config.get('Application', 'RESULTS_DIR', fallback=default_config["application"]["results_dir"]) - }, - "webscraping": { - "timeout": config.get('WebScraping', 'TIMEOUT', fallback=default_config["webscraping"]["timeout"]), - "max_urls": config.get('WebScraping', 'MAX_URLS', fallback=default_config["webscraping"]["max_urls"]), - "max_content_length": config.get('WebScraping', 'MAX_CONTENT_LENGTH', fallback=default_config["webscraping"]["max_content_length"]), - "user_agent": config.get('WebScraping', 'USER_AGENT', fallback=default_config["webscraping"]["user_agent"]) - } - } - - # Debug-Modus einstellen - if result["application"]["debug"].lower() == "true": - logging.basicConfig(level=logging.DEBUG) - logger.setLevel(logging.DEBUG) - logger.debug("Debug-Modus aktiviert") - - return result - except Exception as e: - logger.error(f"Fehler beim Laden der Konfiguration: {e}") - return default_config + # Debug-Modus einstellen + if result["application"]["debug"].lower() == "true": + logging.basicConfig(level=logging.DEBUG) + logger.setLevel(logging.DEBUG) + logger.debug("Debug-Modus aktiviert") + return result class AgentService: @@ -101,7 +64,7 @@ class AgentService: self.user_id = user_id # Konfiguration laden - self.config = load_config() + self.config = load_config_data() # Verzeichnisse aus der Konfiguration übernehmen self.results_dir = self.config["application"]["results_dir"] diff --git a/gwserver/interface_gateway.py b/gwserver/interface_gateway.py index c5663ae0..8fc726e2 100644 --- a/gwserver/interface_gateway.py +++ b/gwserver/interface_gateway.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) # Password-Hashing -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +pwd_context = CryptContext(schemes=["argon2"], deprecated="auto") class GatewayInterface: @@ -33,8 +33,9 @@ class GatewayInterface: self.user_id = user_id if user_id is not None else 1 # Admin-Benutzer als Standard # Datenverzeichnis - self.data_folder = "/data_gateway" + self.data_folder = "data_gateway" os.makedirs(self.data_folder, exist_ok=True) + logger.info("db for data_gateway atteached") # Datenmodell-Modul importieren try: diff --git a/gwserver/interface_lucydom.py b/gwserver/interface_lucydom.py index e3653b0b..6f5248dc 100644 --- a/gwserver/interface_lucydom.py +++ b/gwserver/interface_lucydom.py @@ -26,7 +26,7 @@ class LucyDOMInterface: self.user_id = user_id # Datenverzeichnis - self.data_folder = "/data_lucydom" + self.data_folder = "data_lucydom" os.makedirs(self.data_folder, exist_ok=True) # Datenmodell-Modul importieren diff --git a/gwserver/model_gateway.py b/gwserver/model_gateway.py index 7362decd..4ab16aa8 100644 --- a/gwserver/model_gateway.py +++ b/gwserver/model_gateway.py @@ -26,6 +26,12 @@ class Mandate(BaseModel): description="Label für die Klasse" ) + # Labels für Attribute + field_labels: Dict[str, Label] = { + "id": Label(default="ID", translations={}), + "name": Label(default="Name des Mandanten", translations={"en": "Mandate name", "fr": "Nom du mandat"}), + "language": Label(default="Sprache", translations={"en": "Language", "fr": "Langue"}) + } class User(BaseModel): """Datenmodell für einen Benutzer""" @@ -57,10 +63,14 @@ class User(BaseModel): class UserInDB(User): """Erweiterte Benutzerklasse mit Passwort-Hash""" hashed_password: str = Field(description="Hash des Benutzerpassworts") + + label: Label = Field( + default=Label(default="Benutzer Zugriff", translations={"en": "User Access", "fr": "Accès de l'utilisateur"}), + description="Label für die Klasse" + ) # 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"}) } diff --git a/requirements.txt b/requirements.txt index 181f6d01..d2b9ae5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ 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 +argon2-cffi # For password hashing cryptography>=40.0.2 # Required by jose # Utilities @@ -55,9 +55,9 @@ 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 +#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 diff --git a/start.sh b/start.sh index 7004af23..c8486531 100644 --- a/start.sh +++ b/start.sh @@ -1,21 +1,23 @@ #!/bin/bash -# Farben für die Ausgabe -GREEN='\033[0;32m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color +# Erkennen des Betriebssystems +case "$(uname -s)" in + CYGWIN*|MINGW*|MSYS*|Windows*) + OS="Windows" + VENV_PATH="gwserver/venv/Scripts" + ACTIVATE_CMD="$VENV_PATH/activate" + ;; + *) + OS="Unix" + VENV_PATH="gwserver/venv/bin" + ACTIVATE_CMD="$VENV_PATH/activate" + ;; +esac -echo -e "${GREEN}Data Platform - Multi-Agent Service${NC}" -echo -e "${BLUE}Startskript für gwserver${NC}" -echo "----------------------------------------" - -# Verzeichnisstruktur erstellen, falls sie nicht existiert -mkdir -p gwserver/data -mkdir -p gwserver/uploads -mkdir -p gwserver/results -mkdir -p gwserver/static +echo "Erkanntes Betriebssystem: $OS" # Prüfen, ob Python installiert ist +PYTHON_CMD="" if command -v python3 &>/dev/null; then PYTHON_CMD="python3" elif command -v python &>/dev/null; then @@ -35,7 +37,11 @@ fi # Virtuelle Umgebung aktivieren echo "Aktiviere virtuelle Umgebung..." -source gwserver/venv/bin/activate 2>/dev/null || . gwserver/venv/bin/activate +if [ "$OS" = "Windows" ]; then + source "$ACTIVATE_CMD" 2>/dev/null || . "$ACTIVATE_CMD" 2>/dev/null || echo "Warnung: Aktivierung der virtuellen Umgebung fehlgeschlagen. Fahre fort..." +else + source "$ACTIVATE_CMD" 2>/dev/null || . "$ACTIVATE_CMD" 2>/dev/null || echo "Warnung: Aktivierung der virtuellen Umgebung fehlgeschlagen. Fahre fort..." +fi # Abhängigkeiten installieren echo "Installiere Abhängigkeiten..." @@ -43,20 +49,38 @@ pip install -r requirements.txt # gwserver als Hintergrundprozess starten echo "Starte gwserver-Server..." -cd gwserver -uvicorn app:app --reload --host 0.0.0.0 --port 8000 & -GWSERVER_PID=$! -cd .. -echo "----------------------------------------" +# START CORE +cd gwserver +if [ "$OS" = "Windows" ]; then + # Windows: starte in einem separaten Prozess + start uvicorn app:app --reload --host 0.0.0.0 --port 8000 + # Keine PID-Erfassung nötig unter Windows mit 'start' + GWSERVER_PID="" +else + # Unix: starte im Hintergrund und erfasse PID + uvicorn app:app --reload --host 0.0.0.0 --port 8000 & + GWSERVER_PID=$! +fi +# END CORE + +cd .. echo "gwserver API läuft auf: http://localhost:8000" echo "API-Dokumentation: http://localhost:8000/docs" -echo -e "${BLUE}Drücke STRG+C, um Server zu beenden${NC}" +echo "Drücke STRG+C, um Server zu beenden" # Funktion zum Beenden der Server bei STRG+C cleanup() { - echo -e "\n${GREEN}Beende Server...${NC}" - kill $GWSERVER_PID + echo -e "\nBeende Server..." + if [ "$OS" = "Windows" ]; then + # Windows: Taskkill für uvicorn + taskkill //F //IM uvicorn.exe 2>/dev/null || echo "Konnte uvicorn nicht beenden" + else + # Unix: Verwende die erfasste PID + if [ -n "$GWSERVER_PID" ]; then + kill $GWSERVER_PID 2>/dev/null || echo "Konnte Prozess $GWSERVER_PID nicht beenden" + fi + fi echo "Server wurden beendet" exit 0 } @@ -65,4 +89,17 @@ cleanup() { trap cleanup SIGINT # Warten auf Benutzeraktion -wait \ No newline at end of file +if [ "$OS" = "Windows" ]; then + # Unter Windows: Warte auf Eingabe + read -p "Drücke Enter zum Beenden..." dummy + cleanup +else + # Unter Unix: Warte auf Prozess + if [ -n "$GWSERVER_PID" ]; then + wait $GWSERVER_PID + else + # Fallback: einfach warten + read -p "Drücke Enter zum Beenden..." dummy + cleanup + fi +fi \ No newline at end of file