code.gitea.io/gitea@v1.19.3/modules/notification/ui/ui.go (about)

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