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

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