gateway/modules/routes/routeDataFiles.py
2025-05-28 01:51:10 +02:00

298 lines
No EOL
10 KiB
Python

from fastapi import APIRouter, HTTPException, Depends, File, UploadFile, Form, Path, Request, status, Query, Response, Body
from fastapi.responses import JSONResponse, FileResponse
from typing import List, Dict, Any, Optional
import logging
from datetime import datetime, timezone
from dataclasses import dataclass
import io
# Import auth module
from modules.security.auth import limiter, getCurrentUser
# Import interfaces
import modules.interfaces.serviceManagementClass as serviceManagementClass
from modules.interfaces.serviceManagementModel import FileItem
from modules.shared.attributeUtils import getModelAttributeDefinitions
from modules.interfaces.serviceAppModel import AttributeDefinition, User
# Configure logger
logger = logging.getLogger(__name__)
# Model attributes for FileItem
fileAttributes = getModelAttributeDefinitions(FileItem)
# Create router for file endpoints
router = APIRouter(
prefix="/api/files",
tags=["Manage Files"],
responses={
404: {"description": "Not found"},
400: {"description": "Bad request"},
401: {"description": "Unauthorized"},
403: {"description": "Forbidden"},
500: {"description": "Internal server error"}
}
)
@router.get("/list", response_model=List[FileItem])
@limiter.limit("30/minute")
async def get_files(
request: Request,
currentUser: User = Depends(getCurrentUser)
) -> List[FileItem]:
"""Get all files"""
try:
managementInterface = serviceManagementClass.getInterface(currentUser)
# Get all files generically - only metadata, no binary data
files = managementInterface.getAllFiles()
return [FileItem(**file) for file in files]
except Exception as e:
logger.error(f"Error getting files: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get files: {str(e)}"
)
@router.post("/upload", status_code=status.HTTP_201_CREATED)
@limiter.limit("10/minute")
async def upload_file(
request: Request,
file: UploadFile = File(...),
workflowId: Optional[str] = Form(None),
currentUser: User = Depends(getCurrentUser)
) -> JSONResponse:
"""Upload a file"""
try:
managementInterface = serviceManagementClass.getInterface(currentUser)
# Read file
fileContent = await file.read()
# Check size limits
maxSize = int(serviceManagementClass.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: {serviceManagementClass.APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
)
# Save file via LucyDOM interface in the database
fileMeta = managementInterface.saveUploadedFile(fileContent, file.filename)
# If workflowId is provided, update the file information
if workflowId:
updateData = {"workflowId": workflowId}
managementInterface.updateFile(fileMeta["id"], updateData)
fileMeta["workflowId"] = workflowId
# Successful response
return JSONResponse({
"message": "File uploaded successfully",
"file": fileMeta
})
except serviceManagementClass.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}", response_model=FileItem)
@limiter.limit("30/minute")
async def get_file(
request: Request,
fileId: str = Path(..., description="ID of the file"),
currentUser: User = Depends(getCurrentUser)
) -> FileItem:
"""Get a file"""
try:
managementInterface = serviceManagementClass.getInterface(currentUser)
# Get file via LucyDOM interface from the database
fileData = managementInterface.getFile(fileId)
if not fileData:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"File with ID {fileId} not found"
)
return FileItem(**fileData)
except serviceManagementClass.FileNotFoundError as e:
logger.warning(f"File not found: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except serviceManagementClass.FilePermissionError as e:
logger.warning(f"No permission for file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
except serviceManagementClass.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("/{fileId}", response_model=FileItem)
@limiter.limit("10/minute")
async def update_file(
request: Request,
fileId: str = Path(..., description="ID of the file to update"),
file_info: Dict[str, Any] = Body(...),
currentUser: User = Depends(getCurrentUser)
) -> FileItem:
"""Update file info"""
try:
managementInterface = serviceManagementClass.getInterface(currentUser)
# Get the file from the database
file = managementInterface.getFile(fileId)
if not file:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"File with ID {fileId} not found"
)
# Check if user has access to the file
if file.get("userId", 0) != currentUser.get("id", 0):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to update this file"
)
# Update the file
result = managementInterface.updateFile(fileId, file_info)
if not result:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to update file"
)
# Get updated file and convert to FileItem
updatedFile = managementInterface.getFile(fileId)
return FileItem(**updatedFile)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error updating file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
@router.delete("/{fileId}", status_code=status.HTTP_204_NO_CONTENT)
@limiter.limit("10/minute")
async def delete_file(
request: Request,
fileId: str,
currentUser: User = Depends(getCurrentUser)
) -> JSONResponse:
"""Delete a file"""
try:
managementInterface = serviceManagementClass.getInterface(currentUser)
# Delete file via LucyDOM interface
managementInterface.deleteFile(fileId)
# Return successful deletion without content (204 No Content)
return JSONResponse({
"message": "File deleted successfully"
})
except serviceManagementClass.FileNotFoundError as e:
logger.warning(f"File not found: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except serviceManagementClass.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 serviceManagementClass.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])
@limiter.limit("30/minute")
async def get_file_stats(
request: Request,
currentUser: User = Depends(getCurrentUser)
) -> Dict[str, Any]:
"""Returns statistics about the stored files"""
try:
managementInterface = serviceManagementClass.getInterface(currentUser)
# Get all files - metadata only
allFiles = managementInterface.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)}"
)
@router.get("/attributes", response_model=List[AttributeDefinition])
@limiter.limit("30/minute")
async def get_file_attributes(
request: Request,
currentUser: User = Depends(getCurrentUser)
) -> List[AttributeDefinition]:
"""
Retrieves the attribute definitions for files.
This can be used for dynamic form generation.
Returns:
- A list of attribute definitions that can be used to generate forms
"""
# Get attributes from the FileItem model class
return FileItem.getModelAttributeDefinitions()