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"
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
2021-01-02 20:04:02 +03:00
// =========== Mentions ===========
if err = mailIssueCommentBatch ( ctx , mentions , visited , true ) ; err != nil {
return fmt . Errorf ( "mailIssueCommentBatch() mentions: %v" , err )
}
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
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
}
}
2020-09-09 22:08:55 +03:00
recipients , err := models . GetMaileableUsersByIDs ( unique , fromMention )
2019-11-18 11:08:20 +03:00
if err != nil {
return err
}
2020-12-03 23:25:49 +03:00
// Make sure all recipients can still see the issue
idx := 0
for _ , r := range recipients {
if ctx . Issue . Repo . CheckUnitUser ( r , models . UnitTypeIssues ) {
recipients [ idx ] = r
idx ++
}
}
recipients = recipients [ : idx ]
2019-11-18 11:08:20 +03:00
// 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.
2021-01-02 20:04:02 +03:00
func MailParticipants ( issue * models . Issue , doer * models . User , opType models . ActionType , mentions [ ] * models . User ) error {
return mailParticipants ( issue , doer , opType , mentions )
2017-08-30 07:31:33 +03:00
}
2021-01-02 20:04:02 +03:00
func mailParticipants ( issue * models . Issue , doer * models . User , opType models . ActionType , mentions [ ] * models . User ) ( err error ) {
mentionedIDs := make ( [ ] int64 , len ( mentions ) )
for i , u := range mentions {
mentionedIDs [ i ] = u . ID
2019-11-18 11:08:20 +03:00
}
if err = mailIssueCommentToParticipants (
& mailCommentContext {
Issue : issue ,
Doer : doer ,
ActionType : opType ,
Content : issue . Content ,
Comment : nil ,
2021-01-02 20:04:02 +03:00
} , mentionedIDs ) ; 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
}