2019-09-24 08:02:49 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2016-07-15 19:36:39 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2019-09-24 08:02:49 +03:00
package mailer
2016-07-15 19:36:39 +03:00
import (
"fmt"
2019-09-24 08:02:49 +03:00
"code.gitea.io/gitea/models"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
2019-10-14 01:29:10 +03:00
"code.gitea.io/gitea/modules/references"
2016-07-15 19:36:39 +03:00
)
2019-11-07 16:34:28 +03:00
func fallbackMailSubject ( issue * models . Issue ) string {
2019-05-28 12:41:48 +03:00
return fmt . Sprintf ( "[%s] %s (#%d)" , issue . Repo . FullName ( ) , issue . Title , issue . Index )
2016-07-15 19:36:39 +03:00
}
2019-11-18 11:08:20 +03:00
type mailCommentContext struct {
Issue * models . Issue
Doer * models . User
ActionType models . ActionType
Content string
Comment * models . Comment
}
2016-07-15 19:36:39 +03:00
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
2017-03-16 04:34:24 +03:00
// This function sends two list of emails:
// 1. Repository watchers and users who are participated in comments.
// 2. Users who are not in 1. but get mentioned in current issue/comment.
2019-11-18 11:08:20 +03:00
func mailIssueCommentToParticipants ( ctx * mailCommentContext , mentions [ ] int64 ) error {
2016-07-15 19:36:39 +03:00
2019-11-18 11:08:20 +03:00
// Required by the mail composer; make sure to load these before calling the async function
if err := ctx . Issue . LoadRepo ( ) ; err != nil {
return fmt . Errorf ( "LoadRepo(): %v" , err )
2017-03-16 04:34:24 +03:00
}
2019-11-18 11:08:20 +03:00
if err := ctx . Issue . LoadPoster ( ) ; err != nil {
return fmt . Errorf ( "LoadPoster(): %v" , err )
}
if err := ctx . Issue . LoadPullRequest ( ) ; err != nil {
return fmt . Errorf ( "LoadPullRequest(): %v" , err )
2017-03-16 04:34:24 +03:00
}
2019-11-18 11:08:20 +03:00
// Enough room to avoid reallocations
unfiltered := make ( [ ] int64 , 1 , 64 )
// =========== Original poster ===========
unfiltered [ 0 ] = ctx . Issue . PosterID
// =========== Assignees ===========
ids , err := models . GetAssigneeIDsByIssue ( ctx . Issue . ID )
2017-09-16 03:18:25 +03:00
if err != nil {
2019-11-18 11:08:20 +03:00
return fmt . Errorf ( "GetAssigneeIDsByIssue(%d): %v" , ctx . Issue . ID , err )
2016-07-15 19:36:39 +03:00
}
2019-11-18 11:08:20 +03:00
unfiltered = append ( unfiltered , ids ... )
2016-07-15 19:36:39 +03:00
2019-11-18 11:08:20 +03:00
// =========== Participants (i.e. commenters, reviewers) ===========
ids , err = models . GetParticipantsIDsByIssueID ( ctx . Issue . ID )
2018-05-09 19:29:04 +03:00
if err != nil {
2019-11-18 11:08:20 +03:00
return fmt . Errorf ( "GetParticipantsIDsByIssueID(%d): %v" , ctx . Issue . ID , err )
2018-05-09 19:29:04 +03:00
}
2019-11-18 11:08:20 +03:00
unfiltered = append ( unfiltered , ids ... )
2018-05-09 19:29:04 +03:00
2019-11-18 11:08:20 +03:00
// =========== Issue watchers ===========
2020-02-27 13:07:05 +03:00
ids , err = models . GetIssueWatchersIDs ( ctx . Issue . ID , true )
2019-11-18 11:08:20 +03:00
if err != nil {
return fmt . Errorf ( "GetIssueWatchersIDs(%d): %v" , ctx . Issue . ID , err )
2017-06-23 16:43:37 +03:00
}
2019-11-18 11:08:20 +03:00
unfiltered = append ( unfiltered , ids ... )
2017-06-23 16:43:37 +03:00
2019-11-18 11:08:20 +03:00
// =========== Repo watchers ===========
// Make repo watchers last, since it's likely the list with the most users
ids , err = models . GetRepoWatchersIDs ( ctx . Issue . RepoID )
if err != nil {
return fmt . Errorf ( "GetRepoWatchersIDs(%d): %v" , ctx . Issue . RepoID , err )
2016-07-15 19:36:39 +03:00
}
2019-11-18 11:08:20 +03:00
unfiltered = append ( ids , unfiltered ... )
2017-03-16 04:34:24 +03:00
2019-11-18 11:08:20 +03:00
visited := make ( map [ int64 ] bool , len ( unfiltered ) + len ( mentions ) + 1 )
2017-05-25 05:38:56 +03:00
2019-11-18 11:08:20 +03:00
// Avoid mailing the doer
visited [ ctx . Doer . ID ] = true
2020-02-27 13:07:05 +03:00
// Avoid mailing explicit unwatched
ids , err = models . GetIssueWatchersIDs ( ctx . Issue . ID , false )
if err != nil {
return fmt . Errorf ( "GetIssueWatchersIDs(%d): %v" , ctx . Issue . ID , err )
}
for _ , i := range ids {
visited [ i ] = true
}
2019-01-30 01:43:40 +03:00
2019-11-18 11:08:20 +03:00
if err = mailIssueCommentBatch ( ctx , unfiltered , visited , false ) ; err != nil {
return fmt . Errorf ( "mailIssueCommentBatch(): %v" , err )
2018-08-24 07:41:26 +03:00
}
2016-07-15 19:36:39 +03:00
2019-11-18 11:08:20 +03:00
// =========== Mentions ===========
if err = mailIssueCommentBatch ( ctx , mentions , visited , true ) ; err != nil {
return fmt . Errorf ( "mailIssueCommentBatch() mentions: %v" , err )
2016-07-15 19:36:39 +03:00
}
2018-08-24 07:41:26 +03:00
2019-11-18 11:08:20 +03:00
return nil
}
2018-08-24 07:41:26 +03:00
2019-11-18 11:08:20 +03:00
func mailIssueCommentBatch ( ctx * mailCommentContext , ids [ ] int64 , visited map [ int64 ] bool , fromMention bool ) error {
const batchSize = 100
for i := 0 ; i < len ( ids ) ; i += batchSize {
var last int
if i + batchSize < len ( ids ) {
last = i + batchSize
} else {
last = len ( ids )
}
unique := make ( [ ] int64 , 0 , last - i )
for j := i ; j < last ; j ++ {
id := ids [ j ]
if _ , ok := visited [ id ] ; ! ok {
unique = append ( unique , id )
visited [ id ] = true
}
}
recipients , err := models . GetMaileableUsersByIDs ( unique )
if err != nil {
return err
}
// TODO: Check issue visibility for each user
// TODO: Separate recipients by language for i18n mail templates
tos := make ( [ ] string , len ( recipients ) )
for i := range recipients {
tos [ i ] = recipients [ i ] . Email
}
SendAsyncs ( composeIssueCommentMessages ( ctx , tos , fromMention , "issue comments" ) )
2018-08-24 07:41:26 +03:00
}
2016-07-15 19:36:39 +03:00
return nil
}
// MailParticipants sends new issue thread created emails to repository watchers
// and mentioned people.
2019-09-24 08:02:49 +03:00
func MailParticipants ( issue * models . Issue , doer * models . User , opType models . ActionType ) error {
return mailParticipants ( models . DefaultDBContext ( ) , issue , doer , opType )
2017-08-30 07:31:33 +03:00
}
2019-09-24 08:02:49 +03:00
func mailParticipants ( ctx models . DBContext , issue * models . Issue , doer * models . User , opType models . ActionType ) ( err error ) {
2019-10-14 01:29:10 +03:00
rawMentions := references . FindAllMentionsMarkdown ( issue . Content )
2019-10-10 19:45:11 +03:00
userMentions , err := issue . ResolveMentionsByVisibility ( ctx , doer , rawMentions )
if err != nil {
return fmt . Errorf ( "ResolveMentionsByVisibility [%d]: %v" , issue . ID , err )
}
if err = models . UpdateIssueMentions ( ctx , issue . ID , userMentions ) ; err != nil {
2016-07-15 19:36:39 +03:00
return fmt . Errorf ( "UpdateIssueMentions [%d]: %v" , issue . ID , err )
}
2019-11-18 11:08:20 +03:00
mentions := make ( [ ] int64 , len ( userMentions ) )
2019-10-10 19:45:11 +03:00
for i , u := range userMentions {
2019-11-18 11:08:20 +03:00
mentions [ i ] = u . ID
}
if err = mailIssueCommentToParticipants (
& mailCommentContext {
Issue : issue ,
Doer : doer ,
ActionType : opType ,
Content : issue . Content ,
Comment : nil ,
} , mentions ) ; err != nil {
2019-11-07 16:34:28 +03:00
log . Error ( "mailIssueCommentToParticipants: %v" , err )
2016-07-15 19:36:39 +03:00
}
return nil
}