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 }