gateway/routes/files.py
2025-04-20 22:22:22 +02:00

270 lines
No EOL
9.4 KiB
Python

from fastapi import APIRouter, HTTPException, Depends, File, UploadFile, Form, Path, Request, status, Query, Response
from fastapi.responses import JSONResponse
from typing import List, Dict, Any, Optional
import logging
from datetime import datetime
from dataclasses import dataclass
import io
from modules.auth import get_current_active_user, get_user_context
from modules.configuration import APP_CONFIG
# 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)
@dataclass
class AppContext:
"""Kontext-Objekt für alle benötigten Verbindungen und Benutzerinformationen"""
mandate_id: int
user_id: int
interface_data: Any # LucyDOM Interface
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Erstellt ein zentrales Kontext-Objekt mit allen benötigten Interfaces
Args:
current_user: Aktueller Benutzer aus der Authentifizierung
Returns:
AppContext-Objekt mit allen benötigten Verbindungen
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_lucydom_interface(mandate_id, user_id)
return AppContext(
mandate_id=mandate_id,
user_id=user_id,
interface_data=interface_data
)
# 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:
context = await get_context(current_user)
# Alle Dateien generisch abrufen - nur Metadaten, keine Binärdaten
files = context.interface_data.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
"""
try:
context = await get_context(current_user)
# Datei einlesen
file_content = await file.read()
# Größenbeschränkung prüfen
max_size = int(APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # 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: {APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
)
# Datei über das LucyDOM-Interface in der Datenbank speichern
file_meta = context.interface_data.save_uploaded_file(file_content, file.filename)
# Wenn workflow_id angegeben, aktualisiere die Dateiinformationen
if workflow_id:
update_data = {"workflow_id": workflow_id}
context.interface_data.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 zum Download zurück.
Ruft sowohl Metadaten als auch Binärdaten ab.
"""
try:
context = await get_context(current_user)
# Datei über das LucyDOM-Interface aus der Datenbank abrufen
# Verwendet die download_file-Methode, die nun Metadaten und Binärdaten kombiniert
file_data = context.interface_data.download_file(file_id)
# Datei zurückgeben
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 aus der Datenbank.
Entfernt sowohl die Metadaten als auch die Binärdaten.
"""
try:
context = await get_context(current_user)
# Datei über das LucyDOM-Interface löschen
# Die Methode kümmert sich nun um das Löschen beider Tabellen (files und file_data)
context.interface_data.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("/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:
context = await get_context(current_user)
# Alle Dateien abrufen - nur Metadaten
all_files = context.interface_data.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("mime_type", "unknown").split("/")[0]
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)}"
)