Skip to content

Commit

Permalink
[refactor] mailer service (#15072)
Browse files Browse the repository at this point in the history
* Unexport SendUserMail

* Instead of "[]*models.User" or "[]string" lists infent "[]*MailRecipient" for mailer

* adopt

* code format

* TODOs for "i18n"

* clean

* no fallback for lang -> just use english

* lint

* exec testComposeIssueCommentMessage per lang and use only emails

* rm MailRecipient

* Dont reload from users from db if you alredy have in ram

* nits

* minimize diff

Signed-off-by: 6543 <6543@obermui.de>

* localize subjects

* linter ...

* Tr extend

* start tmpl edit ...

* Apply suggestions from code review

* use translation.Locale

* improve mailIssueCommentBatch

Signed-off-by: Andrew Thornton <art27@cantab.net>

* add i18n to datas

Signed-off-by: Andrew Thornton <art27@cantab.net>

* a comment

Co-authored-by: Andrew Thornton <art27@cantab.net>
  • Loading branch information
6543 and zeripath authored Apr 2, 2021
1 parent cc2d540 commit 80d6c6d
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 143 deletions.
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
2 changes: 1 addition & 1 deletion routers/user/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1397,7 +1397,7 @@ func ForgotPasswdPost(ctx *context.Context) {
return
}

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

if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
Expand Down
4 changes: 2 additions & 2 deletions routers/user/setting/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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
79 changes: 49 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,17 +58,21 @@ 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) {
locale := translation.NewLocale(language)
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, language),
"ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, language),
"Code": code,
"i18n": locale,
"Language": locale.Language(),
}

var content bytes.Buffer

// TODO: i18n templates?
if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil {
log.Error("Template: %v", err)
return
Expand All @@ -79,33 +84,32 @@ 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(locale translation.Locale, u *models.User) {
sendUserMail(locale.Language(), 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(u *models.User) {
locale := translation.NewLocale(u.Language)
sendUserMail(u.Language, 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()),
"Code": u.GenerateEmailActivateCode(email.Email),
"Email": email.Email,
"i18n": locale,
"Language": locale.Language(),
}

var content bytes.Buffer

// TODO: i18n templates?
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
log.Error("Template: %v", err)
return
Expand All @@ -118,19 +122,19 @@ 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(),
"Username": u.Name,
"i18n": locale,
"Language": locale.Language(),
}

var content bytes.Buffer

// TODO: i18n templates?
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil {
log.Error("Template: %v", err)
return
Expand All @@ -144,17 +148,21 @@ 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,
"Link": repo.HTMLURL(),
"i18n": locale,
"Language": locale.Language(),
}

var content bytes.Buffer

// TODO: i18n templates?
if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
log.Error("Template: %v", err)
return
Expand All @@ -166,7 +174,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 +200,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 All @@ -208,6 +215,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent
}
}
}
locale := translation.NewLocale(lang)

mailMeta := map[string]interface{}{
"FallbackSubject": fallback,
Expand All @@ -224,13 +232,16 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent
"ActionType": actType,
"ActionName": actName,
"ReviewComments": reviewComments,
"i18n": locale,
"Language": locale.Language(),
}

var mailSubject bytes.Buffer
// TODO: i18n templates?
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 +254,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent

var mailBody bytes.Buffer

// TODO: i18n templates?
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 +288,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

0 comments on commit 80d6c6d

Please sign in to comment.