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

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package action
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"path"
    10  	"strings"
    11  
    12  	activities_model "code.gitea.io/gitea/models/activities"
    13  	issues_model "code.gitea.io/gitea/models/issues"
    14  	repo_model "code.gitea.io/gitea/models/repo"
    15  	user_model "code.gitea.io/gitea/models/user"
    16  	"code.gitea.io/gitea/modules/json"
    17  	"code.gitea.io/gitea/modules/log"
    18  	"code.gitea.io/gitea/modules/notification/base"
    19  	"code.gitea.io/gitea/modules/repository"
    20  	"code.gitea.io/gitea/modules/util"
    21  )
    22  
    23  type actionNotifier struct {
    24  	base.NullNotifier
    25  }
    26  
    27  var _ base.Notifier = &actionNotifier{}
    28  
    29  // NewNotifier create a new actionNotifier notifier
    30  func NewNotifier() base.Notifier {
    31  	return &actionNotifier{}
    32  }
    33  
    34  func (a *actionNotifier) NotifyNewIssue(ctx context.Context, issue *issues_model.Issue, mentions []*user_model.User) {
    35  	if err := issue.LoadPoster(ctx); err != nil {
    36  		log.Error("issue.LoadPoster: %v", err)
    37  		return
    38  	}
    39  	if err := issue.LoadRepo(ctx); err != nil {
    40  		log.Error("issue.LoadRepo: %v", err)
    41  		return
    42  	}
    43  	repo := issue.Repo
    44  
    45  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
    46  		ActUserID: issue.Poster.ID,
    47  		ActUser:   issue.Poster,
    48  		OpType:    activities_model.ActionCreateIssue,
    49  		Content:   fmt.Sprintf("%d|%s", issue.Index, issue.Title),
    50  		RepoID:    repo.ID,
    51  		Repo:      repo,
    52  		IsPrivate: repo.IsPrivate,
    53  	}); err != nil {
    54  		log.Error("NotifyWatchers: %v", err)
    55  	}
    56  }
    57  
    58  // NotifyIssueChangeStatus notifies close or reopen issue to notifiers
    59  func (a *actionNotifier) NotifyIssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, closeOrReopen bool) {
    60  	// Compose comment action, could be plain comment, close or reopen issue/pull request.
    61  	// This object will be used to notify watchers in the end of function.
    62  	act := &activities_model.Action{
    63  		ActUserID: doer.ID,
    64  		ActUser:   doer,
    65  		Content:   fmt.Sprintf("%d|%s", issue.Index, ""),
    66  		RepoID:    issue.Repo.ID,
    67  		Repo:      issue.Repo,
    68  		Comment:   actionComment,
    69  		CommentID: actionComment.ID,
    70  		IsPrivate: issue.Repo.IsPrivate,
    71  	}
    72  	// Check comment type.
    73  	if closeOrReopen {
    74  		act.OpType = activities_model.ActionCloseIssue
    75  		if issue.IsPull {
    76  			act.OpType = activities_model.ActionClosePullRequest
    77  		}
    78  	} else {
    79  		act.OpType = activities_model.ActionReopenIssue
    80  		if issue.IsPull {
    81  			act.OpType = activities_model.ActionReopenPullRequest
    82  		}
    83  	}
    84  
    85  	// Notify watchers for whatever action comes in, ignore if no action type.
    86  	if err := activities_model.NotifyWatchers(ctx, act); err != nil {
    87  		log.Error("NotifyWatchers: %v", err)
    88  	}
    89  }
    90  
    91  // NotifyCreateIssueComment notifies comment on an issue to notifiers
    92  func (a *actionNotifier) NotifyCreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository,
    93  	issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
    94  ) {
    95  	act := &activities_model.Action{
    96  		ActUserID: doer.ID,
    97  		ActUser:   doer,
    98  		RepoID:    issue.Repo.ID,
    99  		Repo:      issue.Repo,
   100  		Comment:   comment,
   101  		CommentID: comment.ID,
   102  		IsPrivate: issue.Repo.IsPrivate,
   103  	}
   104  
   105  	truncatedContent, truncatedRight := util.SplitStringAtByteN(comment.Content, 200)
   106  	if truncatedRight != "" {
   107  		// in case the content is in a Latin family language, we remove the last broken word.
   108  		lastSpaceIdx := strings.LastIndex(truncatedContent, " ")
   109  		if lastSpaceIdx != -1 && (len(truncatedContent)-lastSpaceIdx < 15) {
   110  			truncatedContent = truncatedContent[:lastSpaceIdx] + "…"
   111  		}
   112  	}
   113  	act.Content = fmt.Sprintf("%d|%s", issue.Index, truncatedContent)
   114  
   115  	if issue.IsPull {
   116  		act.OpType = activities_model.ActionCommentPull
   117  	} else {
   118  		act.OpType = activities_model.ActionCommentIssue
   119  	}
   120  
   121  	// Notify watchers for whatever action comes in, ignore if no action type.
   122  	if err := activities_model.NotifyWatchers(ctx, act); err != nil {
   123  		log.Error("NotifyWatchers: %v", err)
   124  	}
   125  }
   126  
   127  func (a *actionNotifier) NotifyNewPullRequest(ctx context.Context, pull *issues_model.PullRequest, mentions []*user_model.User) {
   128  	if err := pull.LoadIssue(ctx); err != nil {
   129  		log.Error("pull.LoadIssue: %v", err)
   130  		return
   131  	}
   132  	if err := pull.Issue.LoadRepo(ctx); err != nil {
   133  		log.Error("pull.Issue.LoadRepo: %v", err)
   134  		return
   135  	}
   136  	if err := pull.Issue.LoadPoster(ctx); err != nil {
   137  		log.Error("pull.Issue.LoadPoster: %v", err)
   138  		return
   139  	}
   140  
   141  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   142  		ActUserID: pull.Issue.Poster.ID,
   143  		ActUser:   pull.Issue.Poster,
   144  		OpType:    activities_model.ActionCreatePullRequest,
   145  		Content:   fmt.Sprintf("%d|%s", pull.Issue.Index, pull.Issue.Title),
   146  		RepoID:    pull.Issue.Repo.ID,
   147  		Repo:      pull.Issue.Repo,
   148  		IsPrivate: pull.Issue.Repo.IsPrivate,
   149  	}); err != nil {
   150  		log.Error("NotifyWatchers: %v", err)
   151  	}
   152  }
   153  
   154  func (a *actionNotifier) NotifyRenameRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldRepoName string) {
   155  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   156  		ActUserID: doer.ID,
   157  		ActUser:   doer,
   158  		OpType:    activities_model.ActionRenameRepo,
   159  		RepoID:    repo.ID,
   160  		Repo:      repo,
   161  		IsPrivate: repo.IsPrivate,
   162  		Content:   oldRepoName,
   163  	}); err != nil {
   164  		log.Error("NotifyWatchers: %v", err)
   165  	}
   166  }
   167  
   168  func (a *actionNotifier) NotifyTransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldOwnerName string) {
   169  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   170  		ActUserID: doer.ID,
   171  		ActUser:   doer,
   172  		OpType:    activities_model.ActionTransferRepo,
   173  		RepoID:    repo.ID,
   174  		Repo:      repo,
   175  		IsPrivate: repo.IsPrivate,
   176  		Content:   path.Join(oldOwnerName, repo.Name),
   177  	}); err != nil {
   178  		log.Error("NotifyWatchers: %v", err)
   179  	}
   180  }
   181  
   182  func (a *actionNotifier) NotifyCreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
   183  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   184  		ActUserID: doer.ID,
   185  		ActUser:   doer,
   186  		OpType:    activities_model.ActionCreateRepo,
   187  		RepoID:    repo.ID,
   188  		Repo:      repo,
   189  		IsPrivate: repo.IsPrivate,
   190  	}); err != nil {
   191  		log.Error("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
   192  	}
   193  }
   194  
   195  func (a *actionNotifier) NotifyForkRepository(ctx context.Context, doer *user_model.User, oldRepo, repo *repo_model.Repository) {
   196  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   197  		ActUserID: doer.ID,
   198  		ActUser:   doer,
   199  		OpType:    activities_model.ActionCreateRepo,
   200  		RepoID:    repo.ID,
   201  		Repo:      repo,
   202  		IsPrivate: repo.IsPrivate,
   203  	}); err != nil {
   204  		log.Error("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
   205  	}
   206  }
   207  
   208  func (a *actionNotifier) NotifyPullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
   209  	if err := review.LoadReviewer(ctx); err != nil {
   210  		log.Error("LoadReviewer '%d/%d': %v", review.ID, review.ReviewerID, err)
   211  		return
   212  	}
   213  	if err := review.LoadCodeComments(ctx); err != nil {
   214  		log.Error("LoadCodeComments '%d/%d': %v", review.Reviewer.ID, review.ID, err)
   215  		return
   216  	}
   217  
   218  	actions := make([]*activities_model.Action, 0, 10)
   219  	for _, lines := range review.CodeComments {
   220  		for _, comments := range lines {
   221  			for _, comm := range comments {
   222  				actions = append(actions, &activities_model.Action{
   223  					ActUserID: review.Reviewer.ID,
   224  					ActUser:   review.Reviewer,
   225  					Content:   fmt.Sprintf("%d|%s", review.Issue.Index, strings.Split(comm.Content, "\n")[0]),
   226  					OpType:    activities_model.ActionCommentPull,
   227  					RepoID:    review.Issue.RepoID,
   228  					Repo:      review.Issue.Repo,
   229  					IsPrivate: review.Issue.Repo.IsPrivate,
   230  					Comment:   comm,
   231  					CommentID: comm.ID,
   232  				})
   233  			}
   234  		}
   235  	}
   236  
   237  	if review.Type != issues_model.ReviewTypeComment || strings.TrimSpace(comment.Content) != "" {
   238  		action := &activities_model.Action{
   239  			ActUserID: review.Reviewer.ID,
   240  			ActUser:   review.Reviewer,
   241  			Content:   fmt.Sprintf("%d|%s", review.Issue.Index, strings.Split(comment.Content, "\n")[0]),
   242  			RepoID:    review.Issue.RepoID,
   243  			Repo:      review.Issue.Repo,
   244  			IsPrivate: review.Issue.Repo.IsPrivate,
   245  			Comment:   comment,
   246  			CommentID: comment.ID,
   247  		}
   248  
   249  		switch review.Type {
   250  		case issues_model.ReviewTypeApprove:
   251  			action.OpType = activities_model.ActionApprovePullRequest
   252  		case issues_model.ReviewTypeReject:
   253  			action.OpType = activities_model.ActionRejectPullRequest
   254  		default:
   255  			action.OpType = activities_model.ActionCommentPull
   256  		}
   257  
   258  		actions = append(actions, action)
   259  	}
   260  
   261  	if err := activities_model.NotifyWatchersActions(actions); err != nil {
   262  		log.Error("notify watchers '%d/%d': %v", review.Reviewer.ID, review.Issue.RepoID, err)
   263  	}
   264  }
   265  
   266  func (*actionNotifier) NotifyMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
   267  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   268  		ActUserID: doer.ID,
   269  		ActUser:   doer,
   270  		OpType:    activities_model.ActionMergePullRequest,
   271  		Content:   fmt.Sprintf("%d|%s", pr.Issue.Index, pr.Issue.Title),
   272  		RepoID:    pr.Issue.Repo.ID,
   273  		Repo:      pr.Issue.Repo,
   274  		IsPrivate: pr.Issue.Repo.IsPrivate,
   275  	}); err != nil {
   276  		log.Error("NotifyWatchers [%d]: %v", pr.ID, err)
   277  	}
   278  }
   279  
   280  func (*actionNotifier) NotifyAutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
   281  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   282  		ActUserID: doer.ID,
   283  		ActUser:   doer,
   284  		OpType:    activities_model.ActionAutoMergePullRequest,
   285  		Content:   fmt.Sprintf("%d|%s", pr.Issue.Index, pr.Issue.Title),
   286  		RepoID:    pr.Issue.Repo.ID,
   287  		Repo:      pr.Issue.Repo,
   288  		IsPrivate: pr.Issue.Repo.IsPrivate,
   289  	}); err != nil {
   290  		log.Error("NotifyWatchers [%d]: %v", pr.ID, err)
   291  	}
   292  }
   293  
   294  func (*actionNotifier) NotifyPullRevieweDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
   295  	reviewerName := review.Reviewer.Name
   296  	if len(review.OriginalAuthor) > 0 {
   297  		reviewerName = review.OriginalAuthor
   298  	}
   299  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   300  		ActUserID: doer.ID,
   301  		ActUser:   doer,
   302  		OpType:    activities_model.ActionPullReviewDismissed,
   303  		Content:   fmt.Sprintf("%d|%s|%s", review.Issue.Index, reviewerName, comment.Content),
   304  		RepoID:    review.Issue.Repo.ID,
   305  		Repo:      review.Issue.Repo,
   306  		IsPrivate: review.Issue.Repo.IsPrivate,
   307  		CommentID: comment.ID,
   308  		Comment:   comment,
   309  	}); err != nil {
   310  		log.Error("NotifyWatchers [%d]: %v", review.Issue.ID, err)
   311  	}
   312  }
   313  
   314  func (a *actionNotifier) NotifyPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
   315  	data, err := json.Marshal(commits)
   316  	if err != nil {
   317  		log.Error("Marshal: %v", err)
   318  		return
   319  	}
   320  
   321  	opType := activities_model.ActionCommitRepo
   322  
   323  	// Check it's tag push or branch.
   324  	if opts.IsTag() {
   325  		opType = activities_model.ActionPushTag
   326  		if opts.IsDelRef() {
   327  			opType = activities_model.ActionDeleteTag
   328  		}
   329  	} else if opts.IsDelRef() {
   330  		opType = activities_model.ActionDeleteBranch
   331  	}
   332  
   333  	if err = activities_model.NotifyWatchers(ctx, &activities_model.Action{
   334  		ActUserID: pusher.ID,
   335  		ActUser:   pusher,
   336  		OpType:    opType,
   337  		Content:   string(data),
   338  		RepoID:    repo.ID,
   339  		Repo:      repo,
   340  		RefName:   opts.RefFullName,
   341  		IsPrivate: repo.IsPrivate,
   342  	}); err != nil {
   343  		log.Error("NotifyWatchers: %v", err)
   344  	}
   345  }
   346  
   347  func (a *actionNotifier) NotifyCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) {
   348  	opType := activities_model.ActionCommitRepo
   349  	if refType == "tag" {
   350  		// has sent same action in `NotifyPushCommits`, so skip it.
   351  		return
   352  	}
   353  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   354  		ActUserID: doer.ID,
   355  		ActUser:   doer,
   356  		OpType:    opType,
   357  		RepoID:    repo.ID,
   358  		Repo:      repo,
   359  		IsPrivate: repo.IsPrivate,
   360  		RefName:   refFullName,
   361  	}); err != nil {
   362  		log.Error("NotifyWatchers: %v", err)
   363  	}
   364  }
   365  
   366  func (a *actionNotifier) NotifyDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refType, refFullName string) {
   367  	opType := activities_model.ActionDeleteBranch
   368  	if refType == "tag" {
   369  		// has sent same action in `NotifyPushCommits`, so skip it.
   370  		return
   371  	}
   372  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   373  		ActUserID: doer.ID,
   374  		ActUser:   doer,
   375  		OpType:    opType,
   376  		RepoID:    repo.ID,
   377  		Repo:      repo,
   378  		IsPrivate: repo.IsPrivate,
   379  		RefName:   refFullName,
   380  	}); err != nil {
   381  		log.Error("NotifyWatchers: %v", err)
   382  	}
   383  }
   384  
   385  func (a *actionNotifier) NotifySyncPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
   386  	data, err := json.Marshal(commits)
   387  	if err != nil {
   388  		log.Error("json.Marshal: %v", err)
   389  		return
   390  	}
   391  
   392  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   393  		ActUserID: repo.OwnerID,
   394  		ActUser:   repo.MustOwner(ctx),
   395  		OpType:    activities_model.ActionMirrorSyncPush,
   396  		RepoID:    repo.ID,
   397  		Repo:      repo,
   398  		IsPrivate: repo.IsPrivate,
   399  		RefName:   opts.RefFullName,
   400  		Content:   string(data),
   401  	}); err != nil {
   402  		log.Error("NotifyWatchers: %v", err)
   403  	}
   404  }
   405  
   406  func (a *actionNotifier) NotifySyncCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) {
   407  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   408  		ActUserID: repo.OwnerID,
   409  		ActUser:   repo.MustOwner(ctx),
   410  		OpType:    activities_model.ActionMirrorSyncCreate,
   411  		RepoID:    repo.ID,
   412  		Repo:      repo,
   413  		IsPrivate: repo.IsPrivate,
   414  		RefName:   refFullName,
   415  	}); err != nil {
   416  		log.Error("NotifyWatchers: %v", err)
   417  	}
   418  }
   419  
   420  func (a *actionNotifier) NotifySyncDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refType, refFullName string) {
   421  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   422  		ActUserID: repo.OwnerID,
   423  		ActUser:   repo.MustOwner(ctx),
   424  		OpType:    activities_model.ActionMirrorSyncDelete,
   425  		RepoID:    repo.ID,
   426  		Repo:      repo,
   427  		IsPrivate: repo.IsPrivate,
   428  		RefName:   refFullName,
   429  	}); err != nil {
   430  		log.Error("NotifyWatchers: %v", err)
   431  	}
   432  }
   433  
   434  func (a *actionNotifier) NotifyNewRelease(ctx context.Context, rel *repo_model.Release) {
   435  	if err := rel.LoadAttributes(ctx); err != nil {
   436  		log.Error("LoadAttributes: %v", err)
   437  		return
   438  	}
   439  	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
   440  		ActUserID: rel.PublisherID,
   441  		ActUser:   rel.Publisher,
   442  		OpType:    activities_model.ActionPublishRelease,
   443  		RepoID:    rel.RepoID,
   444  		Repo:      rel.Repo,
   445  		IsPrivate: rel.Repo.IsPrivate,
   446  		Content:   rel.Title,
   447  		RefName:   rel.TagName,
   448  	}); err != nil {
   449  		log.Error("NotifyWatchers: %v", err)
   450  	}
   451  }