workflow end2end with outlook

This commit is contained in:
ValueOn AG 2025-08-17 20:41:37 +02:00
parent 2d269137a5
commit 7d21146b6a
12 changed files with 1007 additions and 320 deletions

View file

@ -41,8 +41,21 @@ class DocumentGenerator:
if mime_type == "application/octet-stream": if mime_type == "application/octet-stream":
content = getattr(doc, 'content', '') content = getattr(doc, 'content', '')
mime_type = detectMimeTypeFromContent(content, doc.filename, self.service) mime_type = detectMimeTypeFromContent(content, doc.filename, self.service)
# Add result label to filename for document objects too
base_filename = doc.filename
if hasattr(action, 'execResultLabel') and action.execResultLabel:
result_label = action.execResultLabel.strip()
if result_label:
# Check if filename already starts with resultLabel to avoid duplication
if not base_filename.startswith(f"{result_label}-"):
base_filename = f"{result_label}-{base_filename}"
logger.info(f"Added resultLabel '{result_label}' as prefix to document object filename: {base_filename}")
else:
logger.info(f"Document object filename already has resultLabel prefix: {base_filename}")
return { return {
'filename': doc.filename, 'filename': base_filename,
'fileSize': getattr(doc, 'fileSize', 0), 'fileSize': getattr(doc, 'fileSize', 0),
'mimeType': mime_type, 'mimeType': mime_type,
'content': getattr(doc, 'content', ''), 'content': getattr(doc, 'content', ''),
@ -50,8 +63,33 @@ class DocumentGenerator:
} }
elif isinstance(doc, dict): elif isinstance(doc, dict):
# Dictionary format document - handle both 'documentName' and 'filename' keys # Dictionary format document - handle both 'documentName' and 'filename' keys
filename = doc.get('documentName', doc.get('filename', \ base_filename = doc.get('documentName', doc.get('filename', ''))
f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}"))
# Debug logging for resultLabel
if hasattr(action, 'execResultLabel'):
logger.info(f"Action {action.execMethod}.{action.execAction} has execResultLabel: '{action.execResultLabel}' (type: {type(action.execResultLabel)})")
else:
logger.info(f"Action {action.execMethod}.{action.execAction} has NO execResultLabel attribute")
# If no filename provided, generate one with action info
if not base_filename:
base_filename = f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}"
# ALWAYS add result label to filename for better document selection
# This ensures consistent naming regardless of whether filename was provided or generated
if hasattr(action, 'execResultLabel') and action.execResultLabel:
result_label = action.execResultLabel.strip()
if result_label:
# Check if filename already starts with resultLabel to avoid duplication
if not base_filename.startswith(f"{result_label}-"):
base_filename = f"{result_label}-{base_filename}"
logger.info(f"Added resultLabel '{result_label}' as prefix to filename: {base_filename}")
else:
logger.info(f"Filename already has resultLabel prefix: {base_filename}")
else:
logger.info(f"No resultLabel available for action {action.execMethod}.{action.execAction}")
filename = base_filename
mimeType = doc.get('mimeType', 'application/octet-stream') mimeType = doc.get('mimeType', 'application/octet-stream')
# Handle documentData structure - it might be a dict with 'content' key or direct content # Handle documentData structure - it might be a dict with 'content' key or direct content
@ -85,7 +123,23 @@ class DocumentGenerator:
else: else:
# Unknown document type # Unknown document type
logger.warning(f"Unknown document type for action {action.execMethod}.{action.execAction}: {type(doc)}") logger.warning(f"Unknown document type for action {action.execMethod}.{action.execAction}: {type(doc)}")
filename = f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}" base_filename = f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}"
# ALWAYS add result label to filename for better document selection
# This ensures consistent naming regardless of document type
if hasattr(action, 'execResultLabel') and action.execResultLabel:
result_label = action.execResultLabel.strip()
if result_label:
# Check if filename already starts with resultLabel to avoid duplication
if not base_filename.startswith(f"{result_label}-"):
base_filename = f"{result_label}-{base_filename}"
logger.info(f"Added resultLabel '{result_label}' as prefix to fallback filename: {base_filename}")
else:
logger.info(f"Fallback filename already has resultLabel prefix: {base_filename}")
else:
logger.info(f"No resultLabel available for action {action.execMethod}.{action.execAction}")
filename = base_filename
mimeType = detectMimeTypeFromContent(doc, filename, self.service) mimeType = detectMimeTypeFromContent(doc, filename, self.service)
return { return {
'filename': filename, 'filename': filename,

View file

@ -368,6 +368,45 @@ class HandlingTasks:
continue continue
else: else:
logger.error(f"=== TASK {task_index or '?'} FAILED: {task_step.objective} after {attempt+1} attempts ===") logger.error(f"=== TASK {task_index or '?'} FAILED: {task_step.objective} after {attempt+1} attempts ===")
# Create user-facing error message for task failure
error_message = f"❌ Task {task_index or '?'} - '{task_step.objective}' failed after {attempt+1} attempts\n\n"
error_message += f"Objective: {task_step.objective}\n\n"
# Add specific error details if available
if error:
error_message += f"Error: {error}\n\n"
# Add retry information
error_message += f"Attempts: {attempt+1}\n"
error_message += f"Status: Will retry automatically\n\n"
error_message += "The system will attempt to retry this task. Please wait..."
# Create workflow message for user
message_data = {
"workflowId": workflow.id,
"role": "assistant",
"message": error_message,
"status": "step",
"sequenceNr": len(workflow.messages) + 1,
"publishedAt": datetime.now(UTC).isoformat(),
"actionId": None,
"actionMethod": "task",
"actionName": "task_retry",
"documentsLabel": None,
"documents": []
}
try:
message = self.chatInterface.createWorkflowMessage(message_data)
if message:
workflow.messages.append(message)
logger.info(f"Created user-facing retry message for failed task: {task_step.objective}")
else:
logger.error(f"Failed to create user-facing retry message for failed task: {task_step.objective}")
except Exception as e:
logger.error(f"Error creating user-facing retry message: {str(e)}")
return TaskResult( return TaskResult(
taskId=task_step.id, taskId=task_step.id,
status=TaskStatus.FAILED, status=TaskStatus.FAILED,
@ -376,6 +415,45 @@ class HandlingTasks:
error=error error=error
) )
logger.error(f"=== TASK {task_index or '?'} FAILED AFTER ALL RETRIES: {task_step.objective} ===") logger.error(f"=== TASK {task_index or '?'} FAILED AFTER ALL RETRIES: {task_step.objective} ===")
# Create user-facing error message for task failure
error_message = f"❌ Task {task_index or '?'} - '{task_step.objective}' failed after all retries\n\n"
error_message += f"Objective: {task_step.objective}\n\n"
# Add specific error details if available
if error and error != "Task failed after all retries.":
error_message += f"Error: {error}\n\n"
# Add retry information
error_message += f"Retries attempted: {retry_context.retry_count if retry_context else 'Unknown'}\n"
error_message += f"Status: Task failed permanently\n\n"
error_message += "Please check the connection and try again, or contact support if the issue persists."
# Create workflow message for user
message_data = {
"workflowId": workflow.id,
"role": "assistant",
"message": error_message,
"status": "step",
"sequenceNr": len(workflow.messages) + 1,
"publishedAt": datetime.now(UTC).isoformat(),
"actionId": None,
"actionMethod": "task",
"actionName": "task_failure",
"documentsLabel": None,
"documents": []
}
try:
message = self.chatInterface.createWorkflowMessage(message_data)
if message:
workflow.messages.append(message)
logger.info(f"Created user-facing error message for failed task: {task_step.objective}")
else:
logger.error(f"Failed to create user-facing error message for failed task: {task_step.objective}")
except Exception as e:
logger.error(f"Error creating user-facing error message: {str(e)}")
return TaskResult( return TaskResult(
taskId=task_step.id, taskId=task_step.id,
status=TaskStatus.FAILED, status=TaskStatus.FAILED,

View file

@ -383,6 +383,22 @@ class DatabaseConnector:
self._tablesCache = {} self._tablesCache = {}
self._tableMetadataCache = {} self._tableMetadataCache = {}
def clearTableCache(self, table: str) -> None:
"""Clears cache for a specific table to ensure fresh data."""
if table in self._tablesCache:
del self._tablesCache[table]
logger.debug(f"Cleared cache for table: {table}")
if table in self._tableMetadataCache:
del self._tableMetadataCache[table]
logger.debug(f"Cleared metadata cache for table: {table}")
def clearAllCache(self) -> None:
"""Clears all cache to ensure completely fresh data."""
self._tablesCache.clear()
self._tableMetadataCache.clear()
logger.debug("Cleared all database cache")
# Public API # Public API
def getTables(self) -> List[str]: def getTables(self) -> List[str]:

View file

@ -235,6 +235,9 @@ class AppAccess:
"lastActivity": datetime.now() "lastActivity": datetime.now()
}) })
# Clear cache to ensure fresh data
self.db.clearTableCache("sessions")
return True return True
except Exception as e: except Exception as e:

View file

@ -195,6 +195,10 @@ class AppObjects:
""" """
return self.access.canModify(table, recordId) return self.access.canModify(table, recordId)
def _clearTableCache(self, table: str) -> None:
"""Clears the cache for a specific table to ensure fresh data."""
self.db.clearTableCache(table)
def getInitialId(self, table: str) -> Optional[str]: def getInitialId(self, table: str) -> Optional[str]:
"""Returns the initial ID for a table.""" """Returns the initial ID for a table."""
return self.db.getInitialId(table) return self.db.getInitialId(table)
@ -352,6 +356,9 @@ class AppObjects:
# Save to connections table # Save to connections table
self.db.recordCreate("connections", connection.to_dict()) self.db.recordCreate("connections", connection.to_dict())
# Clear cache to ensure fresh data
self._clearTableCache("connections")
return connection return connection
except Exception as e: except Exception as e:
@ -372,6 +379,9 @@ class AppObjects:
# Delete connection # Delete connection
self.db.recordDelete("connections", connectionId) self.db.recordDelete("connections", connectionId)
# Clear cache to ensure fresh data
self._clearTableCache("connections")
except Exception as e: except Exception as e:
logger.error(f"Error removing user connection: {str(e)}") logger.error(f"Error removing user connection: {str(e)}")
raise ValueError(f"Failed to remove user connection: {str(e)}") raise ValueError(f"Failed to remove user connection: {str(e)}")
@ -379,8 +389,7 @@ class AppObjects:
def authenticateLocalUser(self, username: str, password: str) -> Optional[User]: def authenticateLocalUser(self, username: str, password: str) -> Optional[User]:
"""Authenticates a user by username and password using local authentication.""" """Authenticates a user by username and password using local authentication."""
# Clear the users table from cache and reload it # Clear the users table from cache and reload it
if "users" in self.db._tablesCache: self._clearTableCache("users")
del self.db._tablesCache["users"]
# Get user by username # Get user by username
user = self.getUserByUsername(username) user = self.getUserByUsername(username)
@ -445,6 +454,9 @@ class AppObjects:
if not createdRecord or not createdRecord.get("id"): if not createdRecord or not createdRecord.get("id"):
raise ValueError("Failed to create user record") raise ValueError("Failed to create user record")
# Clear cache to ensure fresh data
self._clearTableCache("users")
# Add external connection if provided # Add external connection if provided
if externalId and externalUsername: if externalId and externalUsername:
self.addUserConnection( self.addUserConnection(
@ -460,11 +472,8 @@ class AppObjects:
if not createdUser or len(createdUser) == 0: if not createdUser or len(createdUser) == 0:
raise ValueError("Failed to retrieve created user") raise ValueError("Failed to retrieve created user")
# Clear both table and metadata caches # Clear cache to ensure fresh data (already done above)
if hasattr(self.db, '_tablesCache') and "users" in self.db._tablesCache: # No need for additional cache clearing since _clearTableCache("users") was called
del self.db._tablesCache["users"]
if hasattr(self.db, '_tableMetadataCache') and "users" in self.db._tableMetadataCache:
del self.db._tableMetadataCache["users"]
return User.from_dict(createdUser[0]) return User.from_dict(createdUser[0])
@ -491,6 +500,9 @@ class AppObjects:
# Update user record # Update user record
self.db.recordModify("users", userId, updatedUser.to_dict()) self.db.recordModify("users", userId, updatedUser.to_dict())
# Clear cache to ensure fresh data
self._clearTableCache("users")
# Get updated user # Get updated user
updatedUser = self.getUser(userId) updatedUser = self.getUser(userId)
if not updatedUser: if not updatedUser:
@ -562,11 +574,8 @@ class AppObjects:
if not success: if not success:
raise ValueError(f"Failed to delete user {userId}") raise ValueError(f"Failed to delete user {userId}")
# Clear both table and metadata caches # Clear cache to ensure fresh data
if hasattr(self.db, '_tablesCache') and "users" in self.db._tablesCache: self._clearTableCache("users")
del self.db._tablesCache["users"]
if hasattr(self.db, '_tableMetadataCache') and "users" in self.db._tableMetadataCache:
del self.db._tableMetadataCache["users"]
logger.info(f"User {userId} successfully deleted") logger.info(f"User {userId} successfully deleted")
return True return True
@ -611,6 +620,9 @@ class AppObjects:
if not createdRecord or not createdRecord.get("id"): if not createdRecord or not createdRecord.get("id"):
raise ValueError("Failed to create mandate record") raise ValueError("Failed to create mandate record")
# Clear cache to ensure fresh data
self._clearTableCache("mandates")
return Mandate.from_dict(createdRecord) return Mandate.from_dict(createdRecord)
def updateMandate(self, mandateId: str, updateData: Dict[str, Any]) -> Mandate: def updateMandate(self, mandateId: str, updateData: Dict[str, Any]) -> Mandate:
@ -637,6 +649,9 @@ class AppObjects:
# Update mandate record # Update mandate record
self.db.recordModify("mandates", mandateId, updatedMandate.to_dict()) self.db.recordModify("mandates", mandateId, updatedMandate.to_dict())
# Clear cache to ensure fresh data
self._clearTableCache("mandates")
# Get updated mandate # Get updated mandate
updatedMandate = self.getMandate(mandateId) updatedMandate = self.getMandate(mandateId)
if not updatedMandate: if not updatedMandate:
@ -665,7 +680,12 @@ class AppObjects:
raise ValueError(f"Cannot delete mandate {mandateId} with existing users") raise ValueError(f"Cannot delete mandate {mandateId} with existing users")
# Delete mandate # Delete mandate
return self.db.recordDelete("mandates", mandateId) success = self.db.recordDelete("mandates", mandateId)
# Clear cache to ensure fresh data
self._clearTableCache("mandates")
return success
except Exception as e: except Exception as e:
logger.error(f"Error deleting mandate: {str(e)}") logger.error(f"Error deleting mandate: {str(e)}")
@ -747,6 +767,9 @@ class AppObjects:
# Save to database # Save to database
self.db.recordCreate("tokens", token_dict) self.db.recordCreate("tokens", token_dict)
# Clear cache to ensure fresh data
self._clearTableCache("tokens")
logger.debug(f"Token saved for user {self.currentUser.id} with authority {token.authority}") logger.debug(f"Token saved for user {self.currentUser.id} with authority {token.authority}")
except Exception as e: except Exception as e:
@ -793,6 +816,9 @@ class AppObjects:
for token in tokens: for token in tokens:
self.db.recordDelete("tokens", token["id"]) self.db.recordDelete("tokens", token["id"])
# Clear cache to ensure fresh data
self._clearTableCache("tokens")
except Exception as e: except Exception as e:
logger.error(f"Error deleting token: {str(e)}") logger.error(f"Error deleting token: {str(e)}")
raise raise

View file

@ -121,6 +121,10 @@ class ChatObjects:
"""Delegate to access control module.""" """Delegate to access control module."""
return self.access.canModify(table, recordId) return self.access.canModify(table, recordId)
def _clearTableCache(self, table: str) -> None:
"""Clears the cache for a specific table to ensure fresh data."""
self.db.clearTableCache(table)
# Utilities # Utilities
def getInitialId(self, table: str) -> Optional[str]: def getInitialId(self, table: str) -> Optional[str]:
@ -196,6 +200,9 @@ class ChatObjects:
# Create workflow in database # Create workflow in database
created = self.db.recordCreate("workflows", workflowData) created = self.db.recordCreate("workflows", workflowData)
# Clear cache to ensure fresh data
self._clearTableCache("workflows")
# Convert to ChatWorkflow model # Convert to ChatWorkflow model
return ChatWorkflow( return ChatWorkflow(
id=created["id"], id=created["id"],
@ -226,6 +233,9 @@ class ChatObjects:
# Update workflow in database # Update workflow in database
updated = self.db.recordModify("workflows", workflowId, workflowData) updated = self.db.recordModify("workflows", workflowId, workflowData)
# Clear cache to ensure fresh data
self._clearTableCache("workflows")
# Convert to ChatWorkflow model # Convert to ChatWorkflow model
return ChatWorkflow( return ChatWorkflow(
id=updated["id"], id=updated["id"],
@ -256,7 +266,12 @@ class ChatObjects:
raise PermissionError(f"No permission to delete workflow {workflowId}") raise PermissionError(f"No permission to delete workflow {workflowId}")
# Delete workflow # Delete workflow
return self.db.recordDelete("workflows", workflowId) success = self.db.recordDelete("workflows", workflowId)
# Clear cache to ensure fresh data
self._clearTableCache("workflows")
return success
# Workflow Messages # Workflow Messages
@ -328,6 +343,9 @@ class ChatObjects:
# Create message in database # Create message in database
createdMessage = self.db.recordCreate("workflowMessages", messageData) createdMessage = self.db.recordCreate("workflowMessages", messageData)
# Clear cache to ensure fresh data
self._clearTableCache("workflowMessages")
# Convert to ChatMessage model # Convert to ChatMessage model
return ChatMessage( return ChatMessage(
id=createdMessage["id"], id=createdMessage["id"],
@ -411,6 +429,9 @@ class ChatObjects:
updatedMessage = self.db.recordModify("workflowMessages", messageId, messageData) updatedMessage = self.db.recordModify("workflowMessages", messageId, messageData)
if updatedMessage: if updatedMessage:
logger.debug(f"Message {messageId} updated successfully") logger.debug(f"Message {messageId} updated successfully")
# Clear cache to ensure fresh data
self._clearTableCache("workflowMessages")
else: else:
logger.warning(f"Failed to update message {messageId}") logger.warning(f"Failed to update message {messageId}")
@ -440,7 +461,12 @@ class ChatObjects:
return False return False
# Delete the message from the database # Delete the message from the database
return self.db.recordDelete("workflowMessages", messageId) success = self.db.recordDelete("workflowMessages", messageId)
# Clear cache to ensure fresh data
self._clearTableCache("workflowMessages")
return success
except Exception as e: except Exception as e:
logger.error(f"Error deleting message {messageId}: {str(e)}") logger.error(f"Error deleting message {messageId}: {str(e)}")
return False return False
@ -709,6 +735,9 @@ class ChatObjects:
# Create log in database # Create log in database
createdLog = self.db.recordCreate("workflowLogs", log_model.to_dict()) createdLog = self.db.recordCreate("workflowLogs", log_model.to_dict())
# Clear cache to ensure fresh data
self._clearTableCache("workflowLogs")
# Return validated ChatLog instance # Return validated ChatLog instance
return ChatLog(**createdLog) return ChatLog(**createdLog)
@ -1078,6 +1107,9 @@ class ChatObjects:
# Create task in database # Create task in database
createdTask = self.db.recordCreate("tasks", taskData) createdTask = self.db.recordCreate("tasks", taskData)
# Clear cache to ensure fresh data
self._clearTableCache("tasks")
# Convert to TaskItem model # Convert to TaskItem model
task = TaskItem( task = TaskItem(
id=createdTask["id"], id=createdTask["id"],
@ -1130,6 +1162,9 @@ class ChatObjects:
# Update task in database # Update task in database
updatedTask = self.db.recordModify("tasks", taskId, taskData) updatedTask = self.db.recordModify("tasks", taskId, taskData)
# Clear cache to ensure fresh data
self._clearTableCache("tasks")
# Convert to TaskItem model # Convert to TaskItem model
return TaskItem( return TaskItem(
id=updatedTask["id"], id=updatedTask["id"],
@ -1178,6 +1213,9 @@ class ChatObjects:
if taskId in workflowTasks: if taskId in workflowTasks:
workflowTasks.remove(taskId) workflowTasks.remove(taskId)
self.updateWorkflow(task.workflowId, {"tasks": workflowTasks}) self.updateWorkflow(task.workflowId, {"tasks": workflowTasks})
# Clear cache to ensure fresh data
self._clearTableCache("tasks")
return True return True
return False return False

View file

@ -235,6 +235,10 @@ class ComponentObjects:
"""Delegate to access control module.""" """Delegate to access control module."""
return self.access.canModify(table, recordId) return self.access.canModify(table, recordId)
def _clearTableCache(self, table: str) -> None:
"""Clears the cache for a specific table to ensure fresh data."""
self.db.clearTableCache(table)
# Utilities # Utilities
def getInitialId(self, table: str) -> Optional[str]: def getInitialId(self, table: str) -> Optional[str]:
@ -279,6 +283,9 @@ class ComponentObjects:
if not createdRecord or not createdRecord.get("id"): if not createdRecord or not createdRecord.get("id"):
raise ValueError("Failed to create prompt record") raise ValueError("Failed to create prompt record")
# Clear cache to ensure fresh data
self._clearTableCache("prompts")
return createdRecord return createdRecord
def updatePrompt(self, promptId: str, updateData: Dict[str, Any]) -> Dict[str, Any]: def updatePrompt(self, promptId: str, updateData: Dict[str, Any]) -> Dict[str, Any]:
@ -292,6 +299,9 @@ class ComponentObjects:
# Update prompt record directly with the update data # Update prompt record directly with the update data
self.db.recordModify("prompts", promptId, updateData) self.db.recordModify("prompts", promptId, updateData)
# Clear cache to ensure fresh data
self._clearTableCache("prompts")
# Get updated prompt # Get updated prompt
updatedPrompt = self.getPrompt(promptId) updatedPrompt = self.getPrompt(promptId)
if not updatedPrompt: if not updatedPrompt:
@ -313,7 +323,13 @@ class ComponentObjects:
if not self._canModify("prompts", promptId): if not self._canModify("prompts", promptId):
raise PermissionError(f"No permission to delete prompt {promptId}") raise PermissionError(f"No permission to delete prompt {promptId}")
return self.db.recordDelete("prompts", promptId) # Delete prompt
success = self.db.recordDelete("prompts", promptId)
# Clear cache to ensure fresh data
self._clearTableCache("prompts")
return success
# File Utilities # File Utilities
@ -528,6 +544,10 @@ class ComponentObjects:
# Store in database # Store in database
self.db.recordCreate("files", fileItem.to_dict()) self.db.recordCreate("files", fileItem.to_dict())
# Clear cache to ensure fresh data
self._clearTableCache("files")
return fileItem return fileItem
def updateFile(self, fileId: str, updateData: Dict[str, Any]) -> Dict[str, Any]: def updateFile(self, fileId: str, updateData: Dict[str, Any]) -> Dict[str, Any]:
@ -545,7 +565,12 @@ class ComponentObjects:
updateData["filename"] = self._generateUniqueFilename(updateData["filename"], fileId) updateData["filename"] = self._generateUniqueFilename(updateData["filename"], fileId)
# Update file # Update file
return self.db.recordModify("files", fileId, updateData) success = self.db.recordModify("files", fileId, updateData)
# Clear cache to ensure fresh data
self._clearTableCache("files")
return success
def deleteFile(self, fileId: str) -> bool: def deleteFile(self, fileId: str) -> bool:
"""Deletes a file if user has access.""" """Deletes a file if user has access."""
@ -576,7 +601,12 @@ class ComponentObjects:
logger.warning(f"Error deleting FileData for file {fileId}: {str(e)}") logger.warning(f"Error deleting FileData for file {fileId}: {str(e)}")
# Delete the FileItem entry # Delete the FileItem entry
return self.db.recordDelete("files", fileId) success = self.db.recordDelete("files", fileId)
# Clear cache to ensure fresh data
self._clearTableCache("files")
return success
except FileNotFoundError as e: except FileNotFoundError as e:
raise raise
@ -634,6 +664,10 @@ class ComponentObjects:
} }
self.db.recordCreate("fileData", fileDataObj) self.db.recordCreate("fileData", fileDataObj)
# Clear cache to ensure fresh data
self._clearTableCache("fileData")
logger.debug(f"Successfully stored data for file {fileId} (base64Encoded: {base64Encoded})") logger.debug(f"Successfully stored data for file {fileId} (base64Encoded: {base64Encoded})")
return True return True
except Exception as e: except Exception as e:
@ -668,8 +702,25 @@ class ComponentObjects:
# Decode base64 to bytes # Decode base64 to bytes
return base64.b64decode(data) return base64.b64decode(data)
else: else:
# Convert text to bytes # Check if this is supposed to be a binary file based on mime type
return data.encode('utf-8') mimeType = file.mimeType
isTextFormat = self.isTextMimeType(mimeType)
if isTextFormat:
# This is a text file, encode to bytes as expected
return data.encode('utf-8')
else:
# This is a binary file that was incorrectly stored as text
# Try to decode it as if it was base64 (common fallback scenario)
try:
logger.warning(f"Binary file {fileId} ({mimeType}) was stored as text, attempting base64 decode")
return base64.b64decode(data)
except Exception as base64_error:
logger.error(f"Failed to decode binary file {fileId} as base64: {str(base64_error)}")
# Last resort: return the data as-is (might be corrupted)
logger.warning(f"Returning raw data for file {fileId} - file may be corrupted")
return data.encode('utf-8') if isinstance(data, str) else data
except Exception as e: except Exception as e:
logger.error(f"Error processing file data for {fileId}: {str(e)}") logger.error(f"Error processing file data for {fileId}: {str(e)}")
return None return None
@ -810,7 +861,11 @@ class ComponentObjects:
self.db.recordCreate("fileData", dataUpdate) self.db.recordCreate("fileData", dataUpdate)
logger.debug(f"Created new file data for file ID {fileId} (base64Encoded: {base64Encoded})") logger.debug(f"Created new file data for file ID {fileId} (base64Encoded: {base64Encoded})")
# Clear cache to ensure fresh data
self._clearTableCache("fileData")
return True return True
except Exception as e: except Exception as e:
logger.error(f"Error updating data for file {fileId}: {str(e)}") logger.error(f"Error updating data for file {fileId}: {str(e)}")
return False return False

File diff suppressed because it is too large Load diff

View file

@ -34,8 +34,7 @@ async def get_connections(
interface = getInterface(currentUser) interface = getInterface(currentUser)
# Clear connections cache to ensure fresh data # Clear connections cache to ensure fresh data
if "connections" in interface.db._tablesCache: interface.db.clearTableCache("connections")
del interface.db._tablesCache["connections"]
if currentUser.privilege in ['admin', 'sysadmin']: if currentUser.privilege in ['admin', 'sysadmin']:
# Admins can see all connections # Admins can see all connections
@ -107,6 +106,9 @@ async def create_connection(
# Save connection record # Save connection record
interface.db.recordModify("connections", connection.id, connection_dict) interface.db.recordModify("connections", connection.id, connection_dict)
# Clear cache to ensure fresh data
interface.db.clearTableCache("connections")
return connection return connection
except HTTPException: except HTTPException:
@ -174,9 +176,13 @@ async def update_connection(
elif isinstance(connection_dict[field], (int, float)): elif isinstance(connection_dict[field], (int, float)):
connection_dict[field] = datetime.fromtimestamp(connection_dict[field]).isoformat() connection_dict[field] = datetime.fromtimestamp(connection_dict[field]).isoformat()
# Update connection record # Update connection
interface.db.recordModify("connections", connectionId, connection_dict) interface.db.recordModify("connections", connectionId, connection_dict)
# Clear cache to ensure fresh data
interface.db.clearTableCache("connections")
# Get updated connection
return connection return connection
except HTTPException: except HTTPException:
@ -306,6 +312,9 @@ async def disconnect_service(
# Update connection record # Update connection record
interface.db.recordModify("connections", connectionId, connection.to_dict()) interface.db.recordModify("connections", connectionId, connection.to_dict())
# Clear cache to ensure fresh data
interface.db.clearTableCache("connections")
return {"message": "Service disconnected successfully"} return {"message": "Service disconnected successfully"}
except HTTPException: except HTTPException:

View file

@ -332,6 +332,9 @@ async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse
# Update connection record directly # Update connection record directly
rootInterface.db.recordModify("connections", connection_id, connection.to_dict()) rootInterface.db.recordModify("connections", connection_id, connection.to_dict())
# Clear cache to ensure fresh data
rootInterface.db.clearTableCache("connections")
# Save token # Save token
token = Token( token = Token(
userId=user.id, # Use local user's ID userId=user.id, # Use local user's ID

View file

@ -314,6 +314,9 @@ async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse
# Update connection record directly # Update connection record directly
rootInterface.db.recordModify("connections", connection_id, connection.to_dict()) rootInterface.db.recordModify("connections", connection_id, connection.to_dict())
# Clear cache to ensure fresh data
rootInterface.db.clearTableCache("connections")
# Save token # Save token
token = Token( token = Token(
userId=user.id, # Use local user's ID userId=user.id, # Use local user's ID

View file

@ -163,6 +163,9 @@ def createUserSession(userId: str, tokenId: str, request: Request) -> Session:
# Save session to database # Save session to database
appInterface.db.recordCreate("sessions", session.to_dict()) appInterface.db.recordCreate("sessions", session.to_dict())
# Clear cache to ensure fresh data
appInterface.db.clearTableCache("sessions")
# Log auth event # Log auth event
event = AuthEvent( event = AuthEvent(
userId=userId, userId=userId,
@ -173,6 +176,9 @@ def createUserSession(userId: str, tokenId: str, request: Request) -> Session:
) )
appInterface.db.recordCreate("auth_events", event.to_dict()) appInterface.db.recordCreate("auth_events", event.to_dict())
# Clear cache to ensure fresh data
appInterface.db.clearTableCache("auth_events")
return session return session
def logAuthEvent(userId: str, eventType: str, details: Dict[str, Any], request: Request) -> None: def logAuthEvent(userId: str, eventType: str, details: Dict[str, Any], request: Request) -> None:
@ -190,6 +196,9 @@ def logAuthEvent(userId: str, eventType: str, details: Dict[str, Any], request:
# Save event to database # Save event to database
appInterface.db.recordCreate("auth_events", event.to_dict()) appInterface.db.recordCreate("auth_events", event.to_dict())
# Clear cache to ensure fresh data
appInterface.db.clearTableCache("auth_events")
def validateSession(sessionId: str) -> bool: def validateSession(sessionId: str) -> bool:
"""Validate a user session.""" """Validate a user session."""
appInterface = getRootInterface() appInterface = getRootInterface()
@ -207,6 +216,9 @@ def validateSession(sessionId: str) -> bool:
"lastActivity": datetime.now(timezone.utc) "lastActivity": datetime.now(timezone.utc)
}) })
# Clear cache to ensure fresh data
appInterface.db.clearTableCache("sessions")
return True return True
def revokeSession(sessionId: str) -> None: def revokeSession(sessionId: str) -> None:
@ -216,6 +228,9 @@ def revokeSession(sessionId: str) -> None:
# Delete session # Delete session
appInterface.db.recordDelete("sessions", sessionId) appInterface.db.recordDelete("sessions", sessionId)
# Clear cache to ensure fresh data
appInterface.db.clearTableCache("sessions")
def revokeAllUserSessions(userId: str) -> None: def revokeAllUserSessions(userId: str) -> None:
"""Revoke all sessions for a user.""" """Revoke all sessions for a user."""
appInterface = getRootInterface() appInterface = getRootInterface()
@ -226,3 +241,6 @@ def revokeAllUserSessions(userId: str) -> None:
# Delete each session # Delete each session
for session in sessions: for session in sessions:
appInterface.db.recordDelete("sessions", session["id"]) appInterface.db.recordDelete("sessions", session["id"])
# Clear cache to ensure fresh data
appInterface.db.clearTableCache("sessions")