Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

mail_issue.go 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2019 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. package mailer
  5. import (
  6. "fmt"
  7. "code.gitea.io/gitea/models"
  8. "code.gitea.io/gitea/modules/log"
  9. "code.gitea.io/gitea/modules/references"
  10. )
  11. func fallbackMailSubject(issue *models.Issue) string {
  12. return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.FullName(), issue.Title, issue.Index)
  13. }
  14. type mailCommentContext struct {
  15. Issue *models.Issue
  16. Doer *models.User
  17. ActionType models.ActionType
  18. Content string
  19. Comment *models.Comment
  20. }
  21. // mailIssueCommentToParticipants can be used for both new issue creation and comment.
  22. // This function sends two list of emails:
  23. // 1. Repository watchers and users who are participated in comments.
  24. // 2. Users who are not in 1. but get mentioned in current issue/comment.
  25. func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []int64) error {
  26. // Required by the mail composer; make sure to load these before calling the async function
  27. if err := ctx.Issue.LoadRepo(); err != nil {
  28. return fmt.Errorf("LoadRepo(): %v", err)
  29. }
  30. if err := ctx.Issue.LoadPoster(); err != nil {
  31. return fmt.Errorf("LoadPoster(): %v", err)
  32. }
  33. if err := ctx.Issue.LoadPullRequest(); err != nil {
  34. return fmt.Errorf("LoadPullRequest(): %v", err)
  35. }
  36. // Enough room to avoid reallocations
  37. unfiltered := make([]int64, 1, 64)
  38. // =========== Original poster ===========
  39. unfiltered[0] = ctx.Issue.PosterID
  40. // =========== Assignees ===========
  41. ids, err := models.GetAssigneeIDsByIssue(ctx.Issue.ID)
  42. if err != nil {
  43. return fmt.Errorf("GetAssigneeIDsByIssue(%d): %v", ctx.Issue.ID, err)
  44. }
  45. unfiltered = append(unfiltered, ids...)
  46. // =========== Participants (i.e. commenters, reviewers) ===========
  47. ids, err = models.GetParticipantsIDsByIssueID(ctx.Issue.ID)
  48. if err != nil {
  49. return fmt.Errorf("GetParticipantsIDsByIssueID(%d): %v", ctx.Issue.ID, err)
  50. }
  51. unfiltered = append(unfiltered, ids...)
  52. // =========== Issue watchers ===========
  53. ids, err = models.GetIssueWatchersIDs(ctx.Issue.ID)
  54. if err != nil {
  55. return fmt.Errorf("GetIssueWatchersIDs(%d): %v", ctx.Issue.ID, err)
  56. }
  57. unfiltered = append(unfiltered, ids...)
  58. // =========== Repo watchers ===========
  59. // Make repo watchers last, since it's likely the list with the most users
  60. ids, err = models.GetRepoWatchersIDs(ctx.Issue.RepoID)
  61. if err != nil {
  62. return fmt.Errorf("GetRepoWatchersIDs(%d): %v", ctx.Issue.RepoID, err)
  63. }
  64. unfiltered = append(ids, unfiltered...)
  65. visited := make(map[int64]bool, len(unfiltered)+len(mentions)+1)
  66. // Avoid mailing the doer
  67. visited[ctx.Doer.ID] = true
  68. if err = mailIssueCommentBatch(ctx, unfiltered, visited, false); err != nil {
  69. return fmt.Errorf("mailIssueCommentBatch(): %v", err)
  70. }
  71. // =========== Mentions ===========
  72. if err = mailIssueCommentBatch(ctx, mentions, visited, true); err != nil {
  73. return fmt.Errorf("mailIssueCommentBatch() mentions: %v", err)
  74. }
  75. return nil
  76. }
  77. func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int64]bool, fromMention bool) error {
  78. const batchSize = 100
  79. for i := 0; i < len(ids); i += batchSize {
  80. var last int
  81. if i+batchSize < len(ids) {
  82. last = i + batchSize
  83. } else {
  84. last = len(ids)
  85. }
  86. unique := make([]int64, 0, last-i)
  87. for j := i; j < last; j++ {
  88. id := ids[j]
  89. if _, ok := visited[id]; !ok {
  90. unique = append(unique, id)
  91. visited[id] = true
  92. }
  93. }
  94. recipients, err := models.GetMaileableUsersByIDs(unique)
  95. if err != nil {
  96. return err
  97. }
  98. // TODO: Check issue visibility for each user
  99. // TODO: Separate recipients by language for i18n mail templates
  100. tos := make([]string, len(recipients))
  101. for i := range recipients {
  102. tos[i] = recipients[i].Email
  103. }
  104. SendAsyncs(composeIssueCommentMessages(ctx, tos, fromMention, "issue comments"))
  105. }
  106. return nil
  107. }
  108. // MailParticipants sends new issue thread created emails to repository watchers
  109. // and mentioned people.
  110. func MailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType) error {
  111. return mailParticipants(models.DefaultDBContext(), issue, doer, opType)
  112. }
  113. func mailParticipants(ctx models.DBContext, issue *models.Issue, doer *models.User, opType models.ActionType) (err error) {
  114. rawMentions := references.FindAllMentionsMarkdown(issue.Content)
  115. userMentions, err := issue.ResolveMentionsByVisibility(ctx, doer, rawMentions)
  116. if err != nil {
  117. return fmt.Errorf("ResolveMentionsByVisibility [%d]: %v", issue.ID, err)
  118. }
  119. if err = models.UpdateIssueMentions(ctx, issue.ID, userMentions); err != nil {
  120. return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
  121. }
  122. mentions := make([]int64, len(userMentions))
  123. for i, u := range userMentions {
  124. mentions[i] = u.ID
  125. }
  126. if err = mailIssueCommentToParticipants(
  127. &mailCommentContext{
  128. Issue: issue,
  129. Doer: doer,
  130. ActionType: opType,
  131. Content: issue.Content,
  132. Comment: nil,
  133. }, mentions); err != nil {
  134. log.Error("mailIssueCommentToParticipants: %v", err)
  135. }
  136. return nil
  137. }