From 1ce125ac75e8210a7a3aa946c32ff3031ecdc4ff Mon Sep 17 00:00:00 2001 From: patrick-motsch Date: Thu, 12 Feb 2026 01:40:00 +0100 Subject: [PATCH] fixes --- .../automation/interfaceFeatureAutomation.py | 36 +++++++++---------- modules/interfaces/interfaceBootstrap.py | 1 + modules/interfaces/interfaceDbChat.py | 31 ++++++++-------- modules/routes/routeDataWorkflows.py | 7 ++-- 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/modules/features/automation/interfaceFeatureAutomation.py b/modules/features/automation/interfaceFeatureAutomation.py index 87c1c3ef..79fbd8ad 100644 --- a/modules/features/automation/interfaceFeatureAutomation.py +++ b/modules/features/automation/interfaceFeatureAutomation.py @@ -501,25 +501,25 @@ class AutomationObjects: System templates (isSystem=True) are always included (read-only for non-SysAdmin). Instance templates (featureInstanceId matches) are included with RBAC filtering. """ - # 1. System templates — always visible to all users - systemTemplates = self.db.getRecordset( - AutomationTemplate, - recordFilter={"isSystem": True} - ) + # Load ALL templates and filter in Python. + # Reason: seeded/legacy templates may have isSystem=NULL (not False/True), + # which breaks SQL equality filters (NULL != True AND NULL != False). + allTemplates = self.db.getRecordset(AutomationTemplate) - # 2. Instance templates — scoped to current feature instance, RBAC-filtered - instanceTemplates = [] - if self.featureInstanceId: - allInstanceTemplates = self.db.getRecordset( - AutomationTemplate, - recordFilter={"featureInstanceId": self.featureInstanceId, "isSystem": False} - ) - # Apply RBAC filtering on instance templates - for t in allInstanceTemplates: - instanceTemplates.append(t) - - # Combine: system first, then instance - filteredTemplates = systemTemplates + instanceTemplates + filteredTemplates = [] + for t in allTemplates: + isSystem = t.get("isSystem") + fid = t.get("featureInstanceId") + + if isSystem is True: + # System templates — always visible to all users + filteredTemplates.append(t) + elif fid and fid == self.featureInstanceId: + # Instance templates — scoped to current feature instance + filteredTemplates.append(t) + elif not fid: + # Global/legacy templates (no featureInstanceId) — visible to all users + filteredTemplates.append(t) # Enrich with user names self._enrichTemplatesWithUserName(filteredTemplates) diff --git a/modules/interfaces/interfaceBootstrap.py b/modules/interfaces/interfaceBootstrap.py index 5c595613..141f7da2 100644 --- a/modules/interfaces/interfaceBootstrap.py +++ b/modules/interfaces/interfaceBootstrap.py @@ -166,6 +166,7 @@ def initAutomationTemplates(dbApp: DatabaseConnector, adminUserId: Optional[str] "label": labelDict, "overview": overviewDict, "template": json.dumps(templateContent), # Store entire template JSON + "isSystem": True, # Seeded templates are system-level, visible to all users } try: diff --git a/modules/interfaces/interfaceDbChat.py b/modules/interfaces/interfaceDbChat.py index 53a33bc3..56743b50 100644 --- a/modules/interfaces/interfaceDbChat.py +++ b/modules/interfaces/interfaceDbChat.py @@ -1242,27 +1242,23 @@ class ChatObjects: if not self.checkRbacPermission(ChatWorkflow, "update", workflowId): raise PermissionError(f"No permission to modify workflow {workflowId}") - # Check if the message exists - messages = self.getMessages(workflowId) - message = next((m for m in messages if m.get("id") == messageId), None) + # Check if the message exists (bypass RBAC -- workflow access already verified above) + matchingMessages = self.db.getRecordset(ChatMessage, recordFilter={"id": messageId, "workflowId": workflowId}) - if not message: + if not matchingMessages: logger.warning(f"Message {messageId} for workflow {workflowId} not found") return False # CASCADE DELETE: Delete all related data first - # 1. Delete message stats - existing_stats = self._getRecordset(ChatStat, recordFilter={"messageId": messageId}) - for stat in existing_stats: - self.db.recordDelete(ChatStat, stat["id"]) - - # 2. Delete message documents (but NOT the files!) - existing_docs = self._getRecordset(ChatDocument, recordFilter={"messageId": messageId}) + # 1. Delete message documents (but NOT the files themselves) + # Bypass RBAC -- workflow access already verified, child records may have different _createdBy + existing_docs = self.db.getRecordset(ChatDocument, recordFilter={"messageId": messageId}) for doc in existing_docs: self.db.recordDelete(ChatDocument, doc["id"]) - # 3. Finally delete the message itself + # 2. Finally delete the message itself + # Note: ChatStat has no messageId field -- stats are workflow-level, not message-level success = self.db.recordDelete(ChatMessage, messageId) return success @@ -1285,11 +1281,14 @@ class ChatObjects: # Get documents for this message from normalized table - documents = self._getRecordset(ChatDocument, recordFilter={"messageId": messageId}) + # Bypass RBAC -- workflow access already verified, child records may have different _createdBy + documents = self.db.getRecordset(ChatDocument, recordFilter={"messageId": messageId}) if not documents: - logger.warning(f"No documents found for message {messageId}") - return False + # No ChatDocument records -- documents may be stored inline on the message (legacy). + # The frontend handles removal optimistically, file itself is preserved. + logger.debug(f"No ChatDocument records for message {messageId} -- inline/legacy data, nothing to delete") + return True # Find and delete the specific document removed = False @@ -1316,6 +1315,8 @@ class ChatObjects: if not removed: logger.warning(f"No matching file {fileId} found in message {messageId}") return False + + return True except Exception as e: logger.error(f"Error removing file {fileId} from message {messageId}: {str(e)}") diff --git a/modules/routes/routeDataWorkflows.py b/modules/routes/routeDataWorkflows.py index da18d23c..be580ec9 100644 --- a/modules/routes/routeDataWorkflows.py +++ b/modules/routes/routeDataWorkflows.py @@ -549,11 +549,8 @@ def delete_workflow_message( detail=f"Message with ID {messageId} not found in workflow {workflowId}" ) - # Update workflow's messageIds - messageIds = workflow.get("messageIds", []) - if messageId in messageIds: - messageIds.remove(messageId) - interfaceDbChat.updateWorkflow(workflowId, {"messageIds": messageIds}) + # Messages are stored in ChatMessage table linked by workflowId -- + # no messageIds list on ChatWorkflow to update. return { "workflowId": workflowId,