github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/job/trigger_share_group.go (about) 1 package job 2 3 import ( 4 "github.com/cozy/cozy-stack/model/contact" 5 "github.com/cozy/cozy-stack/pkg/consts" 6 "github.com/cozy/cozy-stack/pkg/couchdb" 7 "github.com/cozy/cozy-stack/pkg/logger" 8 "github.com/cozy/cozy-stack/pkg/realtime" 9 ) 10 11 type ShareGroupTrigger struct { 12 broker Broker 13 log *logger.Entry 14 unscheduled chan struct{} 15 } 16 17 // ShareGroupMessage is used for jobs on the share-group worker. 18 type ShareGroupMessage struct { 19 ContactID string `json:"contact_id,omitempty"` 20 GroupsAdded []string `json:"added,omitempty"` 21 GroupsRemoved []string `json:"removed,omitempty"` 22 BecomeInvitable bool `json:"invitable,omitempty"` 23 DeletedDoc *couchdb.JSONDoc `json:"deleted_doc,omitempty"` 24 RenamedGroup *couchdb.JSONDoc `json:"renamed_group,omitempty"` 25 } 26 27 func NewShareGroupTrigger(broker Broker) *ShareGroupTrigger { 28 return &ShareGroupTrigger{ 29 broker: broker, 30 log: logger.WithNamespace("scheduler"), 31 unscheduled: make(chan struct{}), 32 } 33 } 34 35 func (t *ShareGroupTrigger) Schedule() { 36 sub := realtime.GetHub().SubscribeFirehose() 37 defer sub.Close() 38 for { 39 select { 40 case e := <-sub.Channel: 41 if msg := t.match(e); msg != nil { 42 t.pushJob(e, msg) 43 } 44 case <-t.unscheduled: 45 return 46 } 47 } 48 } 49 50 func (t *ShareGroupTrigger) match(e *realtime.Event) *ShareGroupMessage { 51 if e.Verb == realtime.EventNotify { 52 return nil 53 } 54 switch e.Doc.DocType() { 55 case consts.Groups: 56 return t.matchGroup(e) 57 case consts.Contacts: 58 return t.matchContact(e) 59 } 60 return nil 61 } 62 63 func (t *ShareGroupTrigger) matchGroup(e *realtime.Event) *ShareGroupMessage { 64 if e.Verb != realtime.EventUpdate { 65 return nil 66 } 67 newdoc, ok := e.Doc.(*couchdb.JSONDoc) 68 if !ok { 69 return nil 70 } 71 olddoc, ok := e.OldDoc.(*couchdb.JSONDoc) 72 if !ok { 73 return nil 74 } 75 if newdoc.M["name"] == olddoc.M["name"] { 76 return nil 77 } 78 return &ShareGroupMessage{RenamedGroup: newdoc} 79 } 80 81 func (t *ShareGroupTrigger) matchContact(e *realtime.Event) *ShareGroupMessage { 82 newdoc, ok := e.Doc.(*couchdb.JSONDoc) 83 if !ok { 84 return nil 85 } 86 newContact := &contact.Contact{JSONDoc: *newdoc} 87 var newgroups []string 88 if e.Verb != realtime.EventDelete { 89 newgroups = newContact.GroupIDs() 90 } 91 92 var oldgroups []string 93 invitable := false 94 olddoc, ok := e.OldDoc.(*couchdb.JSONDoc) 95 if ok { 96 oldContact := &contact.Contact{JSONDoc: *olddoc} 97 oldgroups = oldContact.GroupIDs() 98 invitable = contactIsNowInvitable(oldContact, newContact) 99 } 100 101 added := diffGroupIDs(newgroups, oldgroups) 102 removed := diffGroupIDs(oldgroups, newgroups) 103 104 if len(added) == 0 && len(removed) == 0 && !invitable { 105 return nil 106 } 107 108 msg := &ShareGroupMessage{ 109 ContactID: e.Doc.ID(), 110 GroupsAdded: added, 111 GroupsRemoved: removed, 112 BecomeInvitable: invitable, 113 } 114 if e.Verb == realtime.EventDelete { 115 msg.DeletedDoc = olddoc 116 } 117 return msg 118 } 119 120 func diffGroupIDs(as, bs []string) []string { 121 var diff []string 122 for _, a := range as { 123 found := false 124 for _, b := range bs { 125 if a == b { 126 found = true 127 } 128 } 129 if !found { 130 diff = append(diff, a) 131 } 132 } 133 return diff 134 } 135 136 func contactIsNowInvitable(oldContact, newContact *contact.Contact) bool { 137 if oldURL := oldContact.PrimaryCozyURL(); oldURL != "" { 138 return false 139 } 140 if oldAddr, err := oldContact.ToMailAddress(); err == nil && oldAddr.Email != "" { 141 return false 142 } 143 if newURL := newContact.PrimaryCozyURL(); newURL != "" { 144 return true 145 } 146 if newAddr, err := newContact.ToMailAddress(); err == nil && newAddr.Email != "" { 147 return true 148 } 149 return false 150 } 151 152 func (t *ShareGroupTrigger) pushJob(e *realtime.Event, msg *ShareGroupMessage) { 153 log := t.log.WithField("domain", e.Domain) 154 m, err := NewMessage(msg) 155 if err != nil { 156 log.Infof("trigger share-group: cannot serialize message: %s", err) 157 return 158 } 159 req := &JobRequest{ 160 WorkerType: "share-group", 161 Message: m, 162 } 163 log.Infof("trigger share-group: Pushing new job for contact %s", msg.ContactID) 164 if _, err := t.broker.PushJob(e, req); err != nil { 165 log.Errorf("trigger share-group: Could not schedule a new job: %s", err.Error()) 166 } 167 } 168 169 func (t *ShareGroupTrigger) Unschedule() { 170 close(t.unscheduled) 171 }