gateway/modules/routes/routeFiles.py
2025-05-21 19:38:06 +02:00

257 lines
No EOL
9 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, timezone
from dataclasses import dataclass
import io
# Import auth module
import modules.security.auth as auth
# Import interfaces
import modules.interfaces.lucydomInterface as lucydomInterface
from modules.interfaces.lucydomModel import FileItem
# Configure logger
logger = logging.getLogger(__name__)
# Model attributes for FileItem
fileAttributes = lucydomInterface.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[FileItem])
async def get_files(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Get all available files"""
try:
interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get all files generically - only metadata, no binary data
files = interfaceLucydom.getAllFiles()
return [FileItem(**file) for file in 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(auth.getCurrentActiveUser)
):
"""Upload a file"""
try:
interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Read file
fileContent = await file.read()
# Check size limits
maxSize = int(lucydomInterface.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: {lucydomInterface.APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
)
# Save file via LucyDOM interface in the database
fileMeta = interfaceLucydom.saveUploadedFile(fileContent, file.filename)
# If workflowId is provided, update the file information
if workflowId:
updateData = {"workflowId": workflowId}
interfaceLucydom.updateFile(fileMeta["id"], updateData)
fileMeta["workflowId"] = workflowId
# Successful response
return fileMeta
except lucydomInterface.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(auth.getCurrentActiveUser)
):
"""Returns a file by its ID for download"""
try:
interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get file via LucyDOM interface from the database
fileData = interfaceLucydom.downloadFile(fileId)
# Return file
headers = {
"Content-Disposition": f'attachment; filename="{fileData["name"]}"'
}
return Response(
content=fileData["content"],
media_type=fileData["contentType"],
headers=headers
)
except lucydomInterface.FileNotFoundError as e:
logger.warning(f"File not found: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except lucydomInterface.FilePermissionError as e:
logger.warning(f"No permission for file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
except lucydomInterface.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}", response_model=FileItem)
async def update_file(
file_id: str,
file_data: FileItem,
current_user: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""
Update file metadata
"""
try:
interfaceLucydom = lucydomInterface.getInterface(current_user)
# Get the file from the database
file = interfaceLucydom.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")
# Convert FileItem to dict for interface
update_data = file_data.model_dump()
# Update the file
result = interfaceLucydom.updateFile(file_id, update_data)
if not result:
raise HTTPException(status_code=500, detail="Failed to update file")
# Get updated file and convert to FileItem
updated_file = interfaceLucydom.getFile(file_id)
return FileItem(**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(auth.getCurrentActiveUser)
):
"""Deletes a file by its ID from the database"""
try:
interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Delete file via LucyDOM interface
interfaceLucydom.deleteFile(fileId)
# Return successful deletion without content (204 No Content)
return Response(status_code=status.HTTP_204_NO_CONTENT)
except lucydomInterface.FileNotFoundError as e:
logger.warning(f"File not found: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except lucydomInterface.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 lucydomInterface.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(auth.getCurrentActiveUser)
):
"""Returns statistics about the stored files"""
try:
interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get all files - metadata only
allFiles = interfaceLucydom.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)}"
)