forked from tangled.org/core
Monorepo for Tangled

appview/notify: use sets to manage participant list

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

authored by oppi.li and committed by Tangled d2ecb7c2 b729a533

Changed files
+67 -57
appview
notify
db
+67 -57
appview/notify/db/db.go
··· 3 import ( 4 "context" 5 "log" 6 - "maps" 7 "slices" 8 9 "github.com/bluesky-social/indigo/atproto/syntax" ··· 13 "tangled.org/core/appview/notify" 14 "tangled.org/core/idresolver" 15 "tangled.org/core/orm" 16 ) 17 18 const ( 19 - maxMentions = 5 20 ) 21 22 type databaseNotifier struct { ··· 50 } 51 52 actorDid := syntax.DID(star.Did) 53 - recipients := []syntax.DID{syntax.DID(repo.Did)} 54 eventType := models.NotificationTypeRepoStarred 55 entityType := "repo" 56 entityId := star.RepoAt.String() ··· 75 } 76 77 func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 78 - 79 - // build the recipients list 80 - // - owner of the repo 81 - // - collaborators in the repo 82 - var recipients []syntax.DID 83 - recipients = append(recipients, syntax.DID(issue.Repo.Did)) 84 collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt())) 85 if err != nil { 86 log.Printf("failed to fetch collaborators: %v", err) 87 return 88 } 89 for _, c := range collaborators { 90 - recipients = append(recipients, c.SubjectDid) 91 } 92 93 actorDid := syntax.DID(issue.Did) ··· 109 ) 110 n.notifyEvent( 111 actorDid, 112 - mentions, 113 models.NotificationTypeUserMentioned, 114 entityType, 115 entityId, ··· 131 } 132 issue := issues[0] 133 134 - var recipients []syntax.DID 135 - recipients = append(recipients, syntax.DID(issue.Repo.Did)) 136 137 if comment.IsReply() { 138 // if this comment is a reply, then notify everybody in that thread 139 parentAtUri := *comment.ReplyTo 140 - allThreads := issue.CommentList() 141 142 // find the parent thread, and add all DIDs from here to the recipient list 143 - for _, t := range allThreads { 144 if t.Self.AtUri().String() == parentAtUri { 145 - recipients = append(recipients, t.Participants()...) 146 } 147 } 148 } else { 149 // not a reply, notify just the issue author 150 - recipients = append(recipients, syntax.DID(issue.Did)) 151 } 152 153 actorDid := syntax.DID(comment.Did) ··· 169 ) 170 n.notifyEvent( 171 actorDid, 172 - mentions, 173 models.NotificationTypeUserMentioned, 174 entityType, 175 entityId, ··· 185 186 func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { 187 actorDid := syntax.DID(follow.UserDid) 188 - recipients := []syntax.DID{syntax.DID(follow.SubjectDid)} 189 eventType := models.NotificationTypeFollowed 190 entityType := "follow" 191 entityId := follow.UserDid ··· 213 log.Printf("NewPull: failed to get repos: %v", err) 214 return 215 } 216 - 217 - // build the recipients list 218 - // - owner of the repo 219 - // - collaborators in the repo 220 - var recipients []syntax.DID 221 - recipients = append(recipients, syntax.DID(repo.Did)) 222 collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt())) 223 if err != nil { 224 log.Printf("failed to fetch collaborators: %v", err) 225 return 226 } 227 for _, c := range collaborators { 228 - recipients = append(recipients, c.SubjectDid) 229 } 230 231 actorDid := syntax.DID(pull.OwnerDid) ··· 268 // build up the recipients list: 269 // - repo owner 270 // - all pull participants 271 - var recipients []syntax.DID 272 - recipients = append(recipients, syntax.DID(repo.Did)) 273 for _, p := range pull.Participants() { 274 - recipients = append(recipients, syntax.DID(p)) 275 } 276 277 actorDid := syntax.DID(comment.OwnerDid) ··· 295 ) 296 n.notifyEvent( 297 actorDid, 298 - mentions, 299 models.NotificationTypeUserMentioned, 300 entityType, 301 entityId, ··· 322 } 323 324 func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { 325 - // build up the recipients list: 326 - // - repo owner 327 - // - repo collaborators 328 - // - all issue participants 329 - var recipients []syntax.DID 330 - recipients = append(recipients, syntax.DID(issue.Repo.Did)) 331 collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt())) 332 if err != nil { 333 log.Printf("failed to fetch collaborators: %v", err) 334 return 335 } 336 for _, c := range collaborators { 337 - recipients = append(recipients, c.SubjectDid) 338 } 339 for _, p := range issue.Participants() { 340 - recipients = append(recipients, syntax.DID(p)) 341 } 342 343 entityType := "pull" ··· 373 return 374 } 375 376 - // build up the recipients list: 377 - // - repo owner 378 - // - all pull participants 379 - var recipients []syntax.DID 380 - recipients = append(recipients, syntax.DID(repo.Did)) 381 collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt())) 382 if err != nil { 383 log.Printf("failed to fetch collaborators: %v", err) 384 return 385 } 386 for _, c := range collaborators { 387 - recipients = append(recipients, c.SubjectDid) 388 } 389 for _, p := range pull.Participants() { 390 - recipients = append(recipients, syntax.DID(p)) 391 } 392 393 entityType := "pull" ··· 423 424 func (n *databaseNotifier) notifyEvent( 425 actorDid syntax.DID, 426 - recipients []syntax.DID, 427 eventType models.NotificationType, 428 entityType string, 429 entityId string, ··· 431 issueId *int64, 432 pullId *int64, 433 ) { 434 - if eventType == models.NotificationTypeUserMentioned && len(recipients) > maxMentions { 435 - recipients = recipients[:maxMentions] 436 } 437 - recipientSet := make(map[syntax.DID]struct{}) 438 - for _, did := range recipients { 439 - // everybody except actor themselves 440 - if did != actorDid { 441 - recipientSet[did] = struct{}{} 442 - } 443 - } 444 445 prefMap, err := db.GetNotificationPreferences( 446 n.db, 447 - orm.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))), 448 ) 449 if err != nil { 450 // failed to get prefs for users ··· 460 defer tx.Rollback() 461 462 // filter based on preferences 463 - for recipientDid := range recipientSet { 464 prefs, ok := prefMap[recipientDid] 465 if !ok { 466 prefs = models.DefaultNotificationPreferences(recipientDid)
··· 3 import ( 4 "context" 5 "log" 6 "slices" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" ··· 12 "tangled.org/core/appview/notify" 13 "tangled.org/core/idresolver" 14 "tangled.org/core/orm" 15 + "tangled.org/oppi.li/sets" 16 ) 17 18 const ( 19 + maxMentions = 8 20 ) 21 22 type databaseNotifier struct { ··· 50 } 51 52 actorDid := syntax.DID(star.Did) 53 + recipients := sets.Singleton(syntax.DID(repo.Did)) 54 eventType := models.NotificationTypeRepoStarred 55 entityType := "repo" 56 entityId := star.RepoAt.String() ··· 75 } 76 77 func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 78 collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt())) 79 if err != nil { 80 log.Printf("failed to fetch collaborators: %v", err) 81 return 82 } 83 + 84 + // build the recipients list 85 + // - owner of the repo 86 + // - collaborators in the repo 87 + // - remove users already mentioned 88 + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) 89 for _, c := range collaborators { 90 + recipients.Insert(c.SubjectDid) 91 + } 92 + for _, m := range mentions { 93 + recipients.Remove(m) 94 } 95 96 actorDid := syntax.DID(issue.Did) ··· 112 ) 113 n.notifyEvent( 114 actorDid, 115 + sets.Collect(slices.Values(mentions)), 116 models.NotificationTypeUserMentioned, 117 entityType, 118 entityId, ··· 134 } 135 issue := issues[0] 136 137 + // built the recipients list: 138 + // - the owner of the repo 139 + // - | if the comment is a reply -> everybody on that thread 140 + // | if the comment is a top level -> just the issue owner 141 + // - remove mentioned users from the recipients list 142 + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) 143 144 if comment.IsReply() { 145 // if this comment is a reply, then notify everybody in that thread 146 parentAtUri := *comment.ReplyTo 147 148 // find the parent thread, and add all DIDs from here to the recipient list 149 + for _, t := range issue.CommentList() { 150 if t.Self.AtUri().String() == parentAtUri { 151 + for _, p := range t.Participants() { 152 + recipients.Insert(p) 153 + } 154 } 155 } 156 } else { 157 // not a reply, notify just the issue author 158 + recipients.Insert(syntax.DID(issue.Did)) 159 + } 160 + 161 + for _, m := range mentions { 162 + recipients.Remove(m) 163 } 164 165 actorDid := syntax.DID(comment.Did) ··· 181 ) 182 n.notifyEvent( 183 actorDid, 184 + sets.Collect(slices.Values(mentions)), 185 models.NotificationTypeUserMentioned, 186 entityType, 187 entityId, ··· 197 198 func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { 199 actorDid := syntax.DID(follow.UserDid) 200 + recipients := sets.Singleton(syntax.DID(follow.SubjectDid)) 201 eventType := models.NotificationTypeFollowed 202 entityType := "follow" 203 entityId := follow.UserDid ··· 225 log.Printf("NewPull: failed to get repos: %v", err) 226 return 227 } 228 collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt())) 229 if err != nil { 230 log.Printf("failed to fetch collaborators: %v", err) 231 return 232 } 233 + 234 + // build the recipients list 235 + // - owner of the repo 236 + // - collaborators in the repo 237 + recipients := sets.Singleton(syntax.DID(repo.Did)) 238 for _, c := range collaborators { 239 + recipients.Insert(c.SubjectDid) 240 } 241 242 actorDid := syntax.DID(pull.OwnerDid) ··· 279 // build up the recipients list: 280 // - repo owner 281 // - all pull participants 282 + // - remove those already mentioned 283 + recipients := sets.Singleton(syntax.DID(repo.Did)) 284 for _, p := range pull.Participants() { 285 + recipients.Insert(syntax.DID(p)) 286 + } 287 + for _, m := range mentions { 288 + recipients.Remove(m) 289 } 290 291 actorDid := syntax.DID(comment.OwnerDid) ··· 309 ) 310 n.notifyEvent( 311 actorDid, 312 + sets.Collect(slices.Values(mentions)), 313 models.NotificationTypeUserMentioned, 314 entityType, 315 entityId, ··· 336 } 337 338 func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { 339 collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt())) 340 if err != nil { 341 log.Printf("failed to fetch collaborators: %v", err) 342 return 343 } 344 + 345 + // build up the recipients list: 346 + // - repo owner 347 + // - repo collaborators 348 + // - all issue participants 349 + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) 350 for _, c := range collaborators { 351 + recipients.Insert(c.SubjectDid) 352 } 353 for _, p := range issue.Participants() { 354 + recipients.Insert(syntax.DID(p)) 355 } 356 357 entityType := "pull" ··· 387 return 388 } 389 390 collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt())) 391 if err != nil { 392 log.Printf("failed to fetch collaborators: %v", err) 393 return 394 } 395 + 396 + // build up the recipients list: 397 + // - repo owner 398 + // - all pull participants 399 + recipients := sets.Singleton(syntax.DID(repo.Did)) 400 for _, c := range collaborators { 401 + recipients.Insert(c.SubjectDid) 402 } 403 for _, p := range pull.Participants() { 404 + recipients.Insert(syntax.DID(p)) 405 } 406 407 entityType := "pull" ··· 437 438 func (n *databaseNotifier) notifyEvent( 439 actorDid syntax.DID, 440 + recipients sets.Set[syntax.DID], 441 eventType models.NotificationType, 442 entityType string, 443 entityId string, ··· 445 issueId *int64, 446 pullId *int64, 447 ) { 448 + // if the user is attempting to mention >maxMentions users, this is probably spam, do not mention anybody 449 + if eventType == models.NotificationTypeUserMentioned && recipients.Len() > maxMentions { 450 + return 451 } 452 + 453 + recipients.Remove(actorDid) 454 455 prefMap, err := db.GetNotificationPreferences( 456 n.db, 457 + orm.FilterIn("user_did", slices.Collect(recipients.All())), 458 ) 459 if err != nil { 460 // failed to get prefs for users ··· 470 defer tx.Rollback() 471 472 // filter based on preferences 473 + for recipientDid := range recipients.All() { 474 prefs, ok := prefMap[recipientDid] 475 if !ok { 476 prefs = models.DefaultNotificationPreferences(recipientDid)