# Copyright (c) 2025 Patrick Motsch # All rights reserved. import logging import json import base64 from typing import Dict, Any from modules.datamodels.datamodelChat import ActionResult, ActionDocument logger = logging.getLogger(__name__) async def uploadFile(self, parameters: Dict[str, Any]) -> ActionResult: try: connectionReference = parameters.get("connectionReference") if not connectionReference: return ActionResult.isFailure(error="connectionReference parameter is required") contentParam = parameters.get("content") if not contentParam: return ActionResult.isFailure(error="content parameter is required") # Resolve siteId and folderPath: pathQuery (path) or explicit siteId+folderPath pathQuery = (parameters.get("pathQuery") or parameters.get("path") or "").strip() siteIdParam = parameters.get("siteId") folderPath = parameters.get("folderPath") siteId = None if pathQuery and pathQuery != "*": # Option 1: pathQuery (e.g. /sites/host,siteId,webId/15. Persoenliche Ordner/Ida Dittrich/) sites, errorMsg = await self.siteDiscovery.resolveSitesFromPathQuery(pathQuery) if errorMsg: return ActionResult.isFailure(error=errorMsg) if not sites: return ActionResult.isFailure(error="Could not resolve site from path") parsedPath = self.services.sharepoint.extractSiteFromStandardPath(pathQuery) if not parsedPath: return ActionResult.isFailure(error="path must be a standard SharePoint path (e.g. /sites/SiteName/Shared Documents/Folder)") innerPath = (parsedPath.get("innerPath") or "").strip().rstrip("/") siteId = sites[0].get("id") folderPath = innerPath elif siteIdParam and folderPath: # Option 2: explicit siteId + folderPath if isinstance(siteIdParam, str): from modules.datamodels.datamodelDocref import DocumentReferenceList try: docList = DocumentReferenceList.from_string_list([siteIdParam]) chatDocuments = self.services.chat.getChatDocumentsFromDocumentList(docList) if chatDocuments and len(chatDocuments) > 0: siteInfoJson = json.loads(chatDocuments[0].documentData) siteId = siteInfoJson.get("id") except Exception: pass if not siteId: siteId = siteIdParam else: siteId = siteIdParam else: return ActionResult.isFailure(error="Either path (e.g. /sites/.../Folder) or both siteId and folderPath are required") if not siteId: return ActionResult.isFailure(error="Could not resolve siteId") # fileName: from param or from content document fileName = parameters.get("fileName") if not fileName and contentParam: content = contentParam[0] if isinstance(contentParam, (list, tuple)) and contentParam else contentParam if isinstance(content, dict): fileName = content.get("documentName") or content.get("fileName") elif hasattr(content, "documentName"): fileName = getattr(content, "documentName", None) or getattr(content, "fileName", None) if not fileName: fileName = "file" # Get file content: support inline ActionDocument (from automation2 e.g. sharepoint.downloadFile) # or docItem references (chat workflow) content = contentParam[0] if isinstance(contentParam, (list, tuple)) and contentParam else contentParam fileContentBase64 = None if isinstance(content, dict) and content.get("documentData"): fileContentBase64 = content.get("documentData") elif hasattr(content, "documentData") and content.documentData: fileContentBase64 = content.documentData elif isinstance(content, dict) and (content.get("validationMetadata") or {}).get("fileId"): file_id = content["validationMetadata"]["fileId"] try: raw = self.services.chat.getFileData(file_id) fileContentBase64 = base64.b64encode(raw if isinstance(raw, bytes) else str(raw).encode("utf-8")).decode("utf-8") except Exception as e: return ActionResult.isFailure(error=f"Could not load file content from fileId {file_id}: {e}") if not fileContentBase64: from modules.datamodels.datamodelDocref import DocumentReferenceList docList = DocumentReferenceList.from_string_list([content] if isinstance(content, str) else content) chatDocuments = self.services.chat.getChatDocumentsFromDocumentList(docList) if not chatDocuments or len(chatDocuments) == 0: return ActionResult.isFailure(error="Could not get file content from document reference") fileContentBase64 = chatDocuments[0].documentData # Decode base64 try: fileContent = base64.b64decode(fileContentBase64) except Exception as e: return ActionResult.isFailure(error=f"Could not decode base64 file content: {str(e)}") # Get Microsoft connection connection = self.connection.getMicrosoftConnection(connectionReference) if not connection: return ActionResult.isFailure(error="No valid Microsoft connection found for the provided connection reference") # Upload file uploadResult = await self.services.sharepoint.uploadFile( siteId=siteId, folderPath=folderPath, fileName=fileName, content=fileContent ) if "error" in uploadResult: return ActionResult.isFailure(error=f"Upload failed: {uploadResult['error']}") logger.info(f"Uploaded file to SharePoint: {folderPath}/{fileName} ({len(fileContent)} bytes)") # Generate filename workflowContext = self.services.chat.getWorkflowContext() if hasattr(self.services, 'chat') else None filename = self._generateMeaningfulFileName( "file_upload_result", "json", workflowContext, "uploadFile" ) result = { "success": True, "siteId": siteId, "filePath": f"{folderPath}/{fileName}", "fileSize": len(fileContent), "uploadResult": uploadResult } validationMetadata = self._createValidationMetadata( "uploadFile", siteId=siteId, filePath=f"{folderPath}/{fileName}", fileSize=len(fileContent) ) document = ActionDocument( documentName=filename, documentData=json.dumps(result, indent=2), mimeType="application/json", validationMetadata=validationMetadata ) return ActionResult.isSuccess(documents=[document]) except Exception as e: errorMsg = f"Error uploading file to SharePoint: {str(e)}" logger.error(errorMsg) return ActionResult.isFailure(error=errorMsg)