# Copyright (c) 2025 Patrick Motsch # All rights reserved. from fastapi import APIRouter, HTTPException, Depends, Path, Request, status, Query, Body from typing import List, Dict, Any, Optional import logging # Import auth module from modules.auth import limiter, getRequestContext, RequestContext # Import interfaces from modules.datamodels.datamodelNeutralizer import DataNeutraliserConfig, DataNeutralizerAttributes from modules.features.neutralizePlayground.mainNeutralizePlayground import NeutralizationPlayground # Configure logger logger = logging.getLogger(__name__) # Create router for neutralization endpoints router = APIRouter( prefix="/api/neutralization", tags=["Data Neutralisation"], responses={ 404: {"description": "Not found"}, 400: {"description": "Bad request"}, 401: {"description": "Unauthorized"}, 403: {"description": "Forbidden"}, 500: {"description": "Internal server error"} } ) @router.get("/config", response_model=DataNeutraliserConfig) @limiter.limit("30/minute") async def get_neutralization_config( request: Request, context: RequestContext = Depends(getRequestContext) ) -> DataNeutraliserConfig: """Get data neutralization configuration""" try: service = NeutralizationPlayground(context.user, str(context.mandateId)) config = service.getConfig() if not config: # Return default config instead of 404 return DataNeutraliserConfig( mandateId=context.mandateId, userId=context.user.id, enabled=True, namesToParse="", sharepointSourcePath="", sharepointTargetPath="" ) return config except HTTPException: raise except Exception as e: logger.error(f"Error getting neutralization config: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error getting neutralization config: {str(e)}" ) @router.post("/config", response_model=DataNeutraliserConfig) @limiter.limit("10/minute") async def save_neutralization_config( request: Request, config_data: Dict[str, Any] = Body(...), context: RequestContext = Depends(getRequestContext) ) -> DataNeutraliserConfig: """Save or update data neutralization configuration""" try: service = NeutralizationPlayground(context.user, str(context.mandateId)) config = service.saveConfig(config_data) return config except Exception as e: logger.error(f"Error saving neutralization config: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error saving neutralization config: {str(e)}" ) @router.post("/neutralize-text", response_model=Dict[str, Any]) @limiter.limit("20/minute") async def neutralize_text( request: Request, text_data: Dict[str, Any] = Body(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Neutralize text content""" try: text = text_data.get("text", "") file_id = text_data.get("fileId") if not text: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Text content is required" ) service = NeutralizationPlayground(context.user, str(context.mandateId)) result = service.neutralizeText(text, file_id) return result except HTTPException: raise except Exception as e: logger.error(f"Error neutralizing text: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error neutralizing text: {str(e)}" ) @router.post("/resolve-text", response_model=Dict[str, str]) @limiter.limit("20/minute") async def resolve_text( request: Request, text_data: Dict[str, str] = Body(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, str]: """Resolve UIDs in neutralized text back to original text""" try: text = text_data.get("text", "") if not text: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Text content is required" ) service = NeutralizationPlayground(context.user, str(context.mandateId)) resolved_text = service.resolveText(text) return {"resolved_text": resolved_text} except HTTPException: raise except Exception as e: logger.error(f"Error resolving text: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error resolving text: {str(e)}" ) @router.get("/attributes", response_model=List[DataNeutralizerAttributes]) @limiter.limit("30/minute") async def get_neutralization_attributes( request: Request, fileId: Optional[str] = Query(None, description="Filter by file ID"), context: RequestContext = Depends(getRequestContext) ) -> List[DataNeutralizerAttributes]: """Get neutralization attributes, optionally filtered by file ID""" try: service = NeutralizationPlayground(context.user, str(context.mandateId)) attributes = service.getAttributes(fileId) return attributes except Exception as e: logger.error(f"Error getting neutralization attributes: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error getting neutralization attributes: {str(e)}" ) @router.post("/process-sharepoint", response_model=Dict[str, Any]) @limiter.limit("5/minute") async def process_sharepoint_files( request: Request, paths_data: Dict[str, str] = Body(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Process files from SharePoint source path and store neutralized files in target path""" try: source_path = paths_data.get("sourcePath", "") target_path = paths_data.get("targetPath", "") if not source_path or not target_path: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Both source and target paths are required" ) service = NeutralizationPlayground(context.user, str(context.mandateId)) result = await service.processSharepointFiles(source_path, target_path) return result except HTTPException: raise except Exception as e: logger.error(f"Error processing SharePoint files: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error processing SharePoint files: {str(e)}" ) @router.post("/batch-process", response_model=Dict[str, Any]) @limiter.limit("10/minute") async def batch_process_files( request: Request, files_data: List[Dict[str, Any]] = Body(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Process multiple files for neutralization""" try: if not files_data: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Files data is required" ) service = NeutralizationPlayground(context.user, str(context.mandateId)) result = service.batchNeutralizeFiles(files_data) return result except HTTPException: raise except Exception as e: logger.error(f"Error batch processing files: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error batch processing files: {str(e)}" ) @router.get("/stats", response_model=Dict[str, Any]) @limiter.limit("30/minute") async def get_neutralization_stats( request: Request, context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Get neutralization processing statistics""" try: service = NeutralizationPlayground(context.user, str(context.mandateId)) stats = service.getProcessingStats() return stats except Exception as e: logger.error(f"Error getting neutralization stats: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error getting neutralization stats: {str(e)}" ) @router.delete("/attributes/{fileId}", response_model=Dict[str, str]) @limiter.limit("10/minute") async def cleanup_file_attributes( request: Request, fileId: str = Path(..., description="File ID to cleanup attributes for"), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, str]: """Clean up neutralization attributes for a specific file""" try: service = NeutralizationPlayground(context.user, str(context.mandateId)) success = service.cleanupFileAttributes(fileId) if success: return {"message": f"Successfully cleaned up attributes for file {fileId}"} else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to cleanup file attributes" ) except HTTPException: raise except Exception as e: logger.error(f"Error cleaning up file attributes: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error cleaning up file attributes: {str(e)}" )