gateway/routes/files.py
2025-04-16 23:20:21 +02:00

339 lines
No EOL
12 KiB
Python

from fastapi import APIRouter, HTTPException, Depends, File, UploadFile, Form, Path, Request, status, Query, Response
from fastapi.responses import FileResponse, JSONResponse
from typing import List, Dict, Any, Optional
import logging
from datetime import datetime
from modules.auth import get_current_active_user, get_user_context
# Import interfaces
from modules.lucydom_interface import get_lucydom_interface, FileError, FileNotFoundError, FileStorageError, FilePermissionError, FileDeletionError
from modules.lucydom_model import FileItem
# Logger konfigurieren
logger = logging.getLogger(__name__)
# Alle Attribute des Models ermitteln (außer interne/spezielle Attribute)
def get_model_attributes(model_class):
return [attr for attr in dir(model_class)
if not callable(getattr(model_class, attr))
and not attr.startswith('_')
and attr != 'metadata'
and attr != 'query'
and attr != 'query_class'
and attr != 'label'
and attr != 'field_labels']
# Modell-Attribute für FileItem
file_attributes = get_model_attributes(FileItem)
# Router für Datei-Endpunkte erstellen
router = APIRouter(
prefix="/api/files",
tags=["Files"],
responses={
404: {"description": "Not found"},
400: {"description": "Bad request"},
401: {"description": "Unauthorized"},
403: {"description": "Forbidden"},
500: {"description": "Internal server error"}
}
)
@router.get("", 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"""
try:
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Benutzerkontext initialisieren
lucy_interface = get_lucydom_interface(mandate_id, user_id)
# Alle Dateien generisch abrufen
files = lucy_interface.get_all_files()
return files
except Exception as e:
logger.error(f"Fehler beim Abrufen der Dateien: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Abrufen der Dateien: {str(e)}"
)
@router.post("/upload", status_code=status.HTTP_201_CREATED)
async def upload_file(
file: UploadFile = File(...),
workflow_id: Optional[str] = Form(None),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Upload einer Datei für Workflows oder allgemeine Nutzung.
"""
try:
# Kontext-Informationen extrahieren
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Kontext holen
lucydom = get_lucydom_interface(mandate_id, user_id)
# Datei einlesen
file_content = await file.read()
# Größenbeschränkung prüfen (z.B. 50MB)
max_size = 50 * 1024 * 1024 # 50MB in Bytes
if len(file_content) > max_size:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail=f"Datei zu groß. Maximale Größe: 50MB"
)
# Datei über das LucyDOM-Interface speichern
file_meta = lucydom.save_uploaded_file(file_content, file.filename)
# Wenn workflow_id angegeben, aktualisiere die Dateiinformationen
if workflow_id:
update_data = {"workflow_id": workflow_id}
lucydom.update_file(file_meta["id"], update_data)
file_meta["workflow_id"] = workflow_id
# Erfolgreiche Antwort
return file_meta
except FileStorageError as e:
logger.error(f"Fehler beim Datei-Upload (Speichern): {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
except Exception as e:
logger.error(f"Fehler beim Datei-Upload: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Datei-Upload: {str(e)}"
)
@router.get("/{file_id}")
async def get_file(
file_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Gibt eine Datei anhand ihrer ID zurück.
"""
try:
# Kontext-Informationen extrahieren
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Kontext holen
lucydom = get_lucydom_interface(mandate_id, user_id)
# Datei über das LucyDOM-Interface abrufen
file_data = lucydom.download_file(file_id)
# Datei zurückgeben
if "path" in file_data and file_data["path"]:
# FileResponse verwenden, wenn ein Pfad vorhanden ist (effizienteres Streaming)
return FileResponse(
path=file_data["path"],
media_type=file_data["content_type"],
filename=file_data["name"]
)
else:
# Response mit Binärdaten, wenn kein Pfad vorhanden ist
headers = {
"Content-Disposition": f'attachment; filename="{file_data["name"]}"'
}
return Response(
content=file_data["content"],
media_type=file_data["content_type"],
headers=headers
)
except FileNotFoundError as e:
logger.warning(f"Datei nicht gefunden: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except FilePermissionError as e:
logger.warning(f"Keine Berechtigung für Datei: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
except FileError as e:
logger.error(f"Fehler beim Abrufen der Datei: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
except Exception as e:
logger.error(f"Unerwarteter Fehler beim Abrufen der Datei: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Abrufen der Datei: {str(e)}"
)
@router.delete("/{file_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_file(
file_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Löscht eine Datei anhand ihrer ID.
"""
try:
# Kontext-Informationen extrahieren
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Kontext holen
lucydom = get_lucydom_interface(mandate_id, user_id)
# Datei über das LucyDOM-Interface löschen
lucydom.delete_file(file_id)
# Erfolgreiche Löschung ohne Inhalt zurückgeben (204 No Content)
return Response(status_code=status.HTTP_204_NO_CONTENT)
except FileNotFoundError as e:
logger.warning(f"Datei nicht gefunden: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except FilePermissionError as e:
logger.warning(f"Keine Berechtigung zum Löschen der Datei: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
except FileDeletionError as e:
logger.error(f"Fehler beim Löschen der Datei: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
except Exception as e:
logger.error(f"Unerwarteter Fehler beim Löschen der Datei: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Löschen der Datei: {str(e)}"
)
@router.get("/cleanup/orphaned", response_model=Dict[str, Any])
async def cleanup_orphaned_files(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Bereinigt verwaiste Dateien, die physisch existieren aber keine Einträge in der Datenbank haben.
Nur für Administratoren.
"""
try:
# Kontext-Informationen extrahieren
mandate_id, user_id = await get_user_context(current_user)
# Prüfen, ob der Benutzer Admin-Rechte hat
# TODO: Implementieren einer richtigen Admin-Rechteverwaltung
if user_id != 1: # Temporäre einfache Lösung
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Nur Administratoren können diese Funktion ausführen"
)
# LucyDOM-Interface mit Kontext holen
lucydom = get_lucydom_interface(mandate_id, user_id)
# Verwaiste Dateien bereinigen
lucydom.cleanup_orphaned_files()
# Temporäre Dateien bereinigen
lucydom.cleanup_temp_files()
return {"status": "success", "message": "Bereinigung abgeschlossen"}
except Exception as e:
logger.error(f"Fehler bei der Datei-Bereinigung: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler bei der Datei-Bereinigung: {str(e)}"
)
@router.get("/temp/cleanup", response_model=Dict[str, Any])
async def cleanup_temp_files(
max_age_hours: int = Query(24, ge=1, le=168),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Bereinigt temporäre Dateien, die älter als die angegebene Zeit sind.
Args:
max_age_hours: Maximales Alter der temporären Dateien in Stunden (1-168)
"""
try:
# Kontext-Informationen extrahieren
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Kontext holen
lucydom = get_lucydom_interface(mandate_id, user_id)
# Temporäre Dateien bereinigen
lucydom.cleanup_temp_files(max_age_hours)
return {
"status": "success",
"message": f"Temporäre Dateien älter als {max_age_hours} Stunden wurden bereinigt"
}
except Exception as e:
logger.error(f"Fehler bei der Bereinigung temporärer Dateien: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler bei der Bereinigung temporärer Dateien: {str(e)}"
)
@router.get("/stats", response_model=Dict[str, Any])
async def get_file_stats(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Gibt Statistiken über die gespeicherten Dateien zurück.
"""
try:
# Kontext-Informationen extrahieren
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Kontext holen
lucydom = get_lucydom_interface(mandate_id, user_id)
# Alle Dateien abrufen
all_files = lucydom.get_all_files()
# Statistiken berechnen
total_files = len(all_files)
total_size = sum(file.get("size", 0) for file in all_files)
# Nach Dateityp gruppieren
file_types = {}
for file in all_files:
file_type = file.get("type", "unknown")
if file_type not in file_types:
file_types[file_type] = 0
file_types[file_type] += 1
return {
"total_files": total_files,
"total_size_bytes": total_size,
"file_types": file_types
}
except Exception as e:
logger.error(f"Fehler beim Abrufen der Datei-Statistiken: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Abrufen der Datei-Statistiken: {str(e)}"
)