Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[refactor] mailer service #15072

Merged
merged 31 commits into from
Apr 2, 2021
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e5e1b35
Unexport SendUserMail
6543 Mar 20, 2021
db5059e
Instead of "[]*models.User" or "[]string" lists infent "[]*MailRecipi…
6543 Mar 20, 2021
bb8d322
adopt
6543 Mar 20, 2021
c93b9f8
code format
6543 Mar 20, 2021
02ceed7
TODOs for "i18n"
6543 Mar 20, 2021
22d978c
clean
6543 Mar 20, 2021
f9fec7b
no fallback for lang -> just use english
6543 Mar 21, 2021
227f93b
lint
6543 Mar 21, 2021
6cfe732
exec testComposeIssueCommentMessage per lang and use only emails
6543 Mar 21, 2021
a7e6441
Merge branch 'master' into refactor_mailer
6543 Mar 21, 2021
d4040c0
rm MailRecipient
6543 Mar 21, 2021
eef62bb
Dont reload from users from db if you alredy have in ram
6543 Mar 21, 2021
3ad979b
nits
6543 Mar 21, 2021
388c32c
minimize diff
6543 Mar 21, 2021
e8f9be5
localize subjects
6543 Mar 21, 2021
66799a2
linter ...
6543 Mar 21, 2021
f2c3808
Tr extend
6543 Mar 21, 2021
16b64b2
start tmpl edit ...
6543 Mar 21, 2021
3ca8651
Merge branch 'master' into refactor_mailer
6543 Mar 29, 2021
0a0ae80
Merge branch 'master' into refactor_mailer
6543 Mar 31, 2021
150b836
Merge branch 'master' into refactor_mailer
6543 Apr 1, 2021
0d4f499
Apply suggestions from code review
6543 Apr 1, 2021
22f5610
Merge branch 'master' into refactor_mailer
6543 Apr 1, 2021
9b264ab
Merge branch 'master' into refactor_mailer
6543 Apr 1, 2021
880d68c
use translation.Locale
6543 Apr 1, 2021
291d0e9
improve mailIssueCommentBatch
zeripath Apr 1, 2021
cd80b9c
Merge branch 'master' into refactor_mailer
6543 Apr 1, 2021
9b527c8
add i18n to datas
zeripath Apr 1, 2021
62bb80a
Merge branch 'master' into refactor_mailer
6543 Apr 2, 2021
1bb1ea1
a comment
zeripath Apr 2, 2021
bd3d19a
Merge branch 'master' into refactor_mailer
lunny Apr 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,6 @@ func (u *User) GenerateEmailActivateCode(email string) string {
return code
}

// GenerateActivateCode generates an activate code based on user information.
func (u *User) GenerateActivateCode() string {
return u.GenerateEmailActivateCode(u.Email)
}

// GetFollowers returns range of user's followers.
func (u *User) GetFollowers(listOptions ListOptions) ([]*User, error) {
sess := x.
Expand Down
6 changes: 3 additions & 3 deletions modules/notification/mail/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ func (m *mailNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *model
// mail only sent to added assignees and not self-assignee
if !removed && doer.ID != assignee.ID && assignee.EmailNotifications() == models.EmailNotificationsEnabled {
ct := fmt.Sprintf("Assigned #%d.", issue.Index)
mailer.SendIssueAssignedMail(issue, doer, ct, comment, []string{assignee.Email})
mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*models.User{assignee})
}
}

func (m *mailNotifier) NotifyPullReviewRequest(doer *models.User, issue *models.Issue, reviewer *models.User, isRequest bool, comment *models.Comment) {
if isRequest && doer.ID != reviewer.ID && reviewer.EmailNotifications() == models.EmailNotificationsEnabled {
ct := fmt.Sprintf("Requested to review %s.", issue.HTMLURL())
mailer.SendIssueAssignedMail(issue, doer, ct, comment, []string{reviewer.Email})
mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*models.User{reviewer})
}
}

Expand Down Expand Up @@ -153,7 +153,7 @@ func (m *mailNotifier) NotifyPullRequestPushCommits(doer *models.User, pr *model
}

func (m *mailNotifier) NotifyPullRevieweDismiss(doer *models.User, review *models.Review, comment *models.Comment) {
if err := mailer.MailParticipantsComment(comment, models.ActionPullReviewDismissed, review.Issue, []*models.User{}); err != nil {
if err := mailer.MailParticipantsComment(comment, models.ActionPullReviewDismissed, review.Issue, nil); err != nil {
log.Error("MailParticipantsComment: %v", err)
}
}
Expand Down
8 changes: 8 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,14 @@ reset_password = Recover your account
register_success = Registration successful
register_notify = Welcome to Gitea

release.new.subject = %s in %s released

repo.transfer.subject_to = %s would like to transfer "%s" to %s
repo.transfer.subject_to_you = %s would like to transfer "%s" to you
repo.transfer.to_you = you

repo.collaborator.added.subject = %s added you to %s

[modal]
yes = Yes
no = No
Expand Down
2 changes: 1 addition & 1 deletion routers/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func NewUserPost(ctx *context.Context) {

// Send email notification.
if form.SendNotify {
mailer.SendRegisterNotifyMail(ctx.Locale, u)
mailer.SendRegisterNotifyMail(u)
}

ctx.Flash.Success(ctx.Tr("admin.users.new_success", u.Name))
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func CreateUser(ctx *context.APIContext) {

// Send email notification.
if form.SendNotify {
mailer.SendRegisterNotifyMail(ctx.Locale, u)
mailer.SendRegisterNotifyMail(u)
}
ctx.JSON(http.StatusCreated, convert.ToUser(u, ctx.User))
}
Expand Down
8 changes: 4 additions & 4 deletions routers/user/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,7 @@ func LinkAccountPostRegister(ctx *context.Context) {

// Send confirmation email
if setting.Service.RegisterEmailConfirm && u.ID > 1 {
mailer.SendActivateAccountMail(ctx.Locale, u)
mailer.SendActivateAccountMail(ctx.Locale.Language(), u)

ctx.Data["IsSendRegisterMail"] = true
ctx.Data["Email"] = u.Email
Expand Down Expand Up @@ -1213,7 +1213,7 @@ func SignUpPost(ctx *context.Context) {

// Send confirmation email, no need for social account.
if setting.Service.RegisterEmailConfirm && u.ID > 1 {
mailer.SendActivateAccountMail(ctx.Locale, u)
mailer.SendActivateAccountMail(ctx.Locale.Language(), u)

ctx.Data["IsSendRegisterMail"] = true
ctx.Data["Email"] = u.Email
Expand Down Expand Up @@ -1247,7 +1247,7 @@ func Activate(ctx *context.Context) {
ctx.Data["ResendLimited"] = true
} else {
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
mailer.SendActivateAccountMail(ctx.Locale, ctx.User)
mailer.SendActivateAccountMail(ctx.Locale.Language(), ctx.User)

if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
Expand Down Expand Up @@ -1397,7 +1397,7 @@ func ForgotPasswdPost(ctx *context.Context) {
return
}

mailer.SendResetPasswordMail(ctx.Locale, u)
mailer.SendResetPasswordMail(u.Language, u)

if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
Expand Down
2 changes: 1 addition & 1 deletion routers/user/auth_openid.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ func RegisterOpenIDPost(ctx *context.Context) {

// Send confirmation email, no need for social account.
if setting.Service.RegisterEmailConfirm && u.ID > 1 {
mailer.SendActivateAccountMail(ctx.Locale, u)
mailer.SendActivateAccountMail(ctx.Locale.Language(), u)
6543 marked this conversation as resolved.
Show resolved Hide resolved

ctx.Data["IsSendRegisterMail"] = true
ctx.Data["Email"] = u.Email
Expand Down
6 changes: 3 additions & 3 deletions routers/user/setting/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func EmailPost(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
}
mailer.SendActivateAccountMail(ctx.Locale, ctx.User)
mailer.SendActivateAccountMail(ctx.Locale.Language(), ctx.User)
6543 marked this conversation as resolved.
Show resolved Hide resolved
address = ctx.User.Email
} else {
id := ctx.QueryInt64("id")
Expand All @@ -132,7 +132,7 @@ func EmailPost(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
}
mailer.SendActivateEmailMail(ctx.Locale, ctx.User, email)
mailer.SendActivateEmailMail(ctx.User, email)
6543 marked this conversation as resolved.
Show resolved Hide resolved
address = email.Email
}

Expand Down Expand Up @@ -194,7 +194,7 @@ func EmailPost(ctx *context.Context) {

// Send confirmation email
if setting.Service.RegisterEmailConfirm {
mailer.SendActivateEmailMail(ctx.Locale, ctx.User, email)
mailer.SendActivateEmailMail(ctx.User, email)
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
Expand Down
68 changes: 38 additions & 30 deletions services/mailer/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"

"gopkg.in/gomail.v2"
)
Expand Down Expand Up @@ -57,8 +58,8 @@ func SendTestMail(email string) error {
return gomail.Send(Sender, NewMessage([]string{email}, "Gitea Test Email!", "Gitea Test Email!").ToMessage())
}

// SendUserMail sends a mail to the user
func SendUserMail(language string, u *models.User, tpl base.TplName, code, subject, info string) {
// sendUserMail sends a mail to the user
func sendUserMail(language string, u *models.User, tpl base.TplName, code, subject, info string) {
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, language),
Expand All @@ -68,6 +69,7 @@ func SendUserMail(language string, u *models.User, tpl base.TplName, code, subje

var content bytes.Buffer

// TODO: i18n
zeripath marked this conversation as resolved.
Show resolved Hide resolved
if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil {
zeripath marked this conversation as resolved.
Show resolved Hide resolved
log.Error("Template: %v", err)
return
Expand All @@ -79,24 +81,21 @@ func SendUserMail(language string, u *models.User, tpl base.TplName, code, subje
SendAsync(msg)
}

// Locale represents an interface to translation
type Locale interface {
Language() string
Tr(string, ...interface{}) string
}

// SendActivateAccountMail sends an activation mail to the user (new user registration)
func SendActivateAccountMail(locale Locale, u *models.User) {
SendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateActivateCode(), locale.Tr("mail.activate_account"), "activate account")
func SendActivateAccountMail(lang string, u *models.User) {
locale := translation.NewLocale(lang)
6543 marked this conversation as resolved.
Show resolved Hide resolved
sendUserMail(lang, u, mailAuthActivate, u.GenerateEmailActivateCode(u.Email), locale.Tr("mail.activate_account"), "activate account")
}

// SendResetPasswordMail sends a password reset mail to the user
func SendResetPasswordMail(locale Locale, u *models.User) {
SendUserMail(locale.Language(), u, mailAuthResetPassword, u.GenerateActivateCode(), locale.Tr("mail.reset_password"), "recover account")
func SendResetPasswordMail(lang string, u *models.User) {
locale := translation.NewLocale(lang)
sendUserMail(lang, u, mailAuthResetPassword, u.GenerateEmailActivateCode(u.Email), locale.Tr("mail.reset_password"), "recover account")
}

// SendActivateEmailMail sends confirmation email to confirm new email address
func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAddress) {
func SendActivateEmailMail(u *models.User, email *models.EmailAddress) {
locale := translation.NewLocale(u.Language)
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale.Language()),
Expand All @@ -106,6 +105,7 @@ func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAdd

var content bytes.Buffer

// TODO: i18n
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
log.Error("Template: %v", err)
return
Expand All @@ -118,11 +118,8 @@ func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAdd
}

// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
func SendRegisterNotifyMail(locale Locale, u *models.User) {
if setting.MailService == nil {
log.Warn("SendRegisterNotifyMail is being invoked but mail service hasn't been initialized")
return
}
func SendRegisterNotifyMail(u *models.User) {
locale := translation.NewLocale(u.Language)

data := map[string]interface{}{
"DisplayName": u.DisplayName(),
Expand All @@ -131,6 +128,7 @@ func SendRegisterNotifyMail(locale Locale, u *models.User) {

var content bytes.Buffer

// TODO: i18n
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil {
log.Error("Template: %v", err)
return
Expand All @@ -144,9 +142,10 @@ func SendRegisterNotifyMail(locale Locale, u *models.User) {

// SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) {
locale := translation.NewLocale(u.Language)
repoName := repo.FullName()
subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName)

subject := locale.Tr("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName)
data := map[string]interface{}{
"Subject": subject,
"RepoName": repoName,
Expand All @@ -155,6 +154,7 @@ func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) {

var content bytes.Buffer

// TODO: i18n
if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
log.Error("Template: %v", err)
return
Expand All @@ -166,7 +166,7 @@ func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) {
SendAsync(msg)
}

func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMention bool, info string) []*Message {
func composeIssueCommentMessages(ctx *mailCommentContext, lang string, tos []string, fromMention bool, info string) []*Message {

var (
subject string
Expand All @@ -192,7 +192,6 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent

// This is the body of the new issue or comment, not the mail body
body := string(markup.RenderByType(markdown.MarkupName, []byte(ctx.Content), ctx.Issue.Repo.HTMLURL(), ctx.Issue.Repo.ComposeMetas()))

actType, actName, tplName := actionToTemplate(ctx.Issue, ctx.ActionType, commentType, reviewType)

if actName != "new" {
Expand Down Expand Up @@ -227,10 +226,11 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent
}

var mailSubject bytes.Buffer
// TODO: i18n
if err := subjectTemplates.ExecuteTemplate(&mailSubject, string(tplName), mailMeta); err == nil {
subject = sanitizeSubject(mailSubject.String())
} else {
log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/subject", err)
log.Error("ExecuteTemplate [%s]: %v", tplName+"/subject", err)
}

if subject == "" {
Expand All @@ -243,6 +243,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent

var mailBody bytes.Buffer

// TODO: i18n
if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplName), mailMeta); err != nil {
log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/body", err)
}
Expand Down Expand Up @@ -276,14 +277,21 @@ func sanitizeSubject(subject string) string {
}

// SendIssueAssignedMail composes and sends issue assigned email
func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
SendAsyncs(composeIssueCommentMessages(&mailCommentContext{
Issue: issue,
Doer: doer,
ActionType: models.ActionType(0),
Content: content,
Comment: comment,
}, tos, false, "issue assigned"))
func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, recipients []*models.User) {
langMap := make(map[string][]string)
for _, user := range recipients {
langMap[user.Language] = append(langMap[user.Language], user.Email)
}

for lang, tos := range langMap {
SendAsyncs(composeIssueCommentMessages(&mailCommentContext{
Issue: issue,
Doer: doer,
ActionType: models.ActionType(0),
Content: content,
Comment: comment,
}, lang, tos, false, "issue assigned"))
}
}

// actionToTemplate returns the type and name of the action facing the user
Expand Down
21 changes: 4 additions & 17 deletions services/mailer/mail_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,23 @@ import (
"code.gitea.io/gitea/modules/log"
)

// MailParticipantsComment sends new comment emails to repository watchers
// and mentioned people.
// MailParticipantsComment sends new comment emails to repository watchers and mentioned people.
func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) error {
return mailParticipantsComment(c, opType, issue, mentions)
}

func mailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) (err error) {
mentionedIDs := make([]int64, len(mentions))
for i, u := range mentions {
mentionedIDs[i] = u.ID
}
if err = mailIssueCommentToParticipants(
if err := mailIssueCommentToParticipants(
&mailCommentContext{
Issue: issue,
Doer: c.Poster,
ActionType: opType,
Content: c.Content,
Comment: c,
}, mentionedIDs); err != nil {
}, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
return nil
}

// MailMentionsComment sends email to users mentioned in a code comment
func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*models.User) (err error) {
mentionedIDs := make([]int64, len(mentions))
for i, u := range mentions {
mentionedIDs[i] = u.ID
}
visited := make(map[int64]bool, len(mentions)+1)
visited[c.Poster.ID] = true
if err = mailIssueCommentBatch(
Expand All @@ -48,7 +35,7 @@ func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*
ActionType: models.ActionCommentPull,
Content: c.Content,
Comment: c,
}, mentionedIDs, visited, true); err != nil {
}, mentions, visited, true); err != nil {
log.Error("mailIssueCommentBatch: %v", err)
}
return nil
Expand Down
Loading