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, timezone from dataclasses import dataclass import io from modules.security.auth import getCurrentActiveUser from modules.shared.configuration import APP_CONFIG # Import interfaces from modules.interfaces.lucydomInterface import getInterface, FileError, FileNotFoundError, FileStorageError, FilePermissionError, FileDeletionError from modules.interfaces.lucydomModel import FileItem, getModelAttributes # Configure logger logger = logging.getLogger(__name__) # Model attributes for FileItem fileAttributes = getModelAttributes(FileItem) # Create router for file endpoints 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(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): """Get all available files""" try: myInterface = getInterface(currentUser) # Get all files generically - only metadata, no binary data files = myInterface.getAllFiles() return files except Exception as e: logger.error(f"Error retrieving files: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error retrieving files: {str(e)}" ) @router.post("/upload", status_code=status.HTTP_201_CREATED) async def upload_file( file: UploadFile = File(...), workflowId: Optional[str] = Form(None), currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) ): """Upload a file""" try: myInterface = getInterface(currentUser) # Read file fileContent = await file.read() # Check size limits maxSize = int(APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in bytes if len(fileContent) > maxSize: raise HTTPException( status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail=f"File too large. Maximum size: {APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB" ) # Save file via LucyDOM interface in the database fileMeta = myInterface.saveUploadedFile(fileContent, file.filename) # If workflowId is provided, update the file information if workflowId: updateData = {"workflowId": workflowId} myInterface.updateFile(fileMeta["id"], updateData) fileMeta["workflowId"] = workflowId # Successful response return fileMeta except FileStorageError as e: logger.error(f"Error during file upload (storage): {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) ) except Exception as e: logger.error(f"Error during file upload: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error during file upload: {str(e)}" ) @router.get("/{fileId}") async def get_file( fileId: str, currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) ): """Returns a file by its ID for download""" try: myInterface = getInterface(currentUser) # Get file via LucyDOM interface from the database fileData = myInterface.downloadFile(fileId) # Return file headers = { "Content-Disposition": f'attachment; filename="{fileData["name"]}"' } return Response( content=fileData["content"], media_type=fileData["contentType"], headers=headers ) except FileNotFoundError as e: logger.warning(f"File not found: {str(e)}") raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(e) ) except FilePermissionError as e: logger.warning(f"No permission for file: {str(e)}") raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e) ) except FileError as e: logger.error(f"Error retrieving file: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) ) except Exception as e: logger.error(f"Unexpected error retrieving file: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error retrieving file: {str(e)}" ) @router.put("/{file_id}") async def update_file( file_id: str, file_data: FileItem, current_user: Dict[str, Any] = Depends(getCurrentActiveUser) ): """ Update file metadata """ try: myInterface = getInterface(current_user) # Get the file from the database file = myInterface.getFile(file_id) if not file: raise HTTPException(status_code=404, detail="File not found") # Check if user has access to the file if file.get("userId", 0) != current_user.get("id", 0): raise HTTPException(status_code=403, detail="Not authorized to update this file") # Update file metadata update_data = file_data.dict(exclude_unset=True) update_data["modified_at"] = datetime.now(timezone.utc) # Update in database result = myInterface.updateFile(file_id, update_data) if not result: raise HTTPException(status_code=500, detail="Failed to update file") # Get updated file updated_file = myInterface.getFile(file_id) return updated_file except HTTPException as he: raise he except Exception as e: logger.error(f"Error updating file: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.delete("/{fileId}", status_code=status.HTTP_204_NO_CONTENT) async def delete_file( fileId: str, currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) ): """Deletes a file by its ID from the database""" try: myInterface = getInterface(currentUser) # Delete file via LucyDOM interface myInterface.deleteFile(fileId) # Return successful deletion without content (204 No Content) return Response(status_code=status.HTTP_204_NO_CONTENT) except FileNotFoundError as e: logger.warning(f"File not found: {str(e)}") raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(e) ) except FilePermissionError as e: logger.warning(f"No permission to delete file: {str(e)}") raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e) ) except FileDeletionError as e: logger.error(f"Error deleting file: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) ) except Exception as e: logger.error(f"Unexpected error deleting file: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error deleting file: {str(e)}" ) @router.get("/stats", response_model=Dict[str, Any]) async def get_file_stats( currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) ): """Returns statistics about the stored files""" try: myInterface = getInterface(currentUser) # Get all files - metadata only allFiles = myInterface.getAllFiles() # Calculate statistics totalFiles = len(allFiles) totalSize = sum(file.get("size", 0) for file in allFiles) # Group by file type fileTypes = {} for file in allFiles: fileType = file.get("mimeType", "unknown").split("/")[0] if fileType not in fileTypes: fileTypes[fileType] = 0 fileTypes[fileType] += 1 return { "totalFiles": totalFiles, "totalSizeBytes": totalSize, "fileTypes": fileTypes } except Exception as e: logger.error(f"Error retrieving file statistics: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error retrieving file statistics: {str(e)}" )