forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

appview/notify: use sets to manage participant list

Signed-off-by: oppiliappan <me@oppi.li>

Changed files
+67 -57
appview
notify
db
+67 -57
appview/notify/db/db.go
···
import (
"context"
"log"
-
"maps"
"slices"
"github.com/bluesky-social/indigo/atproto/syntax"
···
"tangled.org/core/appview/notify"
"tangled.org/core/idresolver"
"tangled.org/core/orm"
)
const (
-
maxMentions = 5
)
type databaseNotifier struct {
···
}
actorDid := syntax.DID(star.Did)
-
recipients := []syntax.DID{syntax.DID(repo.Did)}
eventType := models.NotificationTypeRepoStarred
entityType := "repo"
entityId := star.RepoAt.String()
···
}
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
}
for _, c := range collaborators {
-
recipients = append(recipients, c.SubjectDid)
}
actorDid := syntax.DID(issue.Did)
···
)
n.notifyEvent(
actorDid,
-
mentions,
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
}
issue := issues[0]
-
var recipients []syntax.DID
-
recipients = append(recipients, 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 {
if t.Self.AtUri().String() == parentAtUri {
-
recipients = append(recipients, t.Participants()...)
}
}
} else {
// not a reply, notify just the issue author
-
recipients = append(recipients, syntax.DID(issue.Did))
}
actorDid := syntax.DID(comment.Did)
···
)
n.notifyEvent(
actorDid,
-
mentions,
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
actorDid := syntax.DID(follow.UserDid)
-
recipients := []syntax.DID{syntax.DID(follow.SubjectDid)}
eventType := models.NotificationTypeFollowed
entityType := "follow"
entityId := follow.UserDid
···
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
}
for _, c := range collaborators {
-
recipients = append(recipients, c.SubjectDid)
}
actorDid := syntax.DID(pull.OwnerDid)
···
// build up the recipients list:
// - repo owner
// - all pull participants
-
var recipients []syntax.DID
-
recipients = append(recipients, syntax.DID(repo.Did))
for _, p := range pull.Participants() {
-
recipients = append(recipients, syntax.DID(p))
}
actorDid := syntax.DID(comment.OwnerDid)
···
)
n.notifyEvent(
actorDid,
-
mentions,
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
}
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
}
for _, c := range collaborators {
-
recipients = append(recipients, c.SubjectDid)
}
for _, p := range issue.Participants() {
-
recipients = append(recipients, syntax.DID(p))
}
entityType := "pull"
···
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
}
for _, c := range collaborators {
-
recipients = append(recipients, c.SubjectDid)
}
for _, p := range pull.Participants() {
-
recipients = append(recipients, syntax.DID(p))
}
entityType := "pull"
···
func (n *databaseNotifier) notifyEvent(
actorDid syntax.DID,
-
recipients []syntax.DID,
eventType models.NotificationType,
entityType string,
entityId string,
···
issueId *int64,
pullId *int64,
) {
-
if eventType == models.NotificationTypeUserMentioned && len(recipients) > maxMentions {
-
recipients = recipients[:maxMentions]
}
-
recipientSet := make(map[syntax.DID]struct{})
-
for _, did := range recipients {
-
// everybody except actor themselves
-
if did != actorDid {
-
recipientSet[did] = struct{}{}
-
}
-
}
prefMap, err := db.GetNotificationPreferences(
n.db,
-
orm.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))),
)
if err != nil {
// failed to get prefs for users
···
defer tx.Rollback()
// filter based on preferences
-
for recipientDid := range recipientSet {
prefs, ok := prefMap[recipientDid]
if !ok {
prefs = models.DefaultNotificationPreferences(recipientDid)
···
import (
"context"
"log"
"slices"
"github.com/bluesky-social/indigo/atproto/syntax"
···
"tangled.org/core/appview/notify"
"tangled.org/core/idresolver"
"tangled.org/core/orm"
+
"tangled.org/oppi.li/sets"
)
const (
+
maxMentions = 8
)
type databaseNotifier struct {
···
}
actorDid := syntax.DID(star.Did)
+
recipients := sets.Singleton(syntax.DID(repo.Did))
eventType := models.NotificationTypeRepoStarred
entityType := "repo"
entityId := star.RepoAt.String()
···
}
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.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.Insert(c.SubjectDid)
+
}
+
for _, m := range mentions {
+
recipients.Remove(m)
}
actorDid := syntax.DID(issue.Did)
···
)
n.notifyEvent(
actorDid,
+
sets.Collect(slices.Values(mentions)),
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
}
issue := issues[0]
+
// 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
// find the parent thread, and add all DIDs from here to the recipient list
+
for _, t := range issue.CommentList() {
if t.Self.AtUri().String() == parentAtUri {
+
for _, p := range t.Participants() {
+
recipients.Insert(p)
+
}
}
}
} else {
// not a reply, notify just the issue author
+
recipients.Insert(syntax.DID(issue.Did))
+
}
+
+
for _, m := range mentions {
+
recipients.Remove(m)
}
actorDid := syntax.DID(comment.Did)
···
)
n.notifyEvent(
actorDid,
+
sets.Collect(slices.Values(mentions)),
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
actorDid := syntax.DID(follow.UserDid)
+
recipients := sets.Singleton(syntax.DID(follow.SubjectDid))
eventType := models.NotificationTypeFollowed
entityType := "follow"
entityId := follow.UserDid
···
log.Printf("NewPull: failed to get repos: %v", err)
return
}
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.Insert(c.SubjectDid)
}
actorDid := syntax.DID(pull.OwnerDid)
···
// build up the recipients list:
// - repo owner
// - all pull participants
+
// - remove those already mentioned
+
recipients := sets.Singleton(syntax.DID(repo.Did))
for _, p := range pull.Participants() {
+
recipients.Insert(syntax.DID(p))
+
}
+
for _, m := range mentions {
+
recipients.Remove(m)
}
actorDid := syntax.DID(comment.OwnerDid)
···
)
n.notifyEvent(
actorDid,
+
sets.Collect(slices.Values(mentions)),
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
}
func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {
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.Insert(c.SubjectDid)
}
for _, p := range issue.Participants() {
+
recipients.Insert(syntax.DID(p))
}
entityType := "pull"
···
return
}
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.Insert(c.SubjectDid)
}
for _, p := range pull.Participants() {
+
recipients.Insert(syntax.DID(p))
}
entityType := "pull"
···
func (n *databaseNotifier) notifyEvent(
actorDid syntax.DID,
+
recipients sets.Set[syntax.DID],
eventType models.NotificationType,
entityType string,
entityId string,
···
issueId *int64,
pullId *int64,
) {
+
// 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
}
+
+
recipients.Remove(actorDid)
prefMap, err := db.GetNotificationPreferences(
n.db,
+
orm.FilterIn("user_did", slices.Collect(recipients.All())),
)
if err != nil {
// failed to get prefs for users
···
defer tx.Rollback()
// filter based on preferences
+
for recipientDid := range recipients.All() {
prefs, ok := prefMap[recipientDid]
if !ok {
prefs = models.DefaultNotificationPreferences(recipientDid)