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  }