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 5 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
12 changes: 6 additions & 6 deletions modules/notification/mail/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.
act = 0
}

if err := mailer.MailParticipantsComment(comment, act, issue, mentions); err != nil {
if err := mailer.MailParticipantsComment(comment, act, issue, mailer.UsersToMailRecipients(mentions)); err != nil {
log.Error("MailParticipantsComment: %v", err)
}
}
Expand Down Expand Up @@ -89,13 +89,13 @@ func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models
} else if comment.Type == models.CommentTypeComment {
act = models.ActionCommentPull
}
if err := mailer.MailParticipantsComment(comment, act, pr.Issue, mentions); err != nil {
if err := mailer.MailParticipantsComment(comment, act, pr.Issue, mailer.UsersToMailRecipients(mentions)); err != nil {
log.Error("MailParticipantsComment: %v", err)
}
}

func (m *mailNotifier) NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User) {
if err := mailer.MailMentionsComment(pr, comment, mentions); err != nil {
if err := mailer.MailMentionsComment(pr, comment, mailer.UsersToMailRecipients(mentions)); err != nil {
log.Error("MailMentionsComment: %v", err)
}
}
Expand All @@ -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, []*mailer.MailRecipient{mailer.UserToMailRecipient(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, []*mailer.MailRecipient{mailer.UserToMailRecipient(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
4 changes: 4 additions & 0 deletions modules/translation/translation.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ type locale struct {

// NewLocale return a locale
func NewLocale(lang string) Locale {
if len(lang) != 5 {
// TODO: default language
6543 marked this conversation as resolved.
Show resolved Hide resolved
lang = allLangs[0].Lang
}
6543 marked this conversation as resolved.
Show resolved Hide resolved
return &locale{
Lang: lang,
}
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.IsSigned, ctx.User.IsAdmin))
}
Expand Down
2 changes: 1 addition & 1 deletion routers/repo/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ func CollaborationPost(ctx *context.Context) {
}

if setting.Service.EnableNotifyMail {
mailer.SendCollaboratorMail(u, ctx.User, ctx.Repo.Repository)
mailer.SendCollaboratorMail(mailer.UserToMailRecipient(u), ctx.User, ctx.Repo.Repository)
}

ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success"))
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
83 changes: 60 additions & 23 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 All @@ -46,6 +47,30 @@ var (
subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)
)

type MailRecipient struct {
6543 marked this conversation as resolved.
Show resolved Hide resolved
userID int64
Mail string
Language string
}

// Convert User into MailRecipient
func UserToMailRecipient(user *models.User) *MailRecipient {
return &MailRecipient{
userID: user.ID,
Mail: user.Email,
Language: user.Language,
}
}

// Convert list of User into list of MailRecipient
func UsersToMailRecipients(users []*models.User) []*MailRecipient {
recipients := make([]*MailRecipient, len(users))
for i := range users {
recipients[i] = UserToMailRecipient(users[i])
}
return recipients
}

// InitMailRender initializes the mail renderer
func InitMailRender(subjectTpl *texttmpl.Template, bodyTpl *template.Template) {
subjectTemplates = subjectTpl
Expand All @@ -57,8 +82,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 +93,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 @@ -86,17 +112,20 @@ type Locale interface {
}

// 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 +135,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,7 +148,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) {
func SendRegisterNotifyMail(u *models.User) {
locale := translation.NewLocale(u.Language)
if setting.MailService == nil {
log.Warn("SendRegisterNotifyMail is being invoked but mail service hasn't been initialized")
return
Expand All @@ -131,6 +162,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 @@ -143,8 +175,9 @@ func SendRegisterNotifyMail(locale Locale, u *models.User) {
}

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

data := map[string]interface{}{
Expand All @@ -155,18 +188,19 @@ 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
}

msg := NewMessage([]string{u.Email}, subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID)
msg := NewMessage([]string{recipient.Mail}, subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, add collaborator", recipient.userID)

SendAsync(msg)
}

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

var (
subject string
Expand All @@ -192,8 +226,8 @@ 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)
// TODO: i18n
actType, actName, tplName := actionToTemplate("", ctx.Issue, ctx.ActionType, commentType, reviewType)

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

var mailSubject bytes.Buffer
if err := subjectTemplates.ExecuteTemplate(&mailSubject, string(tplName), mailMeta); err == nil {
// TODO: i18n
if err := subjectTemplates.ExecuteTemplate(&mailSubject, 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,14 +278,15 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent

var mailBody bytes.Buffer

if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplName), mailMeta); err != nil {
log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/body", err)
// TODO: i18n
if err := bodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil {
log.Error("ExecuteTemplate [%s]: %v", tplName+"/body", err)
}

// Make sure to compose independent messages to avoid leaking user emails
msgs := make([]*Message, 0, len(tos))
for _, to := range tos {
msg := NewMessageFrom([]string{to}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
msgs := make([]*Message, 0, len(recipients))
for _, to := range recipients {
msg := NewMessageFrom([]string{to.Mail}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)

// Set Message-ID on first message so replies know what to reference
Expand All @@ -276,7 +312,7 @@ 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) {
func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []*MailRecipient) {
SendAsyncs(composeIssueCommentMessages(&mailCommentContext{
Issue: issue,
Doer: doer,
Expand All @@ -288,7 +324,8 @@ func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content strin

// actionToTemplate returns the type and name of the action facing the user
// (slightly different from models.ActionType) and the name of the template to use (based on availability)
func actionToTemplate(issue *models.Issue, actionType models.ActionType,
// TODO: i18n
func actionToTemplate(lang string, issue *models.Issue, actionType models.ActionType,
commentType models.CommentType, reviewType models.ReviewType) (typeName, name, template string) {
if issue.IsPull {
typeName = "pull"
Expand Down
Loading