diff --git a/gwserver/app.py b/gwserver/app.py index 571d66a2..28a019fc 100644 --- a/gwserver/app.py +++ b/gwserver/app.py @@ -1,8 +1,10 @@ -import uvicorn -from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query +from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, Response from fastapi.staticfiles import StaticFiles +from fastapi.security import OAuth2PasswordRequestForm + +import uvicorn from typing import List, Dict, Any, Optional import uuid import os @@ -10,6 +12,10 @@ 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 models import ( Workspace, @@ -21,8 +27,16 @@ from models import ( LogEntry, Result ) -from database import get_db, Database -from agent_service import AgentService, get_agent_service + +from auth import ( + ACCESS_TOKEN_EXPIRE_MINUTES, + authenticate_user, + create_access_token, + fake_users_db, + get_current_user +) + + # Konfiguration des Loggers logging.basicConfig( @@ -32,18 +46,17 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) -app = FastAPI(title="Data Platform API", description="Backend-API für die Multi-Agent Datenplattform") +app = FastAPI(title="PowerOn | Data Platform API", description="Backend-API für die Multi-Agent Platform von ValueOn AG") # CORS-Konfiguration für Frontend-Anfragen app.add_middleware( CORSMiddleware, - allow_origins=["*"], # In Produktion einschränken + allow_origins=["http://localhost:8080"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) - # Pfad zum Webparts-Verzeichnis WEBPARTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webparts") @@ -52,7 +65,9 @@ UPLOAD_DIR = os.path.join(os.getcwd(), "uploads") os.makedirs(UPLOAD_DIR, exist_ok=True) # Komponenten des Frontends -app.mount("/webparts", StaticFiles(directory="webparts"), name="webparts") +app.mount("/static", StaticFiles(directory="static"), name="static") + +# API - ENDPUNKTE @app.get("/", tags=["General"]) async def root(): @@ -64,9 +79,34 @@ async def root(): async def get_test(): return "OK 1.0" +# Token-Endpunkt +@app.post("/token", response_model=Dict[str, str]) +async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): + user = authenticate_user(fake_users_db, form_data.username, form_data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Ungültige Zugangsdaten", + headers={"WWW-Authenticate": "Bearer"}, + ) + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": user["username"]}, 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)): + return current_user + + # Workspace-Endpunkte @app.get("/api/workspaces", tags=["Workspaces"], response_model=List[Workspace]) -async def get_workspaces(db: Database = Depends(get_db)): +async def get_workspaces( + current_user = Depends(get_current_user), # <-- Authentifizierung hinzugefügt + db: Database = Depends(get_db) +): """Alle verfügbaren Workspaces abrufen""" return db.get_all_workspaces() @@ -187,6 +227,42 @@ async def upload_file( raise HTTPException(status_code=500, detail=f"Fehler beim Hochladen der Datei: {str(e)}") +@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) +): + """Löscht eine Datei""" + try: + # Hole die Datei aus der Datenbank + file = db.get_file(file_id) + if not file: + raise HTTPException(status_code=404, detail=f"Datei mit ID {file_id} nicht gefunden") + + # Prüfe, ob die physische Datei existiert + if "path" in file and os.path.exists(file["path"]): + try: + # Versuche, die physische Datei zu löschen + os.remove(file["path"]) + except Exception as e: + logger.warning(f"Konnte physische Datei nicht löschen: {e}") + + # Lösche die Datei aus der Datenbank + success = db.delete_file(file_id) + + if not success: + raise HTTPException(status_code=500, detail="Fehler beim Löschen der Datei aus der Datenbank") + + return {"success": True, "message": f"Datei '{file.get('name', 'unbekannt')}' wurde gelöscht"} + + except HTTPException: + raise + except Exception as e: + logger.error(f"Fehler beim Löschen der Datei: {e}") + raise HTTPException(status_code=500, detail=f"Fehler beim Löschen der Datei: {str(e)}") + + # 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)): diff --git a/gwserver/auth.py b/gwserver/auth.py new file mode 100644 index 00000000..d69f9bed --- /dev/null +++ b/gwserver/auth.py @@ -0,0 +1,70 @@ +from datetime import datetime, timedelta +from typing import Optional +from jose import JWTError, jwt +from passlib.context import CryptContext +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer + +# 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"), + } +} + +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 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 + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + 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)): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Ungültige Authentifizierungsdaten", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + except JWTError: + raise credentials_exception + user = get_user(fake_users_db, username) + if user is None: + raise credentials_exception + return user \ No newline at end of file diff --git a/gwserver/data/files.json b/gwserver/data/files.json index 547c68f6..79c93337 100644 --- a/gwserver/data/files.json +++ b/gwserver/data/files.json @@ -1,65 +1,11 @@ [ { - "id": "file_001", - "name": "Quartalsbericht Q1 2025.pdf", + "id": "0399d060-5beb-46e6-9c3c-830ae7a471cb", + "name": "Index.pdf", "type": "document", + "path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\gwserver\\uploads\\0399d060-5beb-46e6-9c3c-830ae7a471cb.pdf", "content_type": "application/pdf", - "size": 2500000, - "upload_date": "2025-03-14T00:25:25.076526", - "path": "uploads/dummy_file1.pdf" - }, - { - "id": "file_002", - "name": "Marktanalyse-Diagramm.png", - "type": "image", - "content_type": "image/png", - "size": 1150000, - "upload_date": "2025-03-14T00:25:25.076526", - "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, - "upload_date": "2025-03-14T00:25:25.076526", - "path": "uploads/dummy_file3.xlsx" - }, - { - "id": "77bb2097-7044-4267-aae6-f2ca1c119ab8", - "name": "11 PowerOn HLD - Journey.pdf", - "type": "document", - "path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\77bb2097-7044-4267-aae6-f2ca1c119ab8.pdf", - "content_type": "application/pdf", - "size": 56687, - "upload_date": "2025-03-14T00:25:55.641089" - }, - { - "id": "f066b3d6-7d59-4851-a8eb-de4334f4a64e", - "name": "11 PowerOn HLD - Journey.pdf", - "type": "document", - "path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\f066b3d6-7d59-4851-a8eb-de4334f4a64e.pdf", - "content_type": "application/pdf", - "size": 56687, - "upload_date": "2025-03-14T09:46:37.306858" - }, - { - "id": "bb36ca5e-cae0-441d-b59c-feb80ecf6d51", - "name": "11 PowerOn HLD - Journey.pdf", - "type": "document", - "path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\bb36ca5e-cae0-441d-b59c-feb80ecf6d51.pdf", - "content_type": "application/pdf", - "size": 56687, - "upload_date": "2025-03-14T10:53:08.043663" - }, - { - "id": "a40ec285-afab-45fe-9668-ba99d4da3a70", - "name": "ReportingSheet2025.xlsx", - "type": "document", - "path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\a40ec285-afab-45fe-9668-ba99d4da3a70.xlsx", - "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "size": 14727, - "upload_date": "2025-03-14T13:55:15.194910" + "size": 114510, + "upload_date": "2025-03-16T02:10:23.229879" } ] \ No newline at end of file diff --git a/gwserver/database.py b/gwserver/database.py index d29c026b..a5362c46 100644 --- a/gwserver/database.py +++ b/gwserver/database.py @@ -249,7 +249,20 @@ class Database: 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""" diff --git a/gwserver/static/test.txt b/gwserver/static/test.txt new file mode 100644 index 00000000..bebc6e70 --- /dev/null +++ b/gwserver/static/test.txt @@ -0,0 +1 @@ +OK 1.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index aa3c3c15..bfa971ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,7 @@ pandas==2.0.3 openpyxl==3.1.2 PyPDF2==3.0.1 beautifulsoup4==4.12.2 -requests==2.31.0 \ No newline at end of file +requests==2.31.0 +python-jose[cryptography] +passlib==1.7.4 +bcrypt==3.2.0 \ No newline at end of file diff --git a/start.bat b/start.bat deleted file mode 100644 index 187f5989..00000000 --- a/start.bat +++ /dev/null @@ -1,43 +0,0 @@ -@echo off -echo Data Platform - Multi-Agent Service -echo Startskript fuer gwserver -echo ---------------------------------------- - -:: Verzeichnisstruktur erstellen, falls sie nicht existiert -if not exist gwserver\data mkdir gwserver\data -if not exist gwserver\uploads mkdir gwserver\uploads -if not exist gwserver\results mkdir gwserver\results -if not exist gwserver\webparts mkdir gwserver\webparts - -:: Prüfen, ob Python installiert ist -python --version >nul 2>&1 -if %errorlevel% neq 0 ( - echo Python ist nicht installiert. Bitte installieren Sie Python 3.8 oder hoeher. - exit /b 1 -) - -:: Virtuelle Umgebung erstellen, falls sie nicht existiert -if not exist gwserver\venv ( - echo Erstelle virtuelle Python-Umgebung... - cd gwserver - python -m venv venv - cd .. -) - -:: Virtuelle Umgebung aktivieren -echo Aktiviere virtuelle Umgebung... -call gwserver\venv\Scripts\activate - -:: Abhängigkeiten installieren -echo Installiere Abhaengigkeiten... -pip install -r requirements.txt - -:: Starte gwserver in neuem Fenster -echo Starte gwserver-Server... -start cmd /k "cd gwserver && call venv\Scripts\activate && uvicorn app:app --reload --host 0.0.0.0 --port 8000" - -echo ---------------------------------------- -echo Server wurden gestartet! -echo Gateway laeuft auf: http://localhost:8000 -echo API-Dokumentation: http://localhost:8000/docs -pause \ No newline at end of file diff --git a/start.sh b/start.sh index 15161540..7004af23 100644 --- a/start.sh +++ b/start.sh @@ -13,7 +13,7 @@ echo "----------------------------------------" mkdir -p gwserver/data mkdir -p gwserver/uploads mkdir -p gwserver/results -mkdir -p gwserver/webparts +mkdir -p gwserver/static # Prüfen, ob Python installiert ist if command -v python3 &>/dev/null; then