ultisuite-backend/internal/mail/rules/workflow.go
R3D347HR4Y 1d063237b9
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(transcription): integrate Faster Whisper for Jitsi transcriptions
- Added support for Faster Whisper transcription via Jigasi and Skynet.
- Updated .env.example to include new environment variables for transcription settings.
- Enhanced Jitsi Docker Compose configuration to include Skynet and Jigasi services.
- Introduced new API endpoints for managing organizational folders in the drive service.
- Updated Nextcloud initialization script to enable external file mounting.
- Improved error handling and response structures in the drive API.
- Added new properties for organization settings related to transcription and agenda management.
2026-06-12 19:10:18 +02:00

307 lines
7.6 KiB
Go

package rules
import (
"encoding/json"
"fmt"
"strings"
)
const WorkflowVersion = 1
type RuleKind string
const (
RuleKindRule RuleKind = "rule"
RuleKindFunction RuleKind = "function"
)
type TriggerType string
const (
TriggerMessageReceived TriggerType = "message_received"
TriggerLabelAdded TriggerType = "label_added"
TriggerLabelRemoved TriggerType = "label_removed"
TriggerDriveFileCreated TriggerType = "drive_file_created"
TriggerDriveFileUpdated TriggerType = "drive_file_updated"
TriggerDriveFileDeleted TriggerType = "drive_file_deleted"
TriggerDriveFileMoved TriggerType = "drive_file_moved"
TriggerDriveShareUpdated TriggerType = "drive_share_updated"
TriggerContactCreated TriggerType = "contact_created"
TriggerContactUpdated TriggerType = "contact_updated"
TriggerContactDeleted TriggerType = "contact_deleted"
TriggerCalendarEventCreated TriggerType = "calendar_event_created"
TriggerCalendarEventUpdated TriggerType = "calendar_event_updated"
TriggerCalendarEventDeleted TriggerType = "calendar_event_deleted"
TriggerCalendarEventResponse TriggerType = "calendar_event_response"
)
type Trigger struct {
Type TriggerType `json:"type"`
FolderID string `json:"folder_id,omitempty"`
Label string `json:"label,omitempty"`
AccountID string `json:"account_id,omitempty"`
FolderPath string `json:"folder_path,omitempty"`
ContactLabel string `json:"contact_label,omitempty"`
CalendarID string `json:"calendar_id,omitempty"`
}
type TriggerGroup struct {
Operator string `json:"operator"` // "or"
Groups []TriggerAnd `json:"groups"`
}
type TriggerAnd struct {
Operator string `json:"operator"` // "and"
Items []Trigger `json:"items"`
}
type ExecVariable struct {
Name string `json:"name"`
Type string `json:"type"` // string, number, boolean
Default string `json:"default,omitempty"`
}
type WorkflowNode struct {
ID string `json:"id"`
Type string `json:"type"`
Position json.RawMessage `json:"position,omitempty"`
Data json.RawMessage `json:"data"`
}
type WorkflowEdge struct {
ID string `json:"id"`
Source string `json:"source"`
Target string `json:"target"`
SourceHandle string `json:"sourceHandle,omitempty"`
}
type Workflow struct {
Version int `json:"version"`
Kind RuleKind `json:"kind"`
Triggers TriggerGroup `json:"triggers"`
Variables []ExecVariable `json:"variables,omitempty"`
Nodes []WorkflowNode `json:"nodes"`
Edges []WorkflowEdge `json:"edges"`
}
type ConditionNodeData struct {
Field string `json:"field"`
Operator string `json:"operator"`
Value string `json:"value"`
}
type LabelCheckNodeData struct {
Label string `json:"label"`
Operator string `json:"operator"` // has, not_has
}
type SwitchCase struct {
Value string `json:"value"`
Label string `json:"label,omitempty"`
}
type SwitchNodeData struct {
Field string `json:"field"`
Cases []SwitchCase `json:"cases"`
}
type LLMCheckNodeData struct {
Prompt string `json:"prompt"`
Provider string `json:"provider,omitempty"`
Model string `json:"model,omitempty"`
}
type ActionItem struct {
Type string `json:"type"`
Value string `json:"value"`
}
type ActionsNodeData struct {
Actions []ActionItem `json:"actions"`
}
type SetVarNodeData struct {
Name string `json:"name"`
Value string `json:"value"`
}
type CallRuleNodeData struct {
RuleID string `json:"rule_id"`
}
type EventContext struct {
Type TriggerType
FolderID string
Label string
FolderPath string
ContactLabel string
CalendarID string
// Drive payload (when domain is drive)
DriveFileName string
DriveFilePath string
DriveMimeType string
DriveFileSize int64
DriveIsFolder bool
// Contact payload (when domain is contacts)
ContactID string
ContactBookID string
ContactName string
ContactEmail string
ContactPhone string
ContactOrg string
// Calendar payload (when domain is agenda)
CalendarEventTitle string
CalendarEventLocation string
CalendarEventOrganizer string
CalendarEventAttendee string
CalendarEventStart string
CalendarEventEnd string
CalendarEventAllDay bool
CalendarEventHasVideo bool
CalendarEventUID string
}
func ParseWorkflow(raw []byte) (*Workflow, error) {
if len(raw) == 0 || string(raw) == "null" {
return nil, nil
}
var wf Workflow
if err := json.Unmarshal(raw, &wf); err != nil {
return nil, fmt.Errorf("parse workflow: %w", err)
}
if wf.Version == 0 {
wf.Version = WorkflowVersion
}
if wf.Kind == "" {
wf.Kind = RuleKindRule
}
return &wf, nil
}
func (wf *Workflow) nodeMap() map[string]WorkflowNode {
m := make(map[string]WorkflowNode, len(wf.Nodes))
for _, n := range wf.Nodes {
m[n.ID] = n
}
return m
}
func (wf *Workflow) outgoingEdges(nodeID string) []WorkflowEdge {
var out []WorkflowEdge
for _, e := range wf.Edges {
if e.Source == nodeID {
out = append(out, e)
}
}
return out
}
func (wf *Workflow) nextNode(nodeID, handle string) string {
for _, e := range wf.Edges {
if e.Source == nodeID && e.SourceHandle == handle {
return e.Target
}
}
return ""
}
func (wf *Workflow) nextDefault(nodeID string) string {
for _, e := range wf.Edges {
if e.Source == nodeID && e.SourceHandle == "" {
return e.Target
}
}
return ""
}
func (wf *Workflow) findStartNode() string {
for _, n := range wf.Nodes {
if n.Type == "start" {
return n.ID
}
}
return ""
}
func matchesTriggers(triggers TriggerGroup, msg *Message, evt *EventContext) bool {
if len(triggers.Groups) == 0 {
return true
}
for _, group := range triggers.Groups {
if matchesTriggerAnd(group, msg, evt) {
return true
}
}
return false
}
func matchesTriggerAnd(group TriggerAnd, msg *Message, evt *EventContext) bool {
if len(group.Items) == 0 {
return true
}
for _, t := range group.Items {
if !matchTrigger(t, msg, evt) {
return false
}
}
return true
}
func matchTrigger(t Trigger, msg *Message, evt *EventContext) bool {
switch t.Type {
case TriggerMessageReceived:
if evt != nil && evt.Type != TriggerMessageReceived && evt.Type != "" {
return false
}
if t.AccountID != "" && msg.AccountID != "" && t.AccountID != msg.AccountID {
return false
}
if t.FolderID != "" && msg.FolderID != "" && t.FolderID != msg.FolderID {
return false
}
return true
case TriggerLabelAdded:
if evt == nil || evt.Type != TriggerLabelAdded {
return false
}
if t.Label != "" && t.Label != evt.Label {
return false
}
return true
case TriggerLabelRemoved:
if evt == nil || evt.Type != TriggerLabelRemoved {
return false
}
if t.Label != "" && t.Label != evt.Label {
return false
}
return true
case TriggerDriveFileCreated, TriggerDriveFileUpdated, TriggerDriveFileDeleted, TriggerDriveFileMoved, TriggerDriveShareUpdated:
if evt == nil || evt.Type != t.Type {
return false
}
if t.FolderPath != "" && evt.FolderPath != "" && !strings.HasPrefix(evt.DriveFilePath, t.FolderPath) {
return false
}
return true
case TriggerContactCreated, TriggerContactUpdated, TriggerContactDeleted:
if evt == nil || evt.Type != t.Type {
return false
}
if t.ContactLabel != "" && evt.ContactLabel != "" && t.ContactLabel != evt.ContactLabel {
return false
}
return true
case TriggerCalendarEventCreated, TriggerCalendarEventUpdated, TriggerCalendarEventDeleted, TriggerCalendarEventResponse:
if evt == nil || evt.Type != t.Type {
return false
}
if t.CalendarID != "" && evt.CalendarID != "" && t.CalendarID != evt.CalendarID {
return false
}
return true
default:
return false
}
}