package main import ( "encoding/json" "strings" "time" "github.com/tinode/chat/server/logs" "github.com/tinode/chat/server/store" "github.com/tinode/chat/server/store/types" ) func stableIMEventID(parts ...any) string { return types.StableEventID(parts...) } func enqueueIMBusinessEvent(tenantID types.TenantID, eventID, targetType, target string, actor types.Uid, eventType string, payload any) error { if tenantID.IsZero() || eventID == "" || target == "" || eventType == "" { return types.ErrMalformed } encoded, err := json.Marshal(payload) if err != nil { return err } return publishIMBusinessRealtimeEvent(tenantID, targetType, target, actor, eventType, encoded) } func publishIMBusinessRealtimeEvent(tenantID types.TenantID, targetType, target string, actor types.Uid, eventType string, payload json.RawMessage) error { from := "" if !actor.IsZero() { from = actor.UserId() } if len(payload) == 0 { payload = json.RawMessage(`{}`) } message := &ServerComMessage{ TenantID: tenantID, RcptTo: target, AsUser: from, Timestamp: time.Now().UTC().Round(time.Millisecond), Info: &MsgServerInfo{Topic: target, From: from, What: "im", Event: eventType, Payload: payload}, } switch targetType { case "user": uid := types.ParseUserId(target) if uid.IsZero() { return types.ErrMalformed } if globals.sessionStore != nil { globals.sessionStore.DeliverToUser(tenantID, uid, "", message) } publishRealtimeUserMessage(uid, message) case "topic": if globals.hub != nil { select { case globals.hub.routeSrv <- message: default: logs.Warn.Printf("business realtime: hub queue full target=%s event=%s", strings.TrimSpace(target), eventType) } } publishRealtimeTopicMessage(target, message) default: return types.ErrMalformed } return nil } func createIMSystemNotification(tenantID types.TenantID, uid types.Uid, eventID, eventType, title, summary string, action types.KVMap) error { if uid.IsZero() { return types.ErrMalformed } return store.Security.CreateSystemNotification(tenantID, &types.SystemNotification{ TenantID: tenantID, UserID: uid, EventID: eventID, EventType: eventType, Title: title, Summary: summary, Action: action, }) } func publishIMRelationEvent(tenantID types.TenantID, actor, target types.Uid, stableID, eventType, title, summary string) error { return publishIMRelationEventWithAction(tenantID, actor, target, stableID, eventType, title, summary, nil) } func publishIMRelationEventWithAction(tenantID types.TenantID, actor, target types.Uid, stableID, eventType, title, summary string, action types.KVMap) error { if target.IsZero() { return types.ErrMalformed } eventID := stableIMEventID("relation", stableID, eventType, target.UserId()) payload := map[string]any{"user_id": actor.UserId()} notificationAction := types.KVMap{"user_id": actor.UserId()} for key, value := range action { key = strings.TrimSpace(key) if key == "" || key == "user_id" { continue } payload[key] = value notificationAction[key] = value } if target != actor && title != "" { if err := createIMSystemNotification(tenantID, target, stableIMEventID(eventID, "notification"), eventType, title, summary, notificationAction); err != nil { return err } } return enqueueIMBusinessEvent(tenantID, eventID, "user", target.UserId(), actor, eventType, payload) }