stable secure version
This commit is contained in:
parent
5a0c7e0ae8
commit
98c8e8d545
8 changed files with 180 additions and 114 deletions
|
|
@ -1,8 +1,10 @@
|
||||||
import uvicorn
|
from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query, status
|
||||||
from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import JSONResponse, Response
|
from fastapi.responses import JSONResponse, Response
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
|
|
@ -10,6 +12,10 @@ import json
|
||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
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 (
|
from models import (
|
||||||
Workspace,
|
Workspace,
|
||||||
|
|
@ -21,8 +27,16 @@ from models import (
|
||||||
LogEntry,
|
LogEntry,
|
||||||
Result
|
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
|
# Konfiguration des Loggers
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
|
|
@ -32,18 +46,17 @@ logging.basicConfig(
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
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
|
# CORS-Konfiguration für Frontend-Anfragen
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"], # In Produktion einschränken
|
allow_origins=["http://localhost:8080"],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Pfad zum Webparts-Verzeichnis
|
# Pfad zum Webparts-Verzeichnis
|
||||||
WEBPARTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webparts")
|
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)
|
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||||
|
|
||||||
# Komponenten des Frontends
|
# 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"])
|
@app.get("/", tags=["General"])
|
||||||
async def root():
|
async def root():
|
||||||
|
|
@ -64,9 +79,34 @@ async def root():
|
||||||
async def get_test():
|
async def get_test():
|
||||||
return "OK 1.0"
|
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
|
# Workspace-Endpunkte
|
||||||
@app.get("/api/workspaces", tags=["Workspaces"], response_model=List[Workspace])
|
@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"""
|
"""Alle verfügbaren Workspaces abrufen"""
|
||||||
return db.get_all_workspaces()
|
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)}")
|
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
|
# Prompt-Endpunkte
|
||||||
@app.get("/api/prompts", tags=["Prompts"], response_model=List[Prompt])
|
@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)):
|
async def get_prompts(workspace_id: Optional[str] = Query(None), db: Database = Depends(get_db)):
|
||||||
|
|
|
||||||
70
gwserver/auth.py
Normal file
70
gwserver/auth.py
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,65 +1,11 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "file_001",
|
"id": "0399d060-5beb-46e6-9c3c-830ae7a471cb",
|
||||||
"name": "Quartalsbericht Q1 2025.pdf",
|
"name": "Index.pdf",
|
||||||
"type": "document",
|
"type": "document",
|
||||||
|
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\gwserver\\uploads\\0399d060-5beb-46e6-9c3c-830ae7a471cb.pdf",
|
||||||
"content_type": "application/pdf",
|
"content_type": "application/pdf",
|
||||||
"size": 2500000,
|
"size": 114510,
|
||||||
"upload_date": "2025-03-14T00:25:25.076526",
|
"upload_date": "2025-03-16T02:10:23.229879"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -250,6 +250,19 @@ class Database:
|
||||||
self._save_data(self.files_file, files)
|
self._save_data(self.files_file, files)
|
||||||
return file_data
|
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
|
# Prompt-Methoden
|
||||||
def get_all_prompts(self) -> List[Dict[str, Any]]:
|
def get_all_prompts(self) -> List[Dict[str, Any]]:
|
||||||
"""Gibt alle Prompts zurück"""
|
"""Gibt alle Prompts zurück"""
|
||||||
|
|
|
||||||
1
gwserver/static/test.txt
Normal file
1
gwserver/static/test.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
OK 1.0
|
||||||
|
|
@ -16,3 +16,6 @@ openpyxl==3.1.2
|
||||||
PyPDF2==3.0.1
|
PyPDF2==3.0.1
|
||||||
beautifulsoup4==4.12.2
|
beautifulsoup4==4.12.2
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
|
python-jose[cryptography]
|
||||||
|
passlib==1.7.4
|
||||||
|
bcrypt==3.2.0
|
||||||
43
start.bat
43
start.bat
|
|
@ -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
|
|
||||||
2
start.sh
2
start.sh
|
|
@ -13,7 +13,7 @@ echo "----------------------------------------"
|
||||||
mkdir -p gwserver/data
|
mkdir -p gwserver/data
|
||||||
mkdir -p gwserver/uploads
|
mkdir -p gwserver/uploads
|
||||||
mkdir -p gwserver/results
|
mkdir -p gwserver/results
|
||||||
mkdir -p gwserver/webparts
|
mkdir -p gwserver/static
|
||||||
|
|
||||||
# Prüfen, ob Python installiert ist
|
# Prüfen, ob Python installiert ist
|
||||||
if command -v python3 &>/dev/null; then
|
if command -v python3 &>/dev/null; then
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue