199 lines
6.4 KiB
Go
199 lines
6.4 KiB
Go
package automation
|
|
|
|
import "fmt"
|
|
|
|
var chatwootToLocalFilterOperator = map[string]string{
|
|
"equal_to": "equal",
|
|
"not_equal_to": "not_equal",
|
|
}
|
|
|
|
// NormalizeFilterOperator converts Chatwoot frontend operator names to the
|
|
// internal names used by the condition matcher.
|
|
func NormalizeFilterOperator(op string) string {
|
|
if normalized, ok := chatwootToLocalFilterOperator[op]; ok {
|
|
return normalized
|
|
}
|
|
return op
|
|
}
|
|
|
|
// ChatwootFilterOperator converts internal operator names to the dashboard
|
|
// values used by Chatwoot's automation builder.
|
|
func ChatwootFilterOperator(op string) string {
|
|
switch op {
|
|
case "equal":
|
|
return "equal_to"
|
|
case "not_equal":
|
|
return "not_equal_to"
|
|
default:
|
|
return op
|
|
}
|
|
}
|
|
|
|
// NormalizeConditions fills Chatwoot's attribute_key into the internal
|
|
// Attribute field and normalizes filter operator aliases before validation or
|
|
// execution.
|
|
func NormalizeConditions(conditions Conditions) Conditions {
|
|
for i := range conditions {
|
|
if conditions[i].Attribute == "" {
|
|
conditions[i].Attribute = conditions[i].AttributeKey
|
|
}
|
|
if conditions[i].AttributeKey == "" {
|
|
conditions[i].AttributeKey = conditions[i].Attribute
|
|
}
|
|
conditions[i].FilterOperator = NormalizeFilterOperator(conditions[i].FilterOperator)
|
|
}
|
|
return conditions
|
|
}
|
|
|
|
// WhitelistedConditionAttributes defines the set of valid condition attributes.
|
|
// Reference: Chatwoot ConditionValidationService — validates condition keys against whitelisted attributes.
|
|
var WhitelistedConditionAttributes = map[string]bool{
|
|
// Conversation attributes
|
|
"status": true,
|
|
"priority": true,
|
|
"assignee_id": true,
|
|
"team_id": true,
|
|
"inbox_id": true,
|
|
"contact_id": true,
|
|
"channel_type": true,
|
|
"labels": true,
|
|
"contact": true,
|
|
|
|
// Message/content attributes
|
|
"content": true,
|
|
"message_type": true,
|
|
"private_note": true,
|
|
"email": true,
|
|
"mail_subject": true,
|
|
"phone_number": true,
|
|
"company_name": true,
|
|
"referer": true,
|
|
"campaigns": true,
|
|
"conversation_language": true,
|
|
|
|
// Country/locale attributes (Chatwoot extensions)
|
|
"country_code": true,
|
|
"browser_language": true,
|
|
}
|
|
|
|
// WhitelistedFilterOperators defines the set of valid filter operators.
|
|
// Reference: Chatwoot filter_operator enum
|
|
var WhitelistedFilterOperators = map[string]bool{
|
|
"equal": true,
|
|
"equal_to": true,
|
|
"not_equal": true,
|
|
"not_equal_to": true,
|
|
"contains": true,
|
|
"does_not_contain": true,
|
|
"is_present": true,
|
|
"is_not_present": true,
|
|
"attribute_changed": true,
|
|
"is_greater_than": true,
|
|
"is_less_than": true,
|
|
"starts_with": true,
|
|
"days_before": true,
|
|
}
|
|
|
|
// WhitelistedQueryOperators defines the set of valid query operators for combining conditions.
|
|
var WhitelistedQueryOperators = map[string]bool{
|
|
"and": true,
|
|
"or": true,
|
|
}
|
|
|
|
// WhitelistedActionTypes defines the set of valid automation action types.
|
|
// Reference: Chatwoot AutomationRule action_name enum
|
|
var WhitelistedActionTypes = map[string]bool{
|
|
"assign_agent": true,
|
|
"assign_team": true, // Chatwoot: assign conversation to a team
|
|
"change_status": true,
|
|
"add_label": true,
|
|
"remove_label": true,
|
|
"send_message": true,
|
|
"send_attachment": true,
|
|
"send_email_to_contact": true,
|
|
"send_email_to_team": true,
|
|
"send_email_transcript": true,
|
|
"send_webhook_event": true,
|
|
"mute_conversation": true,
|
|
"snooze_conversation": true, // Chatwoot: snooze until a specified time
|
|
"resolve_conversation": true, // Chatwoot: resolve immediately
|
|
"open_conversation": true,
|
|
"pending_conversation": true,
|
|
"change_priority": true,
|
|
"add_sla": true,
|
|
"add_private_note": true,
|
|
"remove_assigned_agent": true,
|
|
"remove_assigned_team": true,
|
|
}
|
|
|
|
// ValidateConditions checks that all conditions use valid attributes, filter operators,
|
|
// and query operators.
|
|
// Reference: Chatwoot ConditionValidationService — validates condition keys against whitelisted attributes
|
|
func ValidateConditions(conditions Conditions) error {
|
|
conditions = NormalizeConditions(conditions)
|
|
for i, c := range conditions {
|
|
// Validate attribute
|
|
if !WhitelistedConditionAttributes[c.Attribute] {
|
|
return fmt.Errorf("condition[%d]: invalid attribute '%s'", i, c.Attribute)
|
|
}
|
|
|
|
// Validate filter operator
|
|
filterOperator := NormalizeFilterOperator(c.FilterOperator)
|
|
if !WhitelistedFilterOperators[filterOperator] {
|
|
return fmt.Errorf("condition[%d]: invalid filter_operator '%s'", i, c.FilterOperator)
|
|
}
|
|
|
|
// Validate query operator (defaults to "and" if empty)
|
|
if c.QueryOperator != "" {
|
|
if !WhitelistedQueryOperators[c.QueryOperator] {
|
|
return fmt.Errorf("condition[%d]: invalid query_operator '%s'", i, c.QueryOperator)
|
|
}
|
|
}
|
|
|
|
// Validate values — attribute_changed requires from/to values
|
|
if filterOperator == "attribute_changed" {
|
|
if len(c.Values) == 0 {
|
|
return fmt.Errorf("condition[%d]: attribute_changed requires at least one value (to)", i)
|
|
}
|
|
}
|
|
|
|
if err := validateAttributeOperator(c.Attribute, filterOperator); err != nil {
|
|
return fmt.Errorf("condition[%d]: %w", i, err)
|
|
}
|
|
|
|
// is_present / is_not_present should not need values
|
|
if filterOperator == "is_present" || filterOperator == "is_not_present" {
|
|
// Values are optional for presence checks — not enforced
|
|
} else if len(c.Values) == 0 {
|
|
return fmt.Errorf("condition[%d]: filter_operator '%s' requires at least one value", i, c.FilterOperator)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateAttributeOperator(attribute string, operator string) error {
|
|
switch attribute {
|
|
case "message_type", "private_note":
|
|
if operator != "equal" && operator != "not_equal" {
|
|
return fmt.Errorf("attribute '%s' does not support filter_operator '%s'", attribute, operator)
|
|
}
|
|
case "content":
|
|
if operator != "equal" && operator != "not_equal" && operator != "contains" && operator != "does_not_contain" {
|
|
return fmt.Errorf("attribute 'content' does not support filter_operator '%s'", operator)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateActions checks that all actions use valid action types.
|
|
// Reference: Chatwoot AutomationRule action_name validation
|
|
func ValidateActions(actions Actions) error {
|
|
for i, a := range actions {
|
|
if !WhitelistedActionTypes[a.ActionName] {
|
|
return fmt.Errorf("action[%d]: invalid action_name '%s'", i, a.ActionName)
|
|
}
|
|
}
|
|
return nil
|
|
}
|