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 = &notificationService{}
34)
35
36// NewNotifier create a new notificationService notifier
37func NewNotifier() base.Notifier {
38	ns := &notificationService{}
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