1// Copyright 2018 The Gitea Authors. All rights reserved. 2// Use of this source code is governed by a MIT-style 3// license that can be found in the LICENSE file. 4 5package ui 6 7import ( 8 "code.gitea.io/gitea/models" 9 "code.gitea.io/gitea/models/db" 10 repo_model "code.gitea.io/gitea/models/repo" 11 user_model "code.gitea.io/gitea/models/user" 12 "code.gitea.io/gitea/modules/graceful" 13 "code.gitea.io/gitea/modules/log" 14 "code.gitea.io/gitea/modules/notification/base" 15 "code.gitea.io/gitea/modules/queue" 16) 17 18type ( 19 notificationService struct { 20 base.NullNotifier 21 issueQueue queue.Queue 22 } 23 24 issueNotificationOpts struct { 25 IssueID int64 26 CommentID int64 27 NotificationAuthorID int64 28 ReceiverID int64 // 0 -- ALL Watcher 29 } 30) 31 32var ( 33 _ base.Notifier = ¬ificationService{} 34) 35 36// NewNotifier create a new notificationService notifier 37func NewNotifier() base.Notifier { 38 ns := ¬ificationService{} 39 ns.issueQueue = queue.CreateQueue("notification-service", ns.handle, issueNotificationOpts{}) 40 return ns 41} 42 43func (ns *notificationService) handle(data ...queue.Data) { 44 for _, datum := range data { 45 opts := datum.(issueNotificationOpts) 46 if err := models.CreateOrUpdateIssueNotifications(opts.IssueID, opts.CommentID, opts.NotificationAuthorID, opts.ReceiverID); err != nil { 47 log.Error("Was unable to create issue notification: %v", err) 48 } 49 } 50} 51 52func (ns *notificationService) Run() { 53 graceful.GetManager().RunWithShutdownFns(ns.issueQueue.Run) 54} 55 56func (ns *notificationService) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository, 57 issue *models.Issue, comment *models.Comment, mentions []*user_model.User) { 58 var opts = issueNotificationOpts{ 59 IssueID: issue.ID, 60 NotificationAuthorID: doer.ID, 61 } 62 if comment != nil { 63 opts.CommentID = comment.ID 64 } 65 _ = ns.issueQueue.Push(opts) 66 for _, mention := range mentions { 67 var opts = issueNotificationOpts{ 68 IssueID: issue.ID, 69 NotificationAuthorID: doer.ID, 70 ReceiverID: mention.ID, 71 } 72 if comment != nil { 73 opts.CommentID = comment.ID 74 } 75 _ = ns.issueQueue.Push(opts) 76 } 77} 78 79func (ns *notificationService) NotifyNewIssue(issue *models.Issue, mentions []*user_model.User) { 80 _ = ns.issueQueue.Push(issueNotificationOpts{ 81 IssueID: issue.ID, 82 NotificationAuthorID: issue.Poster.ID, 83 }) 84 for _, mention := range mentions { 85 _ = ns.issueQueue.Push(issueNotificationOpts{ 86 IssueID: issue.ID, 87 NotificationAuthorID: issue.Poster.ID, 88 ReceiverID: mention.ID, 89 }) 90 } 91} 92 93func (ns *notificationService) NotifyIssueChangeStatus(doer *user_model.User, issue *models.Issue, actionComment *models.Comment, isClosed bool) { 94 _ = ns.issueQueue.Push(issueNotificationOpts{ 95 IssueID: issue.ID, 96 NotificationAuthorID: doer.ID, 97 }) 98} 99 100func (ns *notificationService) NotifyIssueChangeTitle(doer *user_model.User, issue *models.Issue, oldTitle string) { 101 if err := issue.LoadPullRequest(); err != nil { 102 log.Error("issue.LoadPullRequest: %v", err) 103 return 104 } 105 if issue.IsPull && models.HasWorkInProgressPrefix(oldTitle) && !issue.PullRequest.IsWorkInProgress() { 106 _ = ns.issueQueue.Push(issueNotificationOpts{ 107 IssueID: issue.ID, 108 NotificationAuthorID: doer.ID, 109 }) 110 } 111} 112 113func (ns *notificationService) NotifyMergePullRequest(pr *models.PullRequest, doer *user_model.User) { 114 _ = ns.issueQueue.Push(issueNotificationOpts{ 115 IssueID: pr.Issue.ID, 116 NotificationAuthorID: doer.ID, 117 }) 118} 119 120func (ns *notificationService) NotifyNewPullRequest(pr *models.PullRequest, mentions []*user_model.User) { 121 if err := pr.LoadIssue(); err != nil { 122 log.Error("Unable to load issue: %d for pr: %d: Error: %v", pr.IssueID, pr.ID, err) 123 return 124 } 125 toNotify := make(map[int64]struct{}, 32) 126 repoWatchers, err := repo_model.GetRepoWatchersIDs(db.DefaultContext, pr.Issue.RepoID) 127 if err != nil { 128 log.Error("GetRepoWatchersIDs: %v", err) 129 return 130 } 131 for _, id := range repoWatchers { 132 toNotify[id] = struct{}{} 133 } 134 issueParticipants, err := models.GetParticipantsIDsByIssueID(pr.IssueID) 135 if err != nil { 136 log.Error("GetParticipantsIDsByIssueID: %v", err) 137 return 138 } 139 for _, id := range issueParticipants { 140 toNotify[id] = struct{}{} 141 } 142 delete(toNotify, pr.Issue.PosterID) 143 for _, mention := range mentions { 144 toNotify[mention.ID] = struct{}{} 145 } 146 for receiverID := range toNotify { 147 _ = ns.issueQueue.Push(issueNotificationOpts{ 148 IssueID: pr.Issue.ID, 149 NotificationAuthorID: pr.Issue.PosterID, 150 ReceiverID: receiverID, 151 }) 152 } 153} 154 155func (ns *notificationService) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, c *models.Comment, mentions []*user_model.User) { 156 var opts = issueNotificationOpts{ 157 IssueID: pr.Issue.ID, 158 NotificationAuthorID: r.Reviewer.ID, 159 } 160 if c != nil { 161 opts.CommentID = c.ID 162 } 163 _ = ns.issueQueue.Push(opts) 164 for _, mention := range mentions { 165 var opts = issueNotificationOpts{ 166 IssueID: pr.Issue.ID, 167 NotificationAuthorID: r.Reviewer.ID, 168 ReceiverID: mention.ID, 169 } 170 if c != nil { 171 opts.CommentID = c.ID 172 } 173 _ = ns.issueQueue.Push(opts) 174 } 175} 176 177func (ns *notificationService) NotifyPullRequestCodeComment(pr *models.PullRequest, c *models.Comment, mentions []*user_model.User) { 178 for _, mention := range mentions { 179 _ = ns.issueQueue.Push(issueNotificationOpts{ 180 IssueID: pr.Issue.ID, 181 NotificationAuthorID: c.Poster.ID, 182 CommentID: c.ID, 183 ReceiverID: mention.ID, 184 }) 185 } 186} 187 188func (ns *notificationService) NotifyPullRequestPushCommits(doer *user_model.User, pr *models.PullRequest, comment *models.Comment) { 189 var opts = issueNotificationOpts{ 190 IssueID: pr.IssueID, 191 NotificationAuthorID: doer.ID, 192 CommentID: comment.ID, 193 } 194 _ = ns.issueQueue.Push(opts) 195} 196 197func (ns *notificationService) NotifyPullRevieweDismiss(doer *user_model.User, review *models.Review, comment *models.Comment) { 198 var opts = issueNotificationOpts{ 199 IssueID: review.IssueID, 200 NotificationAuthorID: doer.ID, 201 CommentID: comment.ID, 202 } 203 _ = ns.issueQueue.Push(opts) 204} 205 206func (ns *notificationService) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) { 207 if !removed && doer.ID != assignee.ID { 208 var opts = issueNotificationOpts{ 209 IssueID: issue.ID, 210 NotificationAuthorID: doer.ID, 211 ReceiverID: assignee.ID, 212 } 213 214 if comment != nil { 215 opts.CommentID = comment.ID 216 } 217 218 _ = ns.issueQueue.Push(opts) 219 } 220} 221 222func (ns *notificationService) NotifyPullReviewRequest(doer *user_model.User, issue *models.Issue, reviewer *user_model.User, isRequest bool, comment *models.Comment) { 223 if isRequest { 224 var opts = issueNotificationOpts{ 225 IssueID: issue.ID, 226 NotificationAuthorID: doer.ID, 227 ReceiverID: reviewer.ID, 228 } 229 230 if comment != nil { 231 opts.CommentID = comment.ID 232 } 233 234 _ = ns.issueQueue.Push(opts) 235 } 236} 237 238func (ns *notificationService) NotifyRepoPendingTransfer(doer, newOwner *user_model.User, repo *repo_model.Repository) { 239 if err := models.CreateRepoTransferNotification(doer, newOwner, repo); err != nil { 240 log.Error("NotifyRepoPendingTransfer: %v", err) 241 } 242} 243