loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request '[Port] gitea#29930: Move notifications to a standalone file' (#2856) from gusted/forgejo-port-29930 into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2856
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>

Gusted a1111660 168c56d7

+480 -466
-466
models/activities/notification.go
··· 12 12 "code.gitea.io/gitea/models/db" 13 13 issues_model "code.gitea.io/gitea/models/issues" 14 14 "code.gitea.io/gitea/models/organization" 15 - access_model "code.gitea.io/gitea/models/perm/access" 16 15 repo_model "code.gitea.io/gitea/models/repo" 17 - "code.gitea.io/gitea/models/unit" 18 16 user_model "code.gitea.io/gitea/models/user" 19 - "code.gitea.io/gitea/modules/container" 20 - "code.gitea.io/gitea/modules/log" 21 17 "code.gitea.io/gitea/modules/setting" 22 18 "code.gitea.io/gitea/modules/timeutil" 23 19 "code.gitea.io/gitea/modules/util" ··· 80 76 db.RegisterModel(new(Notification)) 81 77 } 82 78 83 - // FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored. 84 - type FindNotificationOptions struct { 85 - db.ListOptions 86 - UserID int64 87 - RepoID int64 88 - IssueID int64 89 - Status []NotificationStatus 90 - Source []NotificationSource 91 - UpdatedAfterUnix int64 92 - UpdatedBeforeUnix int64 93 - } 94 - 95 - // ToCond will convert each condition into a xorm-Cond 96 - func (opts FindNotificationOptions) ToConds() builder.Cond { 97 - cond := builder.NewCond() 98 - if opts.UserID != 0 { 99 - cond = cond.And(builder.Eq{"notification.user_id": opts.UserID}) 100 - } 101 - if opts.RepoID != 0 { 102 - cond = cond.And(builder.Eq{"notification.repo_id": opts.RepoID}) 103 - } 104 - if opts.IssueID != 0 { 105 - cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID}) 106 - } 107 - if len(opts.Status) > 0 { 108 - if len(opts.Status) == 1 { 109 - cond = cond.And(builder.Eq{"notification.status": opts.Status[0]}) 110 - } else { 111 - cond = cond.And(builder.In("notification.status", opts.Status)) 112 - } 113 - } 114 - if len(opts.Source) > 0 { 115 - cond = cond.And(builder.In("notification.source", opts.Source)) 116 - } 117 - if opts.UpdatedAfterUnix != 0 { 118 - cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix}) 119 - } 120 - if opts.UpdatedBeforeUnix != 0 { 121 - cond = cond.And(builder.Lte{"notification.updated_unix": opts.UpdatedBeforeUnix}) 122 - } 123 - return cond 124 - } 125 - 126 - func (opts FindNotificationOptions) ToOrders() string { 127 - return "notification.updated_unix DESC" 128 - } 129 - 130 79 // CreateRepoTransferNotification creates notification for the user a repository was transferred to 131 80 func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) error { 132 81 return db.WithTx(ctx, func(ctx context.Context) error { ··· 160 109 }) 161 110 } 162 111 163 - // CreateOrUpdateIssueNotifications creates an issue notification 164 - // for each watcher, or updates it if already exists 165 - // receiverID > 0 just send to receiver, else send to all watcher 166 - func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { 167 - ctx, committer, err := db.TxContext(ctx) 168 - if err != nil { 169 - return err 170 - } 171 - defer committer.Close() 172 - 173 - if err := createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID); err != nil { 174 - return err 175 - } 176 - 177 - return committer.Commit() 178 - } 179 - 180 - func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { 181 - // init 182 - var toNotify container.Set[int64] 183 - notifications, err := db.Find[Notification](ctx, FindNotificationOptions{ 184 - IssueID: issueID, 185 - }) 186 - if err != nil { 187 - return err 188 - } 189 - 190 - issue, err := issues_model.GetIssueByID(ctx, issueID) 191 - if err != nil { 192 - return err 193 - } 194 - 195 - if receiverID > 0 { 196 - toNotify = make(container.Set[int64], 1) 197 - toNotify.Add(receiverID) 198 - } else { 199 - toNotify = make(container.Set[int64], 32) 200 - issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true) 201 - if err != nil { 202 - return err 203 - } 204 - toNotify.AddMultiple(issueWatches...) 205 - if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) { 206 - repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID) 207 - if err != nil { 208 - return err 209 - } 210 - toNotify.AddMultiple(repoWatches...) 211 - } 212 - issueParticipants, err := issue.GetParticipantIDsByIssue(ctx) 213 - if err != nil { 214 - return err 215 - } 216 - toNotify.AddMultiple(issueParticipants...) 217 - 218 - // dont notify user who cause notification 219 - delete(toNotify, notificationAuthorID) 220 - // explicit unwatch on issue 221 - issueUnWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, false) 222 - if err != nil { 223 - return err 224 - } 225 - for _, id := range issueUnWatches { 226 - toNotify.Remove(id) 227 - } 228 - 229 - // Remove users who have the notification author blocked. 230 - blockedAuthorIDs, err := user_model.ListBlockedByUsersID(ctx, notificationAuthorID) 231 - if err != nil { 232 - return err 233 - } 234 - for _, id := range blockedAuthorIDs { 235 - toNotify.Remove(id) 236 - } 237 - } 238 - 239 - err = issue.LoadRepo(ctx) 240 - if err != nil { 241 - return err 242 - } 243 - 244 - // notify 245 - for userID := range toNotify { 246 - issue.Repo.Units = nil 247 - user, err := user_model.GetUserByID(ctx, userID) 248 - if err != nil { 249 - if user_model.IsErrUserNotExist(err) { 250 - continue 251 - } 252 - 253 - return err 254 - } 255 - if issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) { 256 - continue 257 - } 258 - if !issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) { 259 - continue 260 - } 261 - 262 - if notificationExists(notifications, issue.ID, userID) { 263 - if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil { 264 - return err 265 - } 266 - continue 267 - } 268 - if err = createIssueNotification(ctx, userID, issue, commentID, notificationAuthorID); err != nil { 269 - return err 270 - } 271 - } 272 - return nil 273 - } 274 - 275 112 func createIssueNotification(ctx context.Context, userID int64, issue *issues_model.Issue, commentID, updatedByID int64) error { 276 113 notification := &Notification{ 277 114 UserID: userID, ··· 457 294 `updated_unix < ?) AND status = ? GROUP BY user_id` 458 295 var res []UserIDCount 459 296 return res, db.GetEngine(ctx).SQL(sql, since, until, NotificationStatusUnread).Find(&res) 460 - } 461 - 462 - // NotificationList contains a list of notifications 463 - type NotificationList []*Notification 464 - 465 - // LoadAttributes load Repo Issue User and Comment if not loaded 466 - func (nl NotificationList) LoadAttributes(ctx context.Context) error { 467 - if _, _, err := nl.LoadRepos(ctx); err != nil { 468 - return err 469 - } 470 - if _, err := nl.LoadIssues(ctx); err != nil { 471 - return err 472 - } 473 - if _, err := nl.LoadUsers(ctx); err != nil { 474 - return err 475 - } 476 - if _, err := nl.LoadComments(ctx); err != nil { 477 - return err 478 - } 479 - return nil 480 - } 481 - 482 - func (nl NotificationList) getPendingRepoIDs() []int64 { 483 - ids := make(container.Set[int64], len(nl)) 484 - for _, notification := range nl { 485 - if notification.Repository != nil { 486 - continue 487 - } 488 - ids.Add(notification.RepoID) 489 - } 490 - return ids.Values() 491 - } 492 - 493 - // LoadRepos loads repositories from database 494 - func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.RepositoryList, []int, error) { 495 - if len(nl) == 0 { 496 - return repo_model.RepositoryList{}, []int{}, nil 497 - } 498 - 499 - repoIDs := nl.getPendingRepoIDs() 500 - repos := make(map[int64]*repo_model.Repository, len(repoIDs)) 501 - left := len(repoIDs) 502 - for left > 0 { 503 - limit := db.DefaultMaxInSize 504 - if left < limit { 505 - limit = left 506 - } 507 - rows, err := db.GetEngine(ctx). 508 - In("id", repoIDs[:limit]). 509 - Rows(new(repo_model.Repository)) 510 - if err != nil { 511 - return nil, nil, err 512 - } 513 - 514 - for rows.Next() { 515 - var repo repo_model.Repository 516 - err = rows.Scan(&repo) 517 - if err != nil { 518 - rows.Close() 519 - return nil, nil, err 520 - } 521 - 522 - repos[repo.ID] = &repo 523 - } 524 - _ = rows.Close() 525 - 526 - left -= limit 527 - repoIDs = repoIDs[limit:] 528 - } 529 - 530 - failed := []int{} 531 - 532 - reposList := make(repo_model.RepositoryList, 0, len(repoIDs)) 533 - for i, notification := range nl { 534 - if notification.Repository == nil { 535 - notification.Repository = repos[notification.RepoID] 536 - } 537 - if notification.Repository == nil { 538 - log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID) 539 - failed = append(failed, i) 540 - continue 541 - } 542 - var found bool 543 - for _, r := range reposList { 544 - if r.ID == notification.RepoID { 545 - found = true 546 - break 547 - } 548 - } 549 - if !found { 550 - reposList = append(reposList, notification.Repository) 551 - } 552 - } 553 - return reposList, failed, nil 554 - } 555 - 556 - func (nl NotificationList) getPendingIssueIDs() []int64 { 557 - ids := make(container.Set[int64], len(nl)) 558 - for _, notification := range nl { 559 - if notification.Issue != nil { 560 - continue 561 - } 562 - ids.Add(notification.IssueID) 563 - } 564 - return ids.Values() 565 - } 566 - 567 - // LoadIssues loads issues from database 568 - func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) { 569 - if len(nl) == 0 { 570 - return []int{}, nil 571 - } 572 - 573 - issueIDs := nl.getPendingIssueIDs() 574 - issues := make(map[int64]*issues_model.Issue, len(issueIDs)) 575 - left := len(issueIDs) 576 - for left > 0 { 577 - limit := db.DefaultMaxInSize 578 - if left < limit { 579 - limit = left 580 - } 581 - rows, err := db.GetEngine(ctx). 582 - In("id", issueIDs[:limit]). 583 - Rows(new(issues_model.Issue)) 584 - if err != nil { 585 - return nil, err 586 - } 587 - 588 - for rows.Next() { 589 - var issue issues_model.Issue 590 - err = rows.Scan(&issue) 591 - if err != nil { 592 - rows.Close() 593 - return nil, err 594 - } 595 - 596 - issues[issue.ID] = &issue 597 - } 598 - _ = rows.Close() 599 - 600 - left -= limit 601 - issueIDs = issueIDs[limit:] 602 - } 603 - 604 - failures := []int{} 605 - 606 - for i, notification := range nl { 607 - if notification.Issue == nil { 608 - notification.Issue = issues[notification.IssueID] 609 - if notification.Issue == nil { 610 - if notification.IssueID != 0 { 611 - log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID) 612 - failures = append(failures, i) 613 - } 614 - continue 615 - } 616 - notification.Issue.Repo = notification.Repository 617 - } 618 - } 619 - return failures, nil 620 - } 621 - 622 - // Without returns the notification list without the failures 623 - func (nl NotificationList) Without(failures []int) NotificationList { 624 - if len(failures) == 0 { 625 - return nl 626 - } 627 - remaining := make([]*Notification, 0, len(nl)) 628 - last := -1 629 - var i int 630 - for _, i = range failures { 631 - remaining = append(remaining, nl[last+1:i]...) 632 - last = i 633 - } 634 - if len(nl) > i { 635 - remaining = append(remaining, nl[i+1:]...) 636 - } 637 - return remaining 638 - } 639 - 640 - func (nl NotificationList) getPendingCommentIDs() []int64 { 641 - ids := make(container.Set[int64], len(nl)) 642 - for _, notification := range nl { 643 - if notification.CommentID == 0 || notification.Comment != nil { 644 - continue 645 - } 646 - ids.Add(notification.CommentID) 647 - } 648 - return ids.Values() 649 - } 650 - 651 - func (nl NotificationList) getUserIDs() []int64 { 652 - ids := make(container.Set[int64], len(nl)) 653 - for _, notification := range nl { 654 - if notification.UserID == 0 || notification.User != nil { 655 - continue 656 - } 657 - ids.Add(notification.UserID) 658 - } 659 - return ids.Values() 660 - } 661 - 662 - // LoadUsers loads users from database 663 - func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) { 664 - if len(nl) == 0 { 665 - return []int{}, nil 666 - } 667 - 668 - userIDs := nl.getUserIDs() 669 - users := make(map[int64]*user_model.User, len(userIDs)) 670 - left := len(userIDs) 671 - for left > 0 { 672 - limit := db.DefaultMaxInSize 673 - if left < limit { 674 - limit = left 675 - } 676 - rows, err := db.GetEngine(ctx). 677 - In("id", userIDs[:limit]). 678 - Rows(new(user_model.User)) 679 - if err != nil { 680 - return nil, err 681 - } 682 - 683 - for rows.Next() { 684 - var user user_model.User 685 - err = rows.Scan(&user) 686 - if err != nil { 687 - rows.Close() 688 - return nil, err 689 - } 690 - 691 - users[user.ID] = &user 692 - } 693 - _ = rows.Close() 694 - 695 - left -= limit 696 - userIDs = userIDs[limit:] 697 - } 698 - 699 - failures := []int{} 700 - for i, notification := range nl { 701 - if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil { 702 - notification.User = users[notification.UserID] 703 - if notification.User == nil { 704 - log.Error("Notification[%d]: UserID[%d] failed to load", notification.ID, notification.UserID) 705 - failures = append(failures, i) 706 - continue 707 - } 708 - } 709 - } 710 - return failures, nil 711 - } 712 - 713 - // LoadComments loads comments from database 714 - func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) { 715 - if len(nl) == 0 { 716 - return []int{}, nil 717 - } 718 - 719 - commentIDs := nl.getPendingCommentIDs() 720 - comments := make(map[int64]*issues_model.Comment, len(commentIDs)) 721 - left := len(commentIDs) 722 - for left > 0 { 723 - limit := db.DefaultMaxInSize 724 - if left < limit { 725 - limit = left 726 - } 727 - rows, err := db.GetEngine(ctx). 728 - In("id", commentIDs[:limit]). 729 - Rows(new(issues_model.Comment)) 730 - if err != nil { 731 - return nil, err 732 - } 733 - 734 - for rows.Next() { 735 - var comment issues_model.Comment 736 - err = rows.Scan(&comment) 737 - if err != nil { 738 - rows.Close() 739 - return nil, err 740 - } 741 - 742 - comments[comment.ID] = &comment 743 - } 744 - _ = rows.Close() 745 - 746 - left -= limit 747 - commentIDs = commentIDs[limit:] 748 - } 749 - 750 - failures := []int{} 751 - for i, notification := range nl { 752 - if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil { 753 - notification.Comment = comments[notification.CommentID] 754 - if notification.Comment == nil { 755 - log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID) 756 - failures = append(failures, i) 757 - continue 758 - } 759 - notification.Comment.Issue = notification.Issue 760 - } 761 - } 762 - return failures, nil 763 297 } 764 298 765 299 // SetIssueReadBy sets issue to be read by given user.
+480
models/activities/notification_list.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package activities 5 + 6 + import ( 7 + "context" 8 + 9 + "code.gitea.io/gitea/models/db" 10 + issues_model "code.gitea.io/gitea/models/issues" 11 + access_model "code.gitea.io/gitea/models/perm/access" 12 + repo_model "code.gitea.io/gitea/models/repo" 13 + "code.gitea.io/gitea/models/unit" 14 + user_model "code.gitea.io/gitea/models/user" 15 + "code.gitea.io/gitea/modules/container" 16 + "code.gitea.io/gitea/modules/log" 17 + 18 + "xorm.io/builder" 19 + ) 20 + 21 + // FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored. 22 + type FindNotificationOptions struct { 23 + db.ListOptions 24 + UserID int64 25 + RepoID int64 26 + IssueID int64 27 + Status []NotificationStatus 28 + Source []NotificationSource 29 + UpdatedAfterUnix int64 30 + UpdatedBeforeUnix int64 31 + } 32 + 33 + // ToCond will convert each condition into a xorm-Cond 34 + func (opts FindNotificationOptions) ToConds() builder.Cond { 35 + cond := builder.NewCond() 36 + if opts.UserID != 0 { 37 + cond = cond.And(builder.Eq{"notification.user_id": opts.UserID}) 38 + } 39 + if opts.RepoID != 0 { 40 + cond = cond.And(builder.Eq{"notification.repo_id": opts.RepoID}) 41 + } 42 + if opts.IssueID != 0 { 43 + cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID}) 44 + } 45 + if len(opts.Status) > 0 { 46 + if len(opts.Status) == 1 { 47 + cond = cond.And(builder.Eq{"notification.status": opts.Status[0]}) 48 + } else { 49 + cond = cond.And(builder.In("notification.status", opts.Status)) 50 + } 51 + } 52 + if len(opts.Source) > 0 { 53 + cond = cond.And(builder.In("notification.source", opts.Source)) 54 + } 55 + if opts.UpdatedAfterUnix != 0 { 56 + cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix}) 57 + } 58 + if opts.UpdatedBeforeUnix != 0 { 59 + cond = cond.And(builder.Lte{"notification.updated_unix": opts.UpdatedBeforeUnix}) 60 + } 61 + return cond 62 + } 63 + 64 + func (opts FindNotificationOptions) ToOrders() string { 65 + return "notification.updated_unix DESC" 66 + } 67 + 68 + // CreateOrUpdateIssueNotifications creates an issue notification 69 + // for each watcher, or updates it if already exists 70 + // receiverID > 0 just send to receiver, else send to all watcher 71 + func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { 72 + ctx, committer, err := db.TxContext(ctx) 73 + if err != nil { 74 + return err 75 + } 76 + defer committer.Close() 77 + 78 + if err := createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID); err != nil { 79 + return err 80 + } 81 + 82 + return committer.Commit() 83 + } 84 + 85 + func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { 86 + // init 87 + var toNotify container.Set[int64] 88 + notifications, err := db.Find[Notification](ctx, FindNotificationOptions{ 89 + IssueID: issueID, 90 + }) 91 + if err != nil { 92 + return err 93 + } 94 + 95 + issue, err := issues_model.GetIssueByID(ctx, issueID) 96 + if err != nil { 97 + return err 98 + } 99 + 100 + if receiverID > 0 { 101 + toNotify = make(container.Set[int64], 1) 102 + toNotify.Add(receiverID) 103 + } else { 104 + toNotify = make(container.Set[int64], 32) 105 + issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true) 106 + if err != nil { 107 + return err 108 + } 109 + toNotify.AddMultiple(issueWatches...) 110 + if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) { 111 + repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID) 112 + if err != nil { 113 + return err 114 + } 115 + toNotify.AddMultiple(repoWatches...) 116 + } 117 + issueParticipants, err := issue.GetParticipantIDsByIssue(ctx) 118 + if err != nil { 119 + return err 120 + } 121 + toNotify.AddMultiple(issueParticipants...) 122 + 123 + // dont notify user who cause notification 124 + delete(toNotify, notificationAuthorID) 125 + // explicit unwatch on issue 126 + issueUnWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, false) 127 + if err != nil { 128 + return err 129 + } 130 + for _, id := range issueUnWatches { 131 + toNotify.Remove(id) 132 + } 133 + // Remove users who have the notification author blocked. 134 + blockedAuthorIDs, err := user_model.ListBlockedByUsersID(ctx, notificationAuthorID) 135 + if err != nil { 136 + return err 137 + } 138 + for _, id := range blockedAuthorIDs { 139 + toNotify.Remove(id) 140 + } 141 + } 142 + 143 + err = issue.LoadRepo(ctx) 144 + if err != nil { 145 + return err 146 + } 147 + 148 + // notify 149 + for userID := range toNotify { 150 + issue.Repo.Units = nil 151 + user, err := user_model.GetUserByID(ctx, userID) 152 + if err != nil { 153 + if user_model.IsErrUserNotExist(err) { 154 + continue 155 + } 156 + 157 + return err 158 + } 159 + if issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) { 160 + continue 161 + } 162 + if !issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) { 163 + continue 164 + } 165 + 166 + if notificationExists(notifications, issue.ID, userID) { 167 + if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil { 168 + return err 169 + } 170 + continue 171 + } 172 + if err = createIssueNotification(ctx, userID, issue, commentID, notificationAuthorID); err != nil { 173 + return err 174 + } 175 + } 176 + return nil 177 + } 178 + 179 + // NotificationList contains a list of notifications 180 + type NotificationList []*Notification 181 + 182 + // LoadAttributes load Repo Issue User and Comment if not loaded 183 + func (nl NotificationList) LoadAttributes(ctx context.Context) error { 184 + if _, _, err := nl.LoadRepos(ctx); err != nil { 185 + return err 186 + } 187 + if _, err := nl.LoadIssues(ctx); err != nil { 188 + return err 189 + } 190 + if _, err := nl.LoadUsers(ctx); err != nil { 191 + return err 192 + } 193 + if _, err := nl.LoadComments(ctx); err != nil { 194 + return err 195 + } 196 + return nil 197 + } 198 + 199 + func (nl NotificationList) getPendingRepoIDs() []int64 { 200 + ids := make(container.Set[int64], len(nl)) 201 + for _, notification := range nl { 202 + if notification.Repository != nil { 203 + continue 204 + } 205 + ids.Add(notification.RepoID) 206 + } 207 + return ids.Values() 208 + } 209 + 210 + // LoadRepos loads repositories from database 211 + func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.RepositoryList, []int, error) { 212 + if len(nl) == 0 { 213 + return repo_model.RepositoryList{}, []int{}, nil 214 + } 215 + 216 + repoIDs := nl.getPendingRepoIDs() 217 + repos := make(map[int64]*repo_model.Repository, len(repoIDs)) 218 + left := len(repoIDs) 219 + for left > 0 { 220 + limit := db.DefaultMaxInSize 221 + if left < limit { 222 + limit = left 223 + } 224 + rows, err := db.GetEngine(ctx). 225 + In("id", repoIDs[:limit]). 226 + Rows(new(repo_model.Repository)) 227 + if err != nil { 228 + return nil, nil, err 229 + } 230 + 231 + for rows.Next() { 232 + var repo repo_model.Repository 233 + err = rows.Scan(&repo) 234 + if err != nil { 235 + rows.Close() 236 + return nil, nil, err 237 + } 238 + 239 + repos[repo.ID] = &repo 240 + } 241 + _ = rows.Close() 242 + 243 + left -= limit 244 + repoIDs = repoIDs[limit:] 245 + } 246 + 247 + failed := []int{} 248 + 249 + reposList := make(repo_model.RepositoryList, 0, len(repoIDs)) 250 + for i, notification := range nl { 251 + if notification.Repository == nil { 252 + notification.Repository = repos[notification.RepoID] 253 + } 254 + if notification.Repository == nil { 255 + log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID) 256 + failed = append(failed, i) 257 + continue 258 + } 259 + var found bool 260 + for _, r := range reposList { 261 + if r.ID == notification.RepoID { 262 + found = true 263 + break 264 + } 265 + } 266 + if !found { 267 + reposList = append(reposList, notification.Repository) 268 + } 269 + } 270 + return reposList, failed, nil 271 + } 272 + 273 + func (nl NotificationList) getPendingIssueIDs() []int64 { 274 + ids := make(container.Set[int64], len(nl)) 275 + for _, notification := range nl { 276 + if notification.Issue != nil { 277 + continue 278 + } 279 + ids.Add(notification.IssueID) 280 + } 281 + return ids.Values() 282 + } 283 + 284 + // LoadIssues loads issues from database 285 + func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) { 286 + if len(nl) == 0 { 287 + return []int{}, nil 288 + } 289 + 290 + issueIDs := nl.getPendingIssueIDs() 291 + issues := make(map[int64]*issues_model.Issue, len(issueIDs)) 292 + left := len(issueIDs) 293 + for left > 0 { 294 + limit := db.DefaultMaxInSize 295 + if left < limit { 296 + limit = left 297 + } 298 + rows, err := db.GetEngine(ctx). 299 + In("id", issueIDs[:limit]). 300 + Rows(new(issues_model.Issue)) 301 + if err != nil { 302 + return nil, err 303 + } 304 + 305 + for rows.Next() { 306 + var issue issues_model.Issue 307 + err = rows.Scan(&issue) 308 + if err != nil { 309 + rows.Close() 310 + return nil, err 311 + } 312 + 313 + issues[issue.ID] = &issue 314 + } 315 + _ = rows.Close() 316 + 317 + left -= limit 318 + issueIDs = issueIDs[limit:] 319 + } 320 + 321 + failures := []int{} 322 + 323 + for i, notification := range nl { 324 + if notification.Issue == nil { 325 + notification.Issue = issues[notification.IssueID] 326 + if notification.Issue == nil { 327 + if notification.IssueID != 0 { 328 + log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID) 329 + failures = append(failures, i) 330 + } 331 + continue 332 + } 333 + notification.Issue.Repo = notification.Repository 334 + } 335 + } 336 + return failures, nil 337 + } 338 + 339 + // Without returns the notification list without the failures 340 + func (nl NotificationList) Without(failures []int) NotificationList { 341 + if len(failures) == 0 { 342 + return nl 343 + } 344 + remaining := make([]*Notification, 0, len(nl)) 345 + last := -1 346 + var i int 347 + for _, i = range failures { 348 + remaining = append(remaining, nl[last+1:i]...) 349 + last = i 350 + } 351 + if len(nl) > i { 352 + remaining = append(remaining, nl[i+1:]...) 353 + } 354 + return remaining 355 + } 356 + 357 + func (nl NotificationList) getPendingCommentIDs() []int64 { 358 + ids := make(container.Set[int64], len(nl)) 359 + for _, notification := range nl { 360 + if notification.CommentID == 0 || notification.Comment != nil { 361 + continue 362 + } 363 + ids.Add(notification.CommentID) 364 + } 365 + return ids.Values() 366 + } 367 + 368 + func (nl NotificationList) getUserIDs() []int64 { 369 + ids := make(container.Set[int64], len(nl)) 370 + for _, notification := range nl { 371 + if notification.UserID == 0 || notification.User != nil { 372 + continue 373 + } 374 + ids.Add(notification.UserID) 375 + } 376 + return ids.Values() 377 + } 378 + 379 + // LoadUsers loads users from database 380 + func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) { 381 + if len(nl) == 0 { 382 + return []int{}, nil 383 + } 384 + 385 + userIDs := nl.getUserIDs() 386 + users := make(map[int64]*user_model.User, len(userIDs)) 387 + left := len(userIDs) 388 + for left > 0 { 389 + limit := db.DefaultMaxInSize 390 + if left < limit { 391 + limit = left 392 + } 393 + rows, err := db.GetEngine(ctx). 394 + In("id", userIDs[:limit]). 395 + Rows(new(user_model.User)) 396 + if err != nil { 397 + return nil, err 398 + } 399 + 400 + for rows.Next() { 401 + var user user_model.User 402 + err = rows.Scan(&user) 403 + if err != nil { 404 + rows.Close() 405 + return nil, err 406 + } 407 + 408 + users[user.ID] = &user 409 + } 410 + _ = rows.Close() 411 + 412 + left -= limit 413 + userIDs = userIDs[limit:] 414 + } 415 + 416 + failures := []int{} 417 + for i, notification := range nl { 418 + if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil { 419 + notification.User = users[notification.UserID] 420 + if notification.User == nil { 421 + log.Error("Notification[%d]: UserID[%d] failed to load", notification.ID, notification.UserID) 422 + failures = append(failures, i) 423 + continue 424 + } 425 + } 426 + } 427 + return failures, nil 428 + } 429 + 430 + // LoadComments loads comments from database 431 + func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) { 432 + if len(nl) == 0 { 433 + return []int{}, nil 434 + } 435 + 436 + commentIDs := nl.getPendingCommentIDs() 437 + comments := make(map[int64]*issues_model.Comment, len(commentIDs)) 438 + left := len(commentIDs) 439 + for left > 0 { 440 + limit := db.DefaultMaxInSize 441 + if left < limit { 442 + limit = left 443 + } 444 + rows, err := db.GetEngine(ctx). 445 + In("id", commentIDs[:limit]). 446 + Rows(new(issues_model.Comment)) 447 + if err != nil { 448 + return nil, err 449 + } 450 + 451 + for rows.Next() { 452 + var comment issues_model.Comment 453 + err = rows.Scan(&comment) 454 + if err != nil { 455 + rows.Close() 456 + return nil, err 457 + } 458 + 459 + comments[comment.ID] = &comment 460 + } 461 + _ = rows.Close() 462 + 463 + left -= limit 464 + commentIDs = commentIDs[limit:] 465 + } 466 + 467 + failures := []int{} 468 + for i, notification := range nl { 469 + if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil { 470 + notification.Comment = comments[notification.CommentID] 471 + if notification.Comment == nil { 472 + log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID) 473 + failures = append(failures, i) 474 + continue 475 + } 476 + notification.Comment.Issue = notification.Issue 477 + } 478 + } 479 + return failures, nil 480 + }