package automation import ( "context" "encoding/json" "gorm.io/datatypes" ) // =========================== // Automation Execution Logging // =========================== // ExecutionLogService records automation rule and macro execution events for audit trail. // Reference: Chatwoot does not explicitly store automation execution logs — // gochat adds this for debugging, monitoring, and compliance per M6 requirements. type ExecutionLogService struct { db DBProvider } // ActionExecutionResult records the result of one action within a rule execution. type ActionExecutionResult struct { ActionName string `json:"action_name"` Status string `json:"status"` Error string `json:"error,omitempty"` DeliveryType string `json:"delivery_type,omitempty"` Target string `json:"target,omitempty"` Attempts int `json:"attempts,omitempty"` ResponseCode int `json:"response_code,omitempty"` ResponseBody string `json:"response_body,omitempty"` Retryable bool `json:"retryable,omitempty"` Queued bool `json:"queued,omitempty"` } // NewExecutionLogService creates a new ExecutionLogService. func NewExecutionLogService(db DBProvider) *ExecutionLogService { return &ExecutionLogService{db: db} } // LogRuleExecution records an automation rule execution event. func (s *ExecutionLogService) LogRuleExecution(ctx context.Context, accountID, ruleID, conversationID uint, status string, actionsExecuted, actionsFailed int, errorMsg string) error { return s.LogRuleExecutionWithResults(ctx, accountID, ruleID, conversationID, "", status, actionsExecuted, actionsFailed, errorMsg, nil) } // LogRuleExecutionWithResults records a rule evaluation with optional event name and per-action results. func (s *ExecutionLogService) LogRuleExecutionWithResults(ctx context.Context, accountID, ruleID, conversationID uint, eventName, status string, actionsExecuted, actionsFailed int, errorMsg string, actionResults []ActionExecutionResult) error { resultsJSON := datatypes.JSON([]byte("[]")) if actionResults != nil { payload, err := json.Marshal(actionResults) if err != nil { return err } resultsJSON = datatypes.JSON(payload) } record := &AutomationExecution{ AccountID: accountID, RuleID: ruleID, ConversationID: conversationID, EventName: eventName, Status: status, ActionsExecuted: actionsExecuted, ActionsFailed: actionsFailed, ActionResults: resultsJSON, ErrorMessage: errorMsg, } return s.db.DB().WithContext(ctx).Select( "AccountID", "RuleID", "ConversationID", "EventName", "Status", "ActionsExecuted", "ActionsFailed", "ActionResults", "ErrorMessage", ).Create(record).Error } // ListRuleExecutions retrieves execution logs for an automation rule, ordered by most recent. func (s *ExecutionLogService) ListRuleExecutions(ctx context.Context, accountID, ruleID uint, limit int) ([]AutomationExecution, error) { var logs []AutomationExecution query := s.db.DB().WithContext(ctx). Where("account_id = ? AND rule_id = ?", accountID, ruleID). Order("created_at DESC") if limit > 0 { query = query.Limit(limit) } if err := query.Find(&logs).Error; err != nil { return nil, err } return logs, nil } // ListConversationExecutions retrieves execution logs for a conversation, ordered by most recent. func (s *ExecutionLogService) ListConversationExecutions(ctx context.Context, accountID, conversationID uint, limit int) ([]AutomationExecution, error) { var logs []AutomationExecution query := s.db.DB().WithContext(ctx). Where("account_id = ? AND conversation_id = ?", accountID, conversationID). Order("created_at DESC") if limit > 0 { query = query.Limit(limit) } if err := query.Find(&logs).Error; err != nil { return nil, err } return logs, nil } // LogMacroExecution records a macro execution event (uses MacroExecution model). func (s *ExecutionLogService) LogMacroExecution(ctx context.Context, macroID, conversationID, userID uint) error { record := &MacroExecution{ MacroID: macroID, ConversationID: conversationID, ExecutedByID: userID, } return s.db.DB().WithContext(ctx).Create(record).Error } // ListMacroExecutions retrieves execution logs for a macro, ordered by most recent. func (s *ExecutionLogService) ListMacroExecutions(ctx context.Context, macroID uint, limit int) ([]MacroExecution, error) { var logs []MacroExecution query := s.db.DB().WithContext(ctx). Where("macro_id = ?", macroID). Order("id DESC") if limit > 0 { query = query.Limit(limit) } if err := query.Find(&logs).Error; err != nil { return nil, err } return logs, nil } // ExecutionStatus constants. const ( ExecutionStatusSuccess = "success" ExecutionStatusPartial = "partial" // some actions succeeded, some failed ExecutionStatusFailed = "failed" ExecutionStatusSkipped = "skipped" )