]> source.dussan.org Git - gitea.git/commitdiff
Move feed notification service layer (#26908)
authorLunny Xiao <xiaolunwen@gmail.com>
Tue, 5 Sep 2023 13:00:52 +0000 (21:00 +0800)
committerGitHub <noreply@github.com>
Tue, 5 Sep 2023 13:00:52 +0000 (13:00 +0000)
Extract from #22266

modules/notification/action/action.go [deleted file]
modules/notification/action/action_test.go [deleted file]
modules/notification/notification.go
routers/init.go
services/feed/action.go [new file with mode: 0644]
services/feed/action_test.go [new file with mode: 0644]
services/repository/transfer_test.go

diff --git a/modules/notification/action/action.go b/modules/notification/action/action.go
deleted file mode 100644 (file)
index 1d759e4..0000000
+++ /dev/null
@@ -1,452 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package action
-
-import (
-       "context"
-       "fmt"
-       "path"
-       "strings"
-
-       activities_model "code.gitea.io/gitea/models/activities"
-       issues_model "code.gitea.io/gitea/models/issues"
-       repo_model "code.gitea.io/gitea/models/repo"
-       user_model "code.gitea.io/gitea/models/user"
-       "code.gitea.io/gitea/modules/git"
-       "code.gitea.io/gitea/modules/json"
-       "code.gitea.io/gitea/modules/log"
-       "code.gitea.io/gitea/modules/notification/base"
-       "code.gitea.io/gitea/modules/repository"
-       "code.gitea.io/gitea/modules/util"
-)
-
-type actionNotifier struct {
-       base.NullNotifier
-}
-
-var _ base.Notifier = &actionNotifier{}
-
-// NewNotifier create a new actionNotifier notifier
-func NewNotifier() base.Notifier {
-       return &actionNotifier{}
-}
-
-func (a *actionNotifier) NotifyNewIssue(ctx context.Context, issue *issues_model.Issue, mentions []*user_model.User) {
-       if err := issue.LoadPoster(ctx); err != nil {
-               log.Error("issue.LoadPoster: %v", err)
-               return
-       }
-       if err := issue.LoadRepo(ctx); err != nil {
-               log.Error("issue.LoadRepo: %v", err)
-               return
-       }
-       repo := issue.Repo
-
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: issue.Poster.ID,
-               ActUser:   issue.Poster,
-               OpType:    activities_model.ActionCreateIssue,
-               Content:   fmt.Sprintf("%d|%s", issue.Index, issue.Title),
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-// NotifyIssueChangeStatus notifies close or reopen issue to notifiers
-func (a *actionNotifier) NotifyIssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, closeOrReopen bool) {
-       // Compose comment action, could be plain comment, close or reopen issue/pull request.
-       // This object will be used to notify watchers in the end of function.
-       act := &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               Content:   fmt.Sprintf("%d|%s", issue.Index, ""),
-               RepoID:    issue.Repo.ID,
-               Repo:      issue.Repo,
-               Comment:   actionComment,
-               CommentID: actionComment.ID,
-               IsPrivate: issue.Repo.IsPrivate,
-       }
-       // Check comment type.
-       if closeOrReopen {
-               act.OpType = activities_model.ActionCloseIssue
-               if issue.IsPull {
-                       act.OpType = activities_model.ActionClosePullRequest
-               }
-       } else {
-               act.OpType = activities_model.ActionReopenIssue
-               if issue.IsPull {
-                       act.OpType = activities_model.ActionReopenPullRequest
-               }
-       }
-
-       // Notify watchers for whatever action comes in, ignore if no action type.
-       if err := activities_model.NotifyWatchers(ctx, act); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-// NotifyCreateIssueComment notifies comment on an issue to notifiers
-func (a *actionNotifier) NotifyCreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository,
-       issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
-) {
-       act := &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               RepoID:    issue.Repo.ID,
-               Repo:      issue.Repo,
-               Comment:   comment,
-               CommentID: comment.ID,
-               IsPrivate: issue.Repo.IsPrivate,
-       }
-
-       truncatedContent, truncatedRight := util.SplitStringAtByteN(comment.Content, 200)
-       if truncatedRight != "" {
-               // in case the content is in a Latin family language, we remove the last broken word.
-               lastSpaceIdx := strings.LastIndex(truncatedContent, " ")
-               if lastSpaceIdx != -1 && (len(truncatedContent)-lastSpaceIdx < 15) {
-                       truncatedContent = truncatedContent[:lastSpaceIdx] + "…"
-               }
-       }
-       act.Content = fmt.Sprintf("%d|%s", issue.Index, truncatedContent)
-
-       if issue.IsPull {
-               act.OpType = activities_model.ActionCommentPull
-       } else {
-               act.OpType = activities_model.ActionCommentIssue
-       }
-
-       // Notify watchers for whatever action comes in, ignore if no action type.
-       if err := activities_model.NotifyWatchers(ctx, act); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifyNewPullRequest(ctx context.Context, pull *issues_model.PullRequest, mentions []*user_model.User) {
-       if err := pull.LoadIssue(ctx); err != nil {
-               log.Error("pull.LoadIssue: %v", err)
-               return
-       }
-       if err := pull.Issue.LoadRepo(ctx); err != nil {
-               log.Error("pull.Issue.LoadRepo: %v", err)
-               return
-       }
-       if err := pull.Issue.LoadPoster(ctx); err != nil {
-               log.Error("pull.Issue.LoadPoster: %v", err)
-               return
-       }
-
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: pull.Issue.Poster.ID,
-               ActUser:   pull.Issue.Poster,
-               OpType:    activities_model.ActionCreatePullRequest,
-               Content:   fmt.Sprintf("%d|%s", pull.Issue.Index, pull.Issue.Title),
-               RepoID:    pull.Issue.Repo.ID,
-               Repo:      pull.Issue.Repo,
-               IsPrivate: pull.Issue.Repo.IsPrivate,
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifyRenameRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldRepoName string) {
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    activities_model.ActionRenameRepo,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-               Content:   oldRepoName,
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifyTransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldOwnerName string) {
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    activities_model.ActionTransferRepo,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-               Content:   path.Join(oldOwnerName, repo.Name),
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifyCreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    activities_model.ActionCreateRepo,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-       }); err != nil {
-               log.Error("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
-       }
-}
-
-func (a *actionNotifier) NotifyForkRepository(ctx context.Context, doer *user_model.User, oldRepo, repo *repo_model.Repository) {
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    activities_model.ActionCreateRepo,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-       }); err != nil {
-               log.Error("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
-       }
-}
-
-func (a *actionNotifier) NotifyPullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
-       if err := review.LoadReviewer(ctx); err != nil {
-               log.Error("LoadReviewer '%d/%d': %v", review.ID, review.ReviewerID, err)
-               return
-       }
-       if err := review.LoadCodeComments(ctx); err != nil {
-               log.Error("LoadCodeComments '%d/%d': %v", review.Reviewer.ID, review.ID, err)
-               return
-       }
-
-       actions := make([]*activities_model.Action, 0, 10)
-       for _, lines := range review.CodeComments {
-               for _, comments := range lines {
-                       for _, comm := range comments {
-                               actions = append(actions, &activities_model.Action{
-                                       ActUserID: review.Reviewer.ID,
-                                       ActUser:   review.Reviewer,
-                                       Content:   fmt.Sprintf("%d|%s", review.Issue.Index, strings.Split(comm.Content, "\n")[0]),
-                                       OpType:    activities_model.ActionCommentPull,
-                                       RepoID:    review.Issue.RepoID,
-                                       Repo:      review.Issue.Repo,
-                                       IsPrivate: review.Issue.Repo.IsPrivate,
-                                       Comment:   comm,
-                                       CommentID: comm.ID,
-                               })
-                       }
-               }
-       }
-
-       if review.Type != issues_model.ReviewTypeComment || strings.TrimSpace(comment.Content) != "" {
-               action := &activities_model.Action{
-                       ActUserID: review.Reviewer.ID,
-                       ActUser:   review.Reviewer,
-                       Content:   fmt.Sprintf("%d|%s", review.Issue.Index, strings.Split(comment.Content, "\n")[0]),
-                       RepoID:    review.Issue.RepoID,
-                       Repo:      review.Issue.Repo,
-                       IsPrivate: review.Issue.Repo.IsPrivate,
-                       Comment:   comment,
-                       CommentID: comment.ID,
-               }
-
-               switch review.Type {
-               case issues_model.ReviewTypeApprove:
-                       action.OpType = activities_model.ActionApprovePullRequest
-               case issues_model.ReviewTypeReject:
-                       action.OpType = activities_model.ActionRejectPullRequest
-               default:
-                       action.OpType = activities_model.ActionCommentPull
-               }
-
-               actions = append(actions, action)
-       }
-
-       if err := activities_model.NotifyWatchersActions(actions); err != nil {
-               log.Error("notify watchers '%d/%d': %v", review.Reviewer.ID, review.Issue.RepoID, err)
-       }
-}
-
-func (*actionNotifier) NotifyMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    activities_model.ActionMergePullRequest,
-               Content:   fmt.Sprintf("%d|%s", pr.Issue.Index, pr.Issue.Title),
-               RepoID:    pr.Issue.Repo.ID,
-               Repo:      pr.Issue.Repo,
-               IsPrivate: pr.Issue.Repo.IsPrivate,
-       }); err != nil {
-               log.Error("NotifyWatchers [%d]: %v", pr.ID, err)
-       }
-}
-
-func (*actionNotifier) NotifyAutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    activities_model.ActionAutoMergePullRequest,
-               Content:   fmt.Sprintf("%d|%s", pr.Issue.Index, pr.Issue.Title),
-               RepoID:    pr.Issue.Repo.ID,
-               Repo:      pr.Issue.Repo,
-               IsPrivate: pr.Issue.Repo.IsPrivate,
-       }); err != nil {
-               log.Error("NotifyWatchers [%d]: %v", pr.ID, err)
-       }
-}
-
-func (*actionNotifier) NotifyPullRevieweDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
-       reviewerName := review.Reviewer.Name
-       if len(review.OriginalAuthor) > 0 {
-               reviewerName = review.OriginalAuthor
-       }
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    activities_model.ActionPullReviewDismissed,
-               Content:   fmt.Sprintf("%d|%s|%s", review.Issue.Index, reviewerName, comment.Content),
-               RepoID:    review.Issue.Repo.ID,
-               Repo:      review.Issue.Repo,
-               IsPrivate: review.Issue.Repo.IsPrivate,
-               CommentID: comment.ID,
-               Comment:   comment,
-       }); err != nil {
-               log.Error("NotifyWatchers [%d]: %v", review.Issue.ID, err)
-       }
-}
-
-func (a *actionNotifier) NotifyPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
-       data, err := json.Marshal(commits)
-       if err != nil {
-               log.Error("Marshal: %v", err)
-               return
-       }
-
-       opType := activities_model.ActionCommitRepo
-
-       // Check it's tag push or branch.
-       if opts.RefFullName.IsTag() {
-               opType = activities_model.ActionPushTag
-               if opts.IsDelRef() {
-                       opType = activities_model.ActionDeleteTag
-               }
-       } else if opts.IsDelRef() {
-               opType = activities_model.ActionDeleteBranch
-       }
-
-       if err = activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: pusher.ID,
-               ActUser:   pusher,
-               OpType:    opType,
-               Content:   string(data),
-               RepoID:    repo.ID,
-               Repo:      repo,
-               RefName:   opts.RefFullName.String(),
-               IsPrivate: repo.IsPrivate,
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifyCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
-       opType := activities_model.ActionCommitRepo
-       if refFullName.IsTag() {
-               // has sent same action in `NotifyPushCommits`, so skip it.
-               return
-       }
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    opType,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-               RefName:   refFullName.String(),
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifyDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
-       opType := activities_model.ActionDeleteBranch
-       if refFullName.IsTag() {
-               // has sent same action in `NotifyPushCommits`, so skip it.
-               return
-       }
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: doer.ID,
-               ActUser:   doer,
-               OpType:    opType,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-               RefName:   refFullName.String(),
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifySyncPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
-       data, err := json.Marshal(commits)
-       if err != nil {
-               log.Error("json.Marshal: %v", err)
-               return
-       }
-
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: repo.OwnerID,
-               ActUser:   repo.MustOwner(ctx),
-               OpType:    activities_model.ActionMirrorSyncPush,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-               RefName:   opts.RefFullName.String(),
-               Content:   string(data),
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifySyncCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: repo.OwnerID,
-               ActUser:   repo.MustOwner(ctx),
-               OpType:    activities_model.ActionMirrorSyncCreate,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-               RefName:   refFullName.String(),
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifySyncDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: repo.OwnerID,
-               ActUser:   repo.MustOwner(ctx),
-               OpType:    activities_model.ActionMirrorSyncDelete,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-               RefName:   refFullName.String(),
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
-
-func (a *actionNotifier) NotifyNewRelease(ctx context.Context, rel *repo_model.Release) {
-       if err := rel.LoadAttributes(ctx); err != nil {
-               log.Error("LoadAttributes: %v", err)
-               return
-       }
-       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
-               ActUserID: rel.PublisherID,
-               ActUser:   rel.Publisher,
-               OpType:    activities_model.ActionPublishRelease,
-               RepoID:    rel.RepoID,
-               Repo:      rel.Repo,
-               IsPrivate: rel.Repo.IsPrivate,
-               Content:   rel.Title,
-               RefName:   rel.TagName, // FIXME: use a full ref name?
-       }); err != nil {
-               log.Error("NotifyWatchers: %v", err)
-       }
-}
diff --git a/modules/notification/action/action_test.go b/modules/notification/action/action_test.go
deleted file mode 100644 (file)
index 05ce70f..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package action
-
-import (
-       "path/filepath"
-       "strings"
-       "testing"
-
-       activities_model "code.gitea.io/gitea/models/activities"
-       "code.gitea.io/gitea/models/db"
-       repo_model "code.gitea.io/gitea/models/repo"
-       "code.gitea.io/gitea/models/unittest"
-       user_model "code.gitea.io/gitea/models/user"
-
-       "github.com/stretchr/testify/assert"
-)
-
-func TestMain(m *testing.M) {
-       unittest.MainTest(m, &unittest.TestOptions{
-               GiteaRootPath: filepath.Join("..", "..", ".."),
-       })
-}
-
-func TestRenameRepoAction(t *testing.T) {
-       assert.NoError(t, unittest.PrepareTestDatabase())
-
-       user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
-       repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID})
-       repo.Owner = user
-
-       oldRepoName := repo.Name
-       const newRepoName = "newRepoName"
-       repo.Name = newRepoName
-       repo.LowerName = strings.ToLower(newRepoName)
-
-       actionBean := &activities_model.Action{
-               OpType:    activities_model.ActionRenameRepo,
-               ActUserID: user.ID,
-               ActUser:   user,
-               RepoID:    repo.ID,
-               Repo:      repo,
-               IsPrivate: repo.IsPrivate,
-               Content:   oldRepoName,
-       }
-       unittest.AssertNotExistsBean(t, actionBean)
-
-       NewNotifier().NotifyRenameRepository(db.DefaultContext, user, repo, oldRepoName)
-
-       unittest.AssertExistsAndLoadBean(t, actionBean)
-       unittest.CheckConsistencyFor(t, &activities_model.Action{})
-}
index c265388a180f9437d60046f6b4382cc120bd75f6..3c29dd9256ae199c3f1445674706c6c094b7b198 100644 (file)
@@ -12,7 +12,6 @@ import (
        user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/log"
-       "code.gitea.io/gitea/modules/notification/action"
        "code.gitea.io/gitea/modules/notification/base"
        "code.gitea.io/gitea/modules/repository"
 )
@@ -25,11 +24,6 @@ func RegisterNotifier(notifier base.Notifier) {
        notifiers = append(notifiers, notifier)
 }
 
-// NewContext registers notification handlers
-func NewContext() {
-       RegisterNotifier(action.NewNotifier())
-}
-
 // NotifyNewWikiPage notifies creating new wiki pages to notifiers
 func NotifyNewWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) {
        for _, notifier := range notifiers {
index f311b4f95d3cefb6cf18df8c7f40fa9d0a647e40..6369a39754b079f4be2fe97d76ed3491d6fd5091 100644 (file)
@@ -18,7 +18,6 @@ import (
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/markup"
        "code.gitea.io/gitea/modules/markup/external"
-       "code.gitea.io/gitea/modules/notification"
        "code.gitea.io/gitea/modules/setting"
        "code.gitea.io/gitea/modules/ssh"
        "code.gitea.io/gitea/modules/storage"
@@ -38,6 +37,7 @@ import (
        "code.gitea.io/gitea/services/auth/source/oauth2"
        "code.gitea.io/gitea/services/automerge"
        "code.gitea.io/gitea/services/cron"
+       feed_service "code.gitea.io/gitea/services/feed"
        indexer_service "code.gitea.io/gitea/services/indexer"
        "code.gitea.io/gitea/services/mailer"
        mailer_incoming "code.gitea.io/gitea/services/mailer/incoming"
@@ -119,7 +119,7 @@ func InitWebInstalled(ctx context.Context) {
 
        mailer.NewContext(ctx)
        mustInit(cache.NewContext)
-       notification.NewContext()
+       mustInit(feed_service.Init)
        mustInit(uinotification.Init)
        mustInit(archiver.Init)
 
diff --git a/services/feed/action.go b/services/feed/action.go
new file mode 100644 (file)
index 0000000..3516e79
--- /dev/null
@@ -0,0 +1,459 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package feed
+
+import (
+       "context"
+       "fmt"
+       "path"
+       "strings"
+
+       activities_model "code.gitea.io/gitea/models/activities"
+       issues_model "code.gitea.io/gitea/models/issues"
+       repo_model "code.gitea.io/gitea/models/repo"
+       user_model "code.gitea.io/gitea/models/user"
+       "code.gitea.io/gitea/modules/git"
+       "code.gitea.io/gitea/modules/json"
+       "code.gitea.io/gitea/modules/log"
+       "code.gitea.io/gitea/modules/notification"
+       "code.gitea.io/gitea/modules/notification/base"
+       "code.gitea.io/gitea/modules/repository"
+       "code.gitea.io/gitea/modules/util"
+)
+
+type actionNotifier struct {
+       base.NullNotifier
+}
+
+var _ base.Notifier = &actionNotifier{}
+
+func Init() error {
+       notification.RegisterNotifier(NewNotifier())
+
+       return nil
+}
+
+// NewNotifier create a new actionNotifier notifier
+func NewNotifier() base.Notifier {
+       return &actionNotifier{}
+}
+
+func (a *actionNotifier) NotifyNewIssue(ctx context.Context, issue *issues_model.Issue, mentions []*user_model.User) {
+       if err := issue.LoadPoster(ctx); err != nil {
+               log.Error("issue.LoadPoster: %v", err)
+               return
+       }
+       if err := issue.LoadRepo(ctx); err != nil {
+               log.Error("issue.LoadRepo: %v", err)
+               return
+       }
+       repo := issue.Repo
+
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: issue.Poster.ID,
+               ActUser:   issue.Poster,
+               OpType:    activities_model.ActionCreateIssue,
+               Content:   fmt.Sprintf("%d|%s", issue.Index, issue.Title),
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+// NotifyIssueChangeStatus notifies close or reopen issue to notifiers
+func (a *actionNotifier) NotifyIssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, closeOrReopen bool) {
+       // Compose comment action, could be plain comment, close or reopen issue/pull request.
+       // This object will be used to notify watchers in the end of function.
+       act := &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               Content:   fmt.Sprintf("%d|%s", issue.Index, ""),
+               RepoID:    issue.Repo.ID,
+               Repo:      issue.Repo,
+               Comment:   actionComment,
+               CommentID: actionComment.ID,
+               IsPrivate: issue.Repo.IsPrivate,
+       }
+       // Check comment type.
+       if closeOrReopen {
+               act.OpType = activities_model.ActionCloseIssue
+               if issue.IsPull {
+                       act.OpType = activities_model.ActionClosePullRequest
+               }
+       } else {
+               act.OpType = activities_model.ActionReopenIssue
+               if issue.IsPull {
+                       act.OpType = activities_model.ActionReopenPullRequest
+               }
+       }
+
+       // Notify watchers for whatever action comes in, ignore if no action type.
+       if err := activities_model.NotifyWatchers(ctx, act); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+// NotifyCreateIssueComment notifies comment on an issue to notifiers
+func (a *actionNotifier) NotifyCreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository,
+       issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
+) {
+       act := &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               RepoID:    issue.Repo.ID,
+               Repo:      issue.Repo,
+               Comment:   comment,
+               CommentID: comment.ID,
+               IsPrivate: issue.Repo.IsPrivate,
+       }
+
+       truncatedContent, truncatedRight := util.SplitStringAtByteN(comment.Content, 200)
+       if truncatedRight != "" {
+               // in case the content is in a Latin family language, we remove the last broken word.
+               lastSpaceIdx := strings.LastIndex(truncatedContent, " ")
+               if lastSpaceIdx != -1 && (len(truncatedContent)-lastSpaceIdx < 15) {
+                       truncatedContent = truncatedContent[:lastSpaceIdx] + "…"
+               }
+       }
+       act.Content = fmt.Sprintf("%d|%s", issue.Index, truncatedContent)
+
+       if issue.IsPull {
+               act.OpType = activities_model.ActionCommentPull
+       } else {
+               act.OpType = activities_model.ActionCommentIssue
+       }
+
+       // Notify watchers for whatever action comes in, ignore if no action type.
+       if err := activities_model.NotifyWatchers(ctx, act); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifyNewPullRequest(ctx context.Context, pull *issues_model.PullRequest, mentions []*user_model.User) {
+       if err := pull.LoadIssue(ctx); err != nil {
+               log.Error("pull.LoadIssue: %v", err)
+               return
+       }
+       if err := pull.Issue.LoadRepo(ctx); err != nil {
+               log.Error("pull.Issue.LoadRepo: %v", err)
+               return
+       }
+       if err := pull.Issue.LoadPoster(ctx); err != nil {
+               log.Error("pull.Issue.LoadPoster: %v", err)
+               return
+       }
+
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: pull.Issue.Poster.ID,
+               ActUser:   pull.Issue.Poster,
+               OpType:    activities_model.ActionCreatePullRequest,
+               Content:   fmt.Sprintf("%d|%s", pull.Issue.Index, pull.Issue.Title),
+               RepoID:    pull.Issue.Repo.ID,
+               Repo:      pull.Issue.Repo,
+               IsPrivate: pull.Issue.Repo.IsPrivate,
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifyRenameRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldRepoName string) {
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    activities_model.ActionRenameRepo,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+               Content:   oldRepoName,
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifyTransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldOwnerName string) {
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    activities_model.ActionTransferRepo,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+               Content:   path.Join(oldOwnerName, repo.Name),
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifyCreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    activities_model.ActionCreateRepo,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+       }); err != nil {
+               log.Error("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
+       }
+}
+
+func (a *actionNotifier) NotifyForkRepository(ctx context.Context, doer *user_model.User, oldRepo, repo *repo_model.Repository) {
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    activities_model.ActionCreateRepo,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+       }); err != nil {
+               log.Error("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
+       }
+}
+
+func (a *actionNotifier) NotifyPullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
+       if err := review.LoadReviewer(ctx); err != nil {
+               log.Error("LoadReviewer '%d/%d': %v", review.ID, review.ReviewerID, err)
+               return
+       }
+       if err := review.LoadCodeComments(ctx); err != nil {
+               log.Error("LoadCodeComments '%d/%d': %v", review.Reviewer.ID, review.ID, err)
+               return
+       }
+
+       actions := make([]*activities_model.Action, 0, 10)
+       for _, lines := range review.CodeComments {
+               for _, comments := range lines {
+                       for _, comm := range comments {
+                               actions = append(actions, &activities_model.Action{
+                                       ActUserID: review.Reviewer.ID,
+                                       ActUser:   review.Reviewer,
+                                       Content:   fmt.Sprintf("%d|%s", review.Issue.Index, strings.Split(comm.Content, "\n")[0]),
+                                       OpType:    activities_model.ActionCommentPull,
+                                       RepoID:    review.Issue.RepoID,
+                                       Repo:      review.Issue.Repo,
+                                       IsPrivate: review.Issue.Repo.IsPrivate,
+                                       Comment:   comm,
+                                       CommentID: comm.ID,
+                               })
+                       }
+               }
+       }
+
+       if review.Type != issues_model.ReviewTypeComment || strings.TrimSpace(comment.Content) != "" {
+               action := &activities_model.Action{
+                       ActUserID: review.Reviewer.ID,
+                       ActUser:   review.Reviewer,
+                       Content:   fmt.Sprintf("%d|%s", review.Issue.Index, strings.Split(comment.Content, "\n")[0]),
+                       RepoID:    review.Issue.RepoID,
+                       Repo:      review.Issue.Repo,
+                       IsPrivate: review.Issue.Repo.IsPrivate,
+                       Comment:   comment,
+                       CommentID: comment.ID,
+               }
+
+               switch review.Type {
+               case issues_model.ReviewTypeApprove:
+                       action.OpType = activities_model.ActionApprovePullRequest
+               case issues_model.ReviewTypeReject:
+                       action.OpType = activities_model.ActionRejectPullRequest
+               default:
+                       action.OpType = activities_model.ActionCommentPull
+               }
+
+               actions = append(actions, action)
+       }
+
+       if err := activities_model.NotifyWatchersActions(actions); err != nil {
+               log.Error("notify watchers '%d/%d': %v", review.Reviewer.ID, review.Issue.RepoID, err)
+       }
+}
+
+func (*actionNotifier) NotifyMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    activities_model.ActionMergePullRequest,
+               Content:   fmt.Sprintf("%d|%s", pr.Issue.Index, pr.Issue.Title),
+               RepoID:    pr.Issue.Repo.ID,
+               Repo:      pr.Issue.Repo,
+               IsPrivate: pr.Issue.Repo.IsPrivate,
+       }); err != nil {
+               log.Error("NotifyWatchers [%d]: %v", pr.ID, err)
+       }
+}
+
+func (*actionNotifier) NotifyAutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    activities_model.ActionAutoMergePullRequest,
+               Content:   fmt.Sprintf("%d|%s", pr.Issue.Index, pr.Issue.Title),
+               RepoID:    pr.Issue.Repo.ID,
+               Repo:      pr.Issue.Repo,
+               IsPrivate: pr.Issue.Repo.IsPrivate,
+       }); err != nil {
+               log.Error("NotifyWatchers [%d]: %v", pr.ID, err)
+       }
+}
+
+func (*actionNotifier) NotifyPullRevieweDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
+       reviewerName := review.Reviewer.Name
+       if len(review.OriginalAuthor) > 0 {
+               reviewerName = review.OriginalAuthor
+       }
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    activities_model.ActionPullReviewDismissed,
+               Content:   fmt.Sprintf("%d|%s|%s", review.Issue.Index, reviewerName, comment.Content),
+               RepoID:    review.Issue.Repo.ID,
+               Repo:      review.Issue.Repo,
+               IsPrivate: review.Issue.Repo.IsPrivate,
+               CommentID: comment.ID,
+               Comment:   comment,
+       }); err != nil {
+               log.Error("NotifyWatchers [%d]: %v", review.Issue.ID, err)
+       }
+}
+
+func (a *actionNotifier) NotifyPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
+       data, err := json.Marshal(commits)
+       if err != nil {
+               log.Error("Marshal: %v", err)
+               return
+       }
+
+       opType := activities_model.ActionCommitRepo
+
+       // Check it's tag push or branch.
+       if opts.RefFullName.IsTag() {
+               opType = activities_model.ActionPushTag
+               if opts.IsDelRef() {
+                       opType = activities_model.ActionDeleteTag
+               }
+       } else if opts.IsDelRef() {
+               opType = activities_model.ActionDeleteBranch
+       }
+
+       if err = activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: pusher.ID,
+               ActUser:   pusher,
+               OpType:    opType,
+               Content:   string(data),
+               RepoID:    repo.ID,
+               Repo:      repo,
+               RefName:   opts.RefFullName.String(),
+               IsPrivate: repo.IsPrivate,
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifyCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
+       opType := activities_model.ActionCommitRepo
+       if refFullName.IsTag() {
+               // has sent same action in `NotifyPushCommits`, so skip it.
+               return
+       }
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    opType,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+               RefName:   refFullName.String(),
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifyDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
+       opType := activities_model.ActionDeleteBranch
+       if refFullName.IsTag() {
+               // has sent same action in `NotifyPushCommits`, so skip it.
+               return
+       }
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: doer.ID,
+               ActUser:   doer,
+               OpType:    opType,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+               RefName:   refFullName.String(),
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifySyncPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
+       data, err := json.Marshal(commits)
+       if err != nil {
+               log.Error("json.Marshal: %v", err)
+               return
+       }
+
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: repo.OwnerID,
+               ActUser:   repo.MustOwner(ctx),
+               OpType:    activities_model.ActionMirrorSyncPush,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+               RefName:   opts.RefFullName.String(),
+               Content:   string(data),
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifySyncCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: repo.OwnerID,
+               ActUser:   repo.MustOwner(ctx),
+               OpType:    activities_model.ActionMirrorSyncCreate,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+               RefName:   refFullName.String(),
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifySyncDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: repo.OwnerID,
+               ActUser:   repo.MustOwner(ctx),
+               OpType:    activities_model.ActionMirrorSyncDelete,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+               RefName:   refFullName.String(),
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
+
+func (a *actionNotifier) NotifyNewRelease(ctx context.Context, rel *repo_model.Release) {
+       if err := rel.LoadAttributes(ctx); err != nil {
+               log.Error("LoadAttributes: %v", err)
+               return
+       }
+       if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
+               ActUserID: rel.PublisherID,
+               ActUser:   rel.Publisher,
+               OpType:    activities_model.ActionPublishRelease,
+               RepoID:    rel.RepoID,
+               Repo:      rel.Repo,
+               IsPrivate: rel.Repo.IsPrivate,
+               Content:   rel.Title,
+               RefName:   rel.TagName, // FIXME: use a full ref name?
+       }); err != nil {
+               log.Error("NotifyWatchers: %v", err)
+       }
+}
diff --git a/services/feed/action_test.go b/services/feed/action_test.go
new file mode 100644 (file)
index 0000000..1037ba7
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package feed
+
+import (
+       "path/filepath"
+       "strings"
+       "testing"
+
+       activities_model "code.gitea.io/gitea/models/activities"
+       "code.gitea.io/gitea/models/db"
+       repo_model "code.gitea.io/gitea/models/repo"
+       "code.gitea.io/gitea/models/unittest"
+       user_model "code.gitea.io/gitea/models/user"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestMain(m *testing.M) {
+       unittest.MainTest(m, &unittest.TestOptions{
+               GiteaRootPath: filepath.Join("..", ".."),
+       })
+}
+
+func TestRenameRepoAction(t *testing.T) {
+       assert.NoError(t, unittest.PrepareTestDatabase())
+
+       user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+       repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID})
+       repo.Owner = user
+
+       oldRepoName := repo.Name
+       const newRepoName = "newRepoName"
+       repo.Name = newRepoName
+       repo.LowerName = strings.ToLower(newRepoName)
+
+       actionBean := &activities_model.Action{
+               OpType:    activities_model.ActionRenameRepo,
+               ActUserID: user.ID,
+               ActUser:   user,
+               RepoID:    repo.ID,
+               Repo:      repo,
+               IsPrivate: repo.IsPrivate,
+               Content:   oldRepoName,
+       }
+       unittest.AssertNotExistsBean(t, actionBean)
+
+       NewNotifier().NotifyRenameRepository(db.DefaultContext, user, repo, oldRepoName)
+
+       unittest.AssertExistsAndLoadBean(t, actionBean)
+       unittest.CheckConsistencyFor(t, &activities_model.Action{})
+}
index 1299e66be27810e33909fdd1771eb0701e11509a..53ba3c2441d4d813a03edd2c97bb450728f01e20 100644 (file)
@@ -15,8 +15,8 @@ import (
        "code.gitea.io/gitea/models/unittest"
        user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/notification"
-       "code.gitea.io/gitea/modules/notification/action"
        "code.gitea.io/gitea/modules/util"
+       "code.gitea.io/gitea/services/feed"
 
        "github.com/stretchr/testify/assert"
 )
@@ -25,7 +25,7 @@ var notifySync sync.Once
 
 func registerNotifier() {
        notifySync.Do(func() {
-               notification.RegisterNotifier(action.NewNotifier())
+               notification.RegisterNotifier(feed.NewNotifier())
        })
 }