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,