appview/notify: use sets to manage participant list #874

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