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)}" )