From d2ecb7c261c6156ff3e8013b8e7497600b3fec20 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Wed, 10 Dec 2025 09:16:51 +0000 Subject: [PATCH] appview/notify: use sets to manage participant list Signed-off-by: oppiliappan --- appview/notify/db/db.go | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------- 1 file(s) changed, 67 insertion(s)(+), 57 deletion(s)(-) diff --git a/appview/notify/db/db.go b/appview/notify/db/db.go --- a/appview/notify/db/db.go +++ b/appview/notify/db/db.go @@ -3,7 +3,6 @@ import ( "context" "log" - "maps" "slices" "github.com/bluesky-social/indigo/atproto/syntax" @@ -13,10 +12,11 @@ "tangled.org/core/appview/notify" "tangled.org/core/idresolver" "tangled.org/core/orm" + "tangled.org/oppi.li/sets" ) const ( - maxMentions = 5 + maxMentions = 8 ) type databaseNotifier struct { @@ -50,7 +50,7 @@ } actorDid := syntax.DID(star.Did) - recipients := []syntax.DID{syntax.DID(repo.Did)} + recipients := sets.Singleton(syntax.DID(repo.Did)) eventType := models.NotificationTypeRepoStarred entityType := "repo" entityId := star.RepoAt.String() @@ -75,19 +75,22 @@ } func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { - - // build the recipients list - // - owner of the repo - // - collaborators in the repo - var recipients []syntax.DID - recipients = append(recipients, syntax.DID(issue.Repo.Did)) collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt())) if err != nil { log.Printf("failed to fetch collaborators: %v", err) return } + + // build the recipients list + // - owner of the repo + // - collaborators in the repo + // - remove users already mentioned + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) for _, c := range collaborators { - recipients = append(recipients, c.SubjectDid) + recipients.Insert(c.SubjectDid) + } + for _, m := range mentions { + recipients.Remove(m) } actorDid := syntax.DID(issue.Did) @@ -109,7 +112,7 @@ ) n.notifyEvent( actorDid, - mentions, + sets.Collect(slices.Values(mentions)), models.NotificationTypeUserMentioned, entityType, entityId, @@ -131,23 +134,32 @@ } issue := issues[0] - var recipients []syntax.DID - recipients = append(recipients, syntax.DID(issue.Repo.Did)) + // built the recipients list: + // - the owner of the repo + // - | if the comment is a reply -> everybody on that thread + // | if the comment is a top level -> just the issue owner + // - remove mentioned users from the recipients list + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) if comment.IsReply() { // if this comment is a reply, then notify everybody in that thread parentAtUri := *comment.ReplyTo - allThreads := issue.CommentList() // find the parent thread, and add all DIDs from here to the recipient list - for _, t := range allThreads { + for _, t := range issue.CommentList() { if t.Self.AtUri().String() == parentAtUri { - recipients = append(recipients, t.Participants()...) + for _, p := range t.Participants() { + recipients.Insert(p) + } } } } else { // not a reply, notify just the issue author - recipients = append(recipients, syntax.DID(issue.Did)) + recipients.Insert(syntax.DID(issue.Did)) + } + + for _, m := range mentions { + recipients.Remove(m) } actorDid := syntax.DID(comment.Did) @@ -169,7 +181,7 @@ ) n.notifyEvent( actorDid, - mentions, + sets.Collect(slices.Values(mentions)), models.NotificationTypeUserMentioned, entityType, entityId, @@ -185,7 +197,7 @@ func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { actorDid := syntax.DID(follow.UserDid) - recipients := []syntax.DID{syntax.DID(follow.SubjectDid)} + recipients := sets.Singleton(syntax.DID(follow.SubjectDid)) eventType := models.NotificationTypeFollowed entityType := "follow" entityId := follow.UserDid @@ -213,19 +225,18 @@ log.Printf("NewPull: failed to get repos: %v", err) return } - - // build the recipients list - // - owner of the repo - // - collaborators in the repo - var recipients []syntax.DID - recipients = append(recipients, syntax.DID(repo.Did)) collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt())) if err != nil { log.Printf("failed to fetch collaborators: %v", err) return } + + // build the recipients list + // - owner of the repo + // - collaborators in the repo + recipients := sets.Singleton(syntax.DID(repo.Did)) for _, c := range collaborators { - recipients = append(recipients, c.SubjectDid) + recipients.Insert(c.SubjectDid) } actorDid := syntax.DID(pull.OwnerDid) @@ -268,10 +279,13 @@ // build up the recipients list: // - repo owner // - all pull participants - var recipients []syntax.DID - recipients = append(recipients, syntax.DID(repo.Did)) + // - remove those already mentioned + recipients := sets.Singleton(syntax.DID(repo.Did)) for _, p := range pull.Participants() { - recipients = append(recipients, syntax.DID(p)) + recipients.Insert(syntax.DID(p)) + } + for _, m := range mentions { + recipients.Remove(m) } actorDid := syntax.DID(comment.OwnerDid) @@ -295,7 +309,7 @@ ) n.notifyEvent( actorDid, - mentions, + sets.Collect(slices.Values(mentions)), models.NotificationTypeUserMentioned, entityType, entityId, @@ -322,22 +336,22 @@ } func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { - // build up the recipients list: - // - repo owner - // - repo collaborators - // - all issue participants - var recipients []syntax.DID - recipients = append(recipients, syntax.DID(issue.Repo.Did)) collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt())) if err != nil { log.Printf("failed to fetch collaborators: %v", err) return } + + // build up the recipients list: + // - repo owner + // - repo collaborators + // - all issue participants + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) for _, c := range collaborators { - recipients = append(recipients, c.SubjectDid) + recipients.Insert(c.SubjectDid) } for _, p := range issue.Participants() { - recipients = append(recipients, syntax.DID(p)) + recipients.Insert(syntax.DID(p)) } entityType := "pull" @@ -373,21 +387,21 @@ return } - // build up the recipients list: - // - repo owner - // - all pull participants - var recipients []syntax.DID - recipients = append(recipients, syntax.DID(repo.Did)) collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt())) if err != nil { log.Printf("failed to fetch collaborators: %v", err) return } + + // build up the recipients list: + // - repo owner + // - all pull participants + recipients := sets.Singleton(syntax.DID(repo.Did)) for _, c := range collaborators { - recipients = append(recipients, c.SubjectDid) + recipients.Insert(c.SubjectDid) } for _, p := range pull.Participants() { - recipients = append(recipients, syntax.DID(p)) + recipients.Insert(syntax.DID(p)) } entityType := "pull" @@ -423,7 +437,7 @@ func (n *databaseNotifier) notifyEvent( actorDid syntax.DID, - recipients []syntax.DID, + recipients sets.Set[syntax.DID], eventType models.NotificationType, entityType string, entityId string, @@ -431,20 +445,16 @@ issueId *int64, pullId *int64, ) { - if eventType == models.NotificationTypeUserMentioned && len(recipients) > maxMentions { - recipients = recipients[:maxMentions] + // if the user is attempting to mention >maxMentions users, this is probably spam, do not mention anybody + if eventType == models.NotificationTypeUserMentioned && recipients.Len() > maxMentions { + return } - recipientSet := make(map[syntax.DID]struct{}) - for _, did := range recipients { - // everybody except actor themselves - if did != actorDid { - recipientSet[did] = struct{}{} - } - } + + recipients.Remove(actorDid) prefMap, err := db.GetNotificationPreferences( n.db, - orm.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))), + orm.FilterIn("user_did", slices.Collect(recipients.All())), ) if err != nil { // failed to get prefs for users @@ -460,7 +470,7 @@ defer tx.Rollback() // filter based on preferences - for recipientDid := range recipientSet { + for recipientDid := range recipients.All() { prefs, ok := prefMap[recipientDid] if !ok { prefs = models.DefaultNotificationPreferences(recipientDid) -- tangled.sh