From e5e1b35ca8248adce3774c1894040569ff840ce4 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 20 Mar 2021 18:19:02 +0100 Subject: [PATCH 01/22] Unexport SendUserMail --- services/mailer/mail.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 7d6214c742a5c..1668ac1cd00bf 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -57,8 +57,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), @@ -87,12 +87,12 @@ 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") + sendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateActivateCode(), 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") + sendUserMail(locale.Language(), u, mailAuthResetPassword, u.GenerateActivateCode(), locale.Tr("mail.reset_password"), "recover account") } // SendActivateEmailMail sends confirmation email to confirm new email address From db5059eb60d914ae073a369076e3f6bca3fd1b5a Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 20 Mar 2021 18:23:02 +0100 Subject: [PATCH 02/22] Instead of "[]*models.User" or "[]string" lists infent "[]*MailRecipient" for mailer --- services/mailer/mail.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 1668ac1cd00bf..a187810288889 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -46,6 +46,28 @@ var ( subjectRemoveSpaces = regexp.MustCompile(`[\s]+`) ) +type MailRecipient struct { + Mail string + Language string +} + +// Convert User into MailRecipient +func UserToMailRecipient(user *models.User) *MailRecipient { + return &MailRecipient{ + Mail: user.Email, + Language: user.Language, + } +} + +// Convert list of User into list of MailRecipient +func UsersToMailRecipients(users []*models.User) []*MailRecipient { + recipients := make([]*MailRecipient, 0, 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 From bb8d32215b606599383692420321343cf5d43695 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 20 Mar 2021 20:10:19 +0100 Subject: [PATCH 03/22] adopt --- models/user.go | 5 ---- modules/notification/mail/mail.go | 12 +++++----- modules/translation/translation.go | 4 ++++ routers/admin/users.go | 2 +- routers/api/v1/admin/user.go | 2 +- routers/repo/setting.go | 2 +- routers/user/auth.go | 8 +++---- routers/user/auth_openid.go | 2 +- routers/user/setting/account.go | 6 ++--- services/mailer/mail.go | 37 ++++++++++++++++++------------ services/mailer/mail_comment.go | 17 +++++--------- services/mailer/mail_issue.go | 7 +----- services/mailer/mail_test.go | 11 ++++++--- 13 files changed, 58 insertions(+), 57 deletions(-) diff --git a/models/user.go b/models/user.go index 098f6af2b374a..aacf2957e3d4b 100644 --- a/models/user.go +++ b/models/user.go @@ -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. diff --git a/modules/notification/mail/mail.go b/modules/notification/mail/mail.go index f7192f5a52eff..24d112d808803 100644 --- a/modules/notification/mail/mail.go +++ b/modules/notification/mail/mail.go @@ -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) } } @@ -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) } } @@ -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)}) } } @@ -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) } } diff --git a/modules/translation/translation.go b/modules/translation/translation.go index b7276e53c0149..f9ab035e1f7c8 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -90,6 +90,10 @@ type locale struct { // NewLocale return a locale func NewLocale(lang string) Locale { + if len(lang) != 5 { + // TODO: default language + lang = allLangs[0].Lang + } return &locale{ Lang: lang, } diff --git a/routers/admin/users.go b/routers/admin/users.go index 2d40a883af3db..e3f569203075b 100644 --- a/routers/admin/users.go +++ b/routers/admin/users.go @@ -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)) diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 116c622048d65..fceb038f5bd8c 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -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)) } diff --git a/routers/repo/setting.go b/routers/repo/setting.go index dc14b69b3b0b0..805c90004d013 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -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")) diff --git a/routers/user/auth.go b/routers/user/auth.go index 37181c68e7a24..6853633dd7216 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -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 @@ -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 @@ -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) @@ -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) diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index 967e8aad2d436..3064a354f6149 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -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) ctx.Data["IsSendRegisterMail"] = true ctx.Data["Email"] = u.Email diff --git a/routers/user/setting/account.go b/routers/user/setting/account.go index 4900bba14ac0c..eb33c45200c18 100644 --- a/routers/user/setting/account.go +++ b/routers/user/setting/account.go @@ -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) address = ctx.User.Email } else { id := ctx.QueryInt64("id") @@ -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 } @@ -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) } diff --git a/services/mailer/mail.go b/services/mailer/mail.go index a187810288889..e63857d8ae8ff 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -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" ) @@ -47,6 +48,7 @@ var ( ) type MailRecipient struct { + userID int64 Mail string Language string } @@ -54,6 +56,7 @@ type MailRecipient struct { // Convert User into MailRecipient func UserToMailRecipient(user *models.User) *MailRecipient { return &MailRecipient{ + userID: user.ID, Mail: user.Email, Language: user.Language, } @@ -61,7 +64,7 @@ func UserToMailRecipient(user *models.User) *MailRecipient { // Convert list of User into list of MailRecipient func UsersToMailRecipients(users []*models.User) []*MailRecipient { - recipients := make([]*MailRecipient, 0, len(users)) + recipients := make([]*MailRecipient, len(users)) for i := range users { recipients[i] = UserToMailRecipient(users[i]) } @@ -108,17 +111,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) + 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()), @@ -140,7 +146,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 @@ -165,7 +172,7 @@ 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() subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName) @@ -182,13 +189,13 @@ func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) { 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 @@ -270,9 +277,9 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent } // 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 @@ -298,7 +305,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, diff --git a/services/mailer/mail_comment.go b/services/mailer/mail_comment.go index 2f166720dbd64..ffd5b938f80ee 100644 --- a/services/mailer/mail_comment.go +++ b/services/mailer/mail_comment.go @@ -9,18 +9,13 @@ import ( "code.gitea.io/gitea/modules/log" ) -// 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) { +// MailParticipantsComment sends new comment emails to repository watchers and mentioned people. +func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*MailRecipient) error { mentionedIDs := make([]int64, len(mentions)) for i, u := range mentions { - mentionedIDs[i] = u.ID + mentionedIDs[i] = u.userID } - if err = mailIssueCommentToParticipants( + if err := mailIssueCommentToParticipants( &mailCommentContext{ Issue: issue, Doer: c.Poster, @@ -34,10 +29,10 @@ func mailParticipantsComment(c *models.Comment, opType models.ActionType, issue } // MailMentionsComment sends email to users mentioned in a code comment -func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*models.User) (err error) { +func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*MailRecipient) (err error) { mentionedIDs := make([]int64, len(mentions)) for i, u := range mentions { - mentionedIDs[i] = u.ID + mentionedIDs[i] = u.userID } visited := make(map[int64]bool, len(mentions)+1) visited[c.Poster.ID] = true diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index b600060a67f5e..d247bfb6a5057 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -137,12 +137,7 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int } recipients = recipients[:idx] - // 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")) + SendAsyncs(composeIssueCommentMessages(ctx, UsersToMailRecipients(recipients), fromMention, "issue comments")) } return nil } diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index d7d02d9dee822..2454f86e14686 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -57,7 +57,7 @@ func TestComposeIssueCommentMessage(t *testing.T) { btpl := template.Must(template.New("issue/comment").Parse(bodyTpl)) InitMailRender(stpl, btpl) - tos := []string{"test@gitea.com", "test2@gitea.com"} + tos := []*MailRecipient{{Mail: "test@gitea.com"}, {Mail: "test2@gitea.com"}} msgs := composeIssueCommentMessages(&mailCommentContext{Issue: issue, Doer: doer, ActionType: models.ActionCommentIssue, Content: "test body", Comment: comment}, tos, false, "issue comment") assert.Len(t, msgs, 2) @@ -91,7 +91,7 @@ func TestComposeIssueMessage(t *testing.T) { btpl := template.Must(template.New("issue/new").Parse(bodyTpl)) InitMailRender(stpl, btpl) - tos := []string{"test@gitea.com", "test2@gitea.com"} + tos := []*MailRecipient{{Mail: "test@gitea.com"}, {Mail: "test2@gitea.com"}} msgs := composeIssueCommentMessages(&mailCommentContext{Issue: issue, Doer: doer, ActionType: models.ActionCreateIssue, Content: "test body"}, tos, false, "issue create") assert.Len(t, msgs, 2) @@ -218,7 +218,12 @@ func TestTemplateServices(t *testing.T) { } func testComposeIssueCommentMessage(t *testing.T, ctx *mailCommentContext, tos []string, fromMention bool, info string) *Message { - msgs := composeIssueCommentMessages(ctx, tos, fromMention, info) + recipients := make([]*MailRecipient, len(tos)) + assert.Len(t, tos, len(recipients)) + for i, to := range tos { + recipients[i] = &MailRecipient{Mail: to} + } + msgs := composeIssueCommentMessages(ctx, recipients, fromMention, info) assert.Len(t, msgs, 1) return msgs[0] } From c93b9f890eedd9cd49ee024dfc10641a85bdf7dd Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 20 Mar 2021 20:37:40 +0100 Subject: [PATCH 04/22] code format --- services/mailer/mail.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index e63857d8ae8ff..ca086008326ae 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -256,10 +256,10 @@ func composeIssueCommentMessages(ctx *mailCommentContext, recipients []*MailReci } var mailSubject bytes.Buffer - if err := subjectTemplates.ExecuteTemplate(&mailSubject, string(tplName), mailMeta); err == nil { + 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 == "" { @@ -272,8 +272,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, recipients []*MailReci var mailBody bytes.Buffer - if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplName), mailMeta); err != nil { - log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/body", err) + 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 From 02ceed79f4ee8714dca8d3ef4128f7c7922a3199 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 20 Mar 2021 21:06:21 +0100 Subject: [PATCH 05/22] TODOs for "i18n" --- services/mailer/mail.go | 14 +++++++++++--- services/mailer/mail_repo.go | 37 ++++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index ca086008326ae..d12f3ca6f166b 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -93,6 +93,7 @@ func sendUserMail(language string, u *models.User, tpl base.TplName, code, subje var content bytes.Buffer + // TODO: i18n if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil { log.Error("Template: %v", err) return @@ -134,6 +135,7 @@ func SendActivateEmailMail(u *models.User, email *models.EmailAddress) { var content bytes.Buffer + // TODO: i18n if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil { log.Error("Template: %v", err) return @@ -160,6 +162,7 @@ func SendRegisterNotifyMail(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 @@ -174,6 +177,7 @@ func SendRegisterNotifyMail(u *models.User) { // SendCollaboratorMail sends mail notification to new collaborator. 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{}{ @@ -184,6 +188,7 @@ func SendCollaboratorMail(recipient *MailRecipient, doer *models.User, repo *mod var content bytes.Buffer + // TODO: i18n if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil { log.Error("Template: %v", err) return @@ -221,8 +226,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, recipients []*MailReci // 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: " @@ -256,6 +261,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, recipients []*MailReci } var mailSubject bytes.Buffer + // TODO: i18n if err := subjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil { subject = sanitizeSubject(mailSubject.String()) } else { @@ -272,6 +278,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, recipients []*MailReci var mailBody bytes.Buffer + // TODO: i18n if err := bodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil { log.Error("ExecuteTemplate [%s]: %v", tplName+"/body", err) } @@ -317,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" diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index b9d24f4334d92..10c6f7fdc2785 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -13,11 +13,6 @@ import ( // SendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created func SendRepoTransferNotifyMail(doer, newOwner *models.User, repo *models.Repository) error { - var ( - emails []string - destination string - content bytes.Buffer - ) if newOwner.IsOrganization() { users, err := models.GetUsersWhoCanCreateOrgRepo(newOwner.ID) @@ -25,15 +20,36 @@ func SendRepoTransferNotifyMail(doer, newOwner *models.User, repo *models.Reposi return err } - for i := range users { - emails = append(emails, users[i].Email) + langMap := make(map[string][]string) + for _, user := range users { + langMap[user.Language] = append(langMap[user.Language], user.Email) + } + + for k, v := range langMap { + if err := sendRepoTransferNotifyMailPerLang(k, newOwner, doer, v, repo); err != nil { + return err + } } + + return nil + } + + return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []string{newOwner.Email}, repo) +} + +// sendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created for each language +func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *models.User, emails []string, repo *models.Repository) error { + var ( + content bytes.Buffer + ) + + // TODO: i18n + destination := "you" + if newOwner.IsOrganization() { destination = newOwner.DisplayName() - } else { - emails = []string{newOwner.Email} - destination = "you" } + // TODO: i18n subject := fmt.Sprintf("%s would like to transfer \"%s\" to %s", doer.DisplayName(), repo.FullName(), destination) data := map[string]interface{}{ "Doer": doer, @@ -45,6 +61,7 @@ func SendRepoTransferNotifyMail(doer, newOwner *models.User, repo *models.Reposi "Destination": destination, } + // TODO: i18n if err := bodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil { return err } From 22d978cd0e6bb4b2261ec28cf7e1534324db8c6f Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 20 Mar 2021 21:14:05 +0100 Subject: [PATCH 06/22] clean --- services/mailer/mail.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index d12f3ca6f166b..2686ec9589e3f 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -105,12 +105,6 @@ 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(lang string, u *models.User) { locale := translation.NewLocale(lang) From f9fec7b60cfc340749601ecec640bb868d92917c Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 15:34:20 +0100 Subject: [PATCH 07/22] no fallback for lang -> just use english --- modules/translation/translation.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/translation/translation.go b/modules/translation/translation.go index f9ab035e1f7c8..b7276e53c0149 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -90,10 +90,6 @@ type locale struct { // NewLocale return a locale func NewLocale(lang string) Locale { - if len(lang) != 5 { - // TODO: default language - lang = allLangs[0].Lang - } return &locale{ Lang: lang, } From 227f93b01a1b90b753ec3d76e413670638d86299 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 10:06:05 +0100 Subject: [PATCH 08/22] lint --- services/mailer/mail.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 2686ec9589e3f..f70d712eec1ab 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -47,13 +47,14 @@ var ( subjectRemoveSpaces = regexp.MustCompile(`[\s]+`) ) +// MailRecipient represent a mail recipient type MailRecipient struct { userID int64 Mail string Language string } -// Convert User into MailRecipient +// UserToMailRecipient convert User into MailRecipient func UserToMailRecipient(user *models.User) *MailRecipient { return &MailRecipient{ userID: user.ID, @@ -62,7 +63,7 @@ func UserToMailRecipient(user *models.User) *MailRecipient { } } -// Convert list of User into list of MailRecipient +// UsersToMailRecipients convert list of User into list of MailRecipient func UsersToMailRecipients(users []*models.User) []*MailRecipient { recipients := make([]*MailRecipient, len(users)) for i := range users { From 6cfe732fe0ad9d5b41a6547de6c71e5132882ca9 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 15:57:59 +0100 Subject: [PATCH 09/22] exec testComposeIssueCommentMessage per lang and use only emails --- services/mailer/mail.go | 31 +++++++++++++++++++------------ services/mailer/mail_issue.go | 9 ++++++++- services/mailer/mail_repo.go | 4 ++-- services/mailer/mail_test.go | 15 +++++---------- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index f70d712eec1ab..9f1e44d603e19 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -195,7 +195,7 @@ func SendCollaboratorMail(recipient *MailRecipient, doer *models.User, repo *mod SendAsync(msg) } -func composeIssueCommentMessages(ctx *mailCommentContext, recipients []*MailRecipient, fromMention bool, info string) []*Message { +func composeIssueCommentMessages(ctx *mailCommentContext, lang string, tos []string, fromMention bool, info string) []*Message { var ( subject string @@ -279,9 +279,9 @@ func composeIssueCommentMessages(ctx *mailCommentContext, recipients []*MailReci } // Make sure to compose independent messages to avoid leaking user emails - msgs := make([]*Message, 0, len(recipients)) - for _, to := range recipients { - msg := NewMessageFrom([]string{to.Mail}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String()) + msgs := make([]*Message, 0, len(tos)) + for _, to := range tos { + msg := NewMessageFrom([]string{to}, 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 @@ -307,14 +307,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 []*MailRecipient) { - 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 []*MailRecipient) { + langMap := make(map[string][]string) + for _, user := range recipients { + langMap[user.Language] = append(langMap[user.Language], user.Mail) + } + + 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 diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index d247bfb6a5057..fd0e01cfba485 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -137,7 +137,14 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int } recipients = recipients[:idx] - SendAsyncs(composeIssueCommentMessages(ctx, UsersToMailRecipients(recipients), fromMention, "issue comments")) + 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(ctx, lang, tos, fromMention, "issue comments")) + } } return nil } diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 10c6f7fdc2785..2df6593e38e44 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -25,8 +25,8 @@ func SendRepoTransferNotifyMail(doer, newOwner *models.User, repo *models.Reposi langMap[user.Language] = append(langMap[user.Language], user.Email) } - for k, v := range langMap { - if err := sendRepoTransferNotifyMailPerLang(k, newOwner, doer, v, repo); err != nil { + for lang, tos := range langMap { + if err := sendRepoTransferNotifyMailPerLang(lang, newOwner, doer, tos, repo); err != nil { return err } } diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 2454f86e14686..9eef084408dff 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -57,9 +57,9 @@ func TestComposeIssueCommentMessage(t *testing.T) { btpl := template.Must(template.New("issue/comment").Parse(bodyTpl)) InitMailRender(stpl, btpl) - tos := []*MailRecipient{{Mail: "test@gitea.com"}, {Mail: "test2@gitea.com"}} + tos := []string{"test@gitea.com", "test2@gitea.com"} msgs := composeIssueCommentMessages(&mailCommentContext{Issue: issue, Doer: doer, ActionType: models.ActionCommentIssue, - Content: "test body", Comment: comment}, tos, false, "issue comment") + Content: "test body", Comment: comment}, "en-US", tos, false, "issue comment") assert.Len(t, msgs, 2) gomailMsg := msgs[0].ToMessage() mailto := gomailMsg.GetHeader("To") @@ -91,9 +91,9 @@ func TestComposeIssueMessage(t *testing.T) { btpl := template.Must(template.New("issue/new").Parse(bodyTpl)) InitMailRender(stpl, btpl) - tos := []*MailRecipient{{Mail: "test@gitea.com"}, {Mail: "test2@gitea.com"}} + tos := []string{"test@gitea.com", "test2@gitea.com"} msgs := composeIssueCommentMessages(&mailCommentContext{Issue: issue, Doer: doer, ActionType: models.ActionCreateIssue, - Content: "test body"}, tos, false, "issue create") + Content: "test body"}, "en-US", tos, false, "issue create") assert.Len(t, msgs, 2) gomailMsg := msgs[0].ToMessage() @@ -218,12 +218,7 @@ func TestTemplateServices(t *testing.T) { } func testComposeIssueCommentMessage(t *testing.T, ctx *mailCommentContext, tos []string, fromMention bool, info string) *Message { - recipients := make([]*MailRecipient, len(tos)) - assert.Len(t, tos, len(recipients)) - for i, to := range tos { - recipients[i] = &MailRecipient{Mail: to} - } - msgs := composeIssueCommentMessages(ctx, recipients, fromMention, info) + msgs := composeIssueCommentMessages(ctx, "en-US", tos, fromMention, info) assert.Len(t, msgs, 1) return msgs[0] } From d4040c09b62d28a103cae216bc3dc64157cac8a3 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 20:28:40 +0100 Subject: [PATCH 10/22] rm MailRecipient --- modules/notification/mail/mail.go | 10 ++++----- routers/repo/setting.go | 2 +- services/mailer/mail.go | 35 +++++-------------------------- services/mailer/mail_comment.go | 8 +++---- 4 files changed, 15 insertions(+), 40 deletions(-) diff --git a/modules/notification/mail/mail.go b/modules/notification/mail/mail.go index 24d112d808803..9c000da0f6c71 100644 --- a/modules/notification/mail/mail.go +++ b/modules/notification/mail/mail.go @@ -41,7 +41,7 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models. act = 0 } - if err := mailer.MailParticipantsComment(comment, act, issue, mailer.UsersToMailRecipients(mentions)); err != nil { + if err := mailer.MailParticipantsComment(comment, act, issue, mentions); err != nil { log.Error("MailParticipantsComment: %v", err) } } @@ -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, mailer.UsersToMailRecipients(mentions)); err != nil { + if err := mailer.MailParticipantsComment(comment, act, pr.Issue, 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, mailer.UsersToMailRecipients(mentions)); err != nil { + if err := mailer.MailMentionsComment(pr, comment, mentions); err != nil { log.Error("MailMentionsComment: %v", err) } } @@ -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, []*mailer.MailRecipient{mailer.UserToMailRecipient(assignee)}) + 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, []*mailer.MailRecipient{mailer.UserToMailRecipient(reviewer)}) + mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*models.User{reviewer}) } } diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 805c90004d013..dc14b69b3b0b0 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -684,7 +684,7 @@ func CollaborationPost(ctx *context.Context) { } if setting.Service.EnableNotifyMail { - mailer.SendCollaboratorMail(mailer.UserToMailRecipient(u), ctx.User, ctx.Repo.Repository) + mailer.SendCollaboratorMail(u, ctx.User, ctx.Repo.Repository) } ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 9f1e44d603e19..ef4e6d654fdfb 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -47,31 +47,6 @@ var ( subjectRemoveSpaces = regexp.MustCompile(`[\s]+`) ) -// MailRecipient represent a mail recipient -type MailRecipient struct { - userID int64 - Mail string - Language string -} - -// UserToMailRecipient convert User into MailRecipient -func UserToMailRecipient(user *models.User) *MailRecipient { - return &MailRecipient{ - userID: user.ID, - Mail: user.Email, - Language: user.Language, - } -} - -// UsersToMailRecipients 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 @@ -170,7 +145,7 @@ func SendRegisterNotifyMail(u *models.User) { } // SendCollaboratorMail sends mail notification to new collaborator. -func SendCollaboratorMail(recipient *MailRecipient, doer *models.User, repo *models.Repository) { +func SendCollaboratorMail(recipient, doer *models.User, repo *models.Repository) { repoName := repo.FullName() // TODO: i18n subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName) @@ -189,8 +164,8 @@ func SendCollaboratorMail(recipient *MailRecipient, doer *models.User, repo *mod return } - msg := NewMessage([]string{recipient.Mail}, subject, content.String()) - msg.Info = fmt.Sprintf("UID: %d, add collaborator", recipient.userID) + msg := NewMessage([]string{recipient.Email}, subject, content.String()) + msg.Info = fmt.Sprintf("UID: %d, add collaborator", recipient.ID) SendAsync(msg) } @@ -307,10 +282,10 @@ 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, recipients []*MailRecipient) { +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.Mail) + langMap[user.Language] = append(langMap[user.Language], user.Email) } for lang, tos := range langMap { diff --git a/services/mailer/mail_comment.go b/services/mailer/mail_comment.go index ffd5b938f80ee..9d63e5676705f 100644 --- a/services/mailer/mail_comment.go +++ b/services/mailer/mail_comment.go @@ -10,10 +10,10 @@ import ( ) // MailParticipantsComment sends new comment emails to repository watchers and mentioned people. -func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*MailRecipient) error { +func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) error { mentionedIDs := make([]int64, len(mentions)) for i, u := range mentions { - mentionedIDs[i] = u.userID + mentionedIDs[i] = u.ID } if err := mailIssueCommentToParticipants( &mailCommentContext{ @@ -29,10 +29,10 @@ func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue } // MailMentionsComment sends email to users mentioned in a code comment -func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*MailRecipient) (err error) { +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.userID + mentionedIDs[i] = u.ID } visited := make(map[int64]bool, len(mentions)+1) visited[c.Poster.ID] = true From eef62bb0b84927b5597e356df2ff1c104170109e Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 20:40:27 +0100 Subject: [PATCH 11/22] Dont reload from users from db if you alredy have in ram --- services/mailer/mail_comment.go | 12 +----- services/mailer/mail_issue.go | 68 ++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/services/mailer/mail_comment.go b/services/mailer/mail_comment.go index 9d63e5676705f..f73c9fb6372f9 100644 --- a/services/mailer/mail_comment.go +++ b/services/mailer/mail_comment.go @@ -11,10 +11,6 @@ import ( // 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 { - mentionedIDs := make([]int64, len(mentions)) - for i, u := range mentions { - mentionedIDs[i] = u.ID - } if err := mailIssueCommentToParticipants( &mailCommentContext{ Issue: issue, @@ -22,7 +18,7 @@ func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue ActionType: opType, Content: c.Content, Comment: c, - }, mentionedIDs); err != nil { + }, mentions); err != nil { log.Error("mailIssueCommentToParticipants: %v", err) } return nil @@ -30,10 +26,6 @@ func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue // 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( @@ -43,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 diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index fd0e01cfba485..519afa3b442f1 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -23,11 +23,15 @@ type mailCommentContext struct { Comment *models.Comment } +const ( + MailBatchSize = 100 +) + // mailIssueCommentToParticipants can be used for both new issue creation and comment. // 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. -func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []int64) error { +func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*models.User) error { // Required by the mail composer; make sure to load these before calling the async function if err := ctx.Issue.LoadRepo(); err != nil { @@ -94,34 +98,44 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []int64) e visited[i] = true } - if err = mailIssueCommentBatch(ctx, unfiltered, visited, false); err != nil { + unfilteredUsers, err := models.GetMaileableUsersByIDs(unfiltered, false) + if err != nil { + return err + } + if err = mailIssueCommentBatch(ctx, unfilteredUsers, visited, false); err != nil { return fmt.Errorf("mailIssueCommentBatch(): %v", err) } return nil } -func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int64]bool, fromMention bool) error { - const batchSize = 100 - for i := 0; i < len(ids); i += batchSize { +func mailIssueCommentBatch(ctx *mailCommentContext, users []*models.User, visited map[int64]bool, fromMention bool) error { + receivers := make([]*models.User, 0, len(users)) + for i := range users { + // if user has all mails enabled ... + if users[i].EmailNotificationsPreference == models.EmailNotificationsEnabled || + // else if user only get mail on mention and this is one ... + fromMention && users[i].EmailNotificationsPreference == models.EmailNotificationsOnMention { + // ... add to mail relievers + receivers = append(receivers, users[i]) + } + } + + for i := 0; i < len(receivers); i += MailBatchSize { var last int - if i+batchSize < len(ids) { - last = i + batchSize + if i+MailBatchSize < len(receivers) { + last = i + MailBatchSize } else { - last = len(ids) + last = len(receivers) } - unique := make([]int64, 0, last-i) + + uniqueRecipients := make([]*models.User, 0, last-i) for j := i; j < last; j++ { - id := ids[j] - if _, ok := visited[id]; !ok { - unique = append(unique, id) - visited[id] = true + if _, ok := visited[receivers[j].ID]; !ok { + uniqueRecipients = append(uniqueRecipients, receivers[j]) + visited[receivers[j].ID] = true } } - recipients, err := models.GetMaileableUsersByIDs(unique, fromMention) - if err != nil { - return err - } checkUnit := models.UnitTypeIssues if ctx.Issue.IsPull { @@ -129,16 +143,16 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int } // Make sure all recipients can still see the issue idx := 0 - for _, r := range recipients { + for _, r := range uniqueRecipients { if ctx.Issue.Repo.CheckUnitUser(r, checkUnit) { - recipients[idx] = r + uniqueRecipients[idx] = r idx++ } } - recipients = recipients[:idx] + uniqueRecipients = uniqueRecipients[:idx] langMap := make(map[string][]string) - for _, user := range recipients { + for _, user := range uniqueRecipients { langMap[user.Language] = append(langMap[user.Language], user.Email) } @@ -152,22 +166,14 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int // MailParticipants sends new issue thread created emails to repository watchers // and mentioned people. func MailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType, mentions []*models.User) error { - return mailParticipants(issue, doer, opType, mentions) -} - -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 - } - if err = mailIssueCommentToParticipants( + if err := mailIssueCommentToParticipants( &mailCommentContext{ Issue: issue, Doer: doer, ActionType: opType, Content: issue.Content, Comment: nil, - }, mentionedIDs); err != nil { + }, mentions); err != nil { log.Error("mailIssueCommentToParticipants: %v", err) } return nil From 3ad979b036accef1328c44bf14f69fcdd05ee94b Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 21:45:09 +0100 Subject: [PATCH 12/22] nits --- services/mailer/mail.go | 4 ---- services/mailer/mail_release.go | 1 + services/mailer/mailer.go | 9 ++++++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index ef4e6d654fdfb..cb78b1827d080 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -120,10 +120,6 @@ func SendActivateEmailMail(u *models.User, email *models.EmailAddress) { // SendRegisterNotifyMail triggers a notify e-mail by admin created a account. 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 - } data := map[string]interface{}{ "DisplayName": u.DisplayName(), diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index f278c853aebd2..1c3d95f421b80 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -41,6 +41,7 @@ func MailNewRelease(rel *models.Release) { } rel.RenderedNote = markdown.RenderString(rel.Note, rel.Repo.Link(), rel.Repo.ComposeMetas()) + // TODO: i18n subject := fmt.Sprintf("%s in %s released", rel.TagName, rel.Repo.FullName()) mailMeta := map[string]interface{}{ diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index 2e7beffa151ab..6b86734bf845c 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -337,13 +337,16 @@ func NewContext() { // SendAsync send mail asynchronously func SendAsync(msg *Message) { - go func() { - _ = mailQueue.Push(msg) - }() + SendAsyncs([]*Message{msg}) } // SendAsyncs send mails asynchronously func SendAsyncs(msgs []*Message) { + if setting.MailService == nil { + log.Error("Mailer: SendAsyncs is being invoked but mail service hasn't been initialized") + return + } + go func() { for _, msg := range msgs { _ = mailQueue.Push(msg) From 388c32c33704b317c3a26dbdd54011d3f5d933c3 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 21:50:53 +0100 Subject: [PATCH 13/22] minimize diff Signed-off-by: 6543 <6543@obermui.de> --- services/mailer/mail.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index cb78b1827d080..f76eb7b8a3e67 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -141,7 +141,7 @@ func SendRegisterNotifyMail(u *models.User) { } // SendCollaboratorMail sends mail notification to new collaborator. -func SendCollaboratorMail(recipient, doer *models.User, repo *models.Repository) { +func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) { repoName := repo.FullName() // TODO: i18n subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName) @@ -160,8 +160,8 @@ func SendCollaboratorMail(recipient, doer *models.User, repo *models.Repository) return } - msg := NewMessage([]string{recipient.Email}, subject, content.String()) - msg.Info = fmt.Sprintf("UID: %d, add collaborator", recipient.ID) + msg := NewMessage([]string{u.Email}, subject, content.String()) + msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID) SendAsync(msg) } @@ -228,7 +228,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, tos []str var mailSubject bytes.Buffer // TODO: i18n - if err := subjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil { + if err := subjectTemplates.ExecuteTemplate(&mailSubject, string(tplName), mailMeta); err == nil { subject = sanitizeSubject(mailSubject.String()) } else { log.Error("ExecuteTemplate [%s]: %v", tplName+"/subject", err) @@ -245,8 +245,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, tos []str var mailBody bytes.Buffer // TODO: i18n - if err := bodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil { - log.Error("ExecuteTemplate [%s]: %v", tplName+"/body", err) + if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplName), mailMeta); err != nil { + log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/body", err) } // Make sure to compose independent messages to avoid leaking user emails From e8f9be5f127355f7237b1006e935b9fa65517862 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 22:27:09 +0100 Subject: [PATCH 14/22] localize subjects --- options/locale/locale_en-US.ini | 7 +++++++ services/mailer/mail.go | 12 ++++++------ services/mailer/mail_release.go | 26 +++++++++++++++++--------- services/mailer/mail_repo.go | 8 ++++---- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index e679e1e874979..736b6dfc93f44 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -320,6 +320,13 @@ reset_password = Recover your account register_success = Registration successful register_notify = Welcome to Gitea +release.new.subject = %s in %s released + +repo.transfer.subject = %s would like to transfer "%s" to %s +repo.transfer.to_you = you + +repo.collaborator.added.subject = %s added you to %s + [modal] yes = Yes no = No diff --git a/services/mailer/mail.go b/services/mailer/mail.go index f76eb7b8a3e67..d7e522b1d96eb 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -142,10 +142,10 @@ func SendRegisterNotifyMail(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() - // TODO: i18n - 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, @@ -169,6 +169,8 @@ func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) { func composeIssueCommentMessages(ctx *mailCommentContext, lang string, tos []string, fromMention bool, info string) []*Message { var ( + locale = translation.NewLocale(lang) + subject string link string prefix string @@ -192,8 +194,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, tos []str // 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())) - // TODO: i18n - actType, actName, tplName := actionToTemplate("", ctx.Issue, ctx.ActionType, commentType, reviewType) + actType, actName, tplName := actionToTemplate(ctx.Issue, ctx.ActionType, commentType, reviewType) if actName != "new" { prefix = "Re: " @@ -297,8 +298,7 @@ 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) -// TODO: i18n -func actionToTemplate(lang string, issue *models.Issue, actionType models.ActionType, +func actionToTemplate(issue *models.Issue, actionType models.ActionType, commentType models.CommentType, reviewType models.ReviewType) (typeName, name, template string) { if issue.IsPull { typeName = "pull" diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index 1c3d95f421b80..b60b3dd715d42 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -6,13 +6,13 @@ package mailer import ( "bytes" - "fmt" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" ) const ( @@ -33,17 +33,24 @@ func MailNewRelease(rel *models.Release) { return } - tos := make([]string, 0, len(recipients)) - for _, to := range recipients { - if to.ID != rel.PublisherID { - tos = append(tos, to.Email) + langMap := make(map[string][]string) + for _, user := range recipients { + if user.ID != rel.PublisherID { + langMap[user.Language] = append(langMap[user.Language], user.Email) } } + for lang, tos := range langMap { + mailNewRelease(lang, tos, rel) + } +} + +func mailNewRelease(lang string, tos []string, rel *models.Release) { + locale := translation.NewLocale(lang) + rel.RenderedNote = markdown.RenderString(rel.Note, rel.Repo.Link(), rel.Repo.ComposeMetas()) - // TODO: i18n - subject := fmt.Sprintf("%s in %s released", rel.TagName, rel.Repo.FullName()) + subject := locale.Tr("mail.release.new.subject", rel.TagName, rel.Repo.FullName()) mailMeta := map[string]interface{}{ "Release": rel, "Subject": subject, @@ -51,12 +58,13 @@ func MailNewRelease(rel *models.Release) { var mailBody bytes.Buffer - if err = bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewReleaseMail), mailMeta); err != nil { + // TODO: i18n + if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewReleaseMail), mailMeta); err != nil { log.Error("ExecuteTemplate [%s]: %v", string(tplNewReleaseMail)+"/body", err) return } - msgs := make([]*Message, 0, len(recipients)) + msgs := make([]*Message, 0, len(tos)) publisherName := rel.Publisher.DisplayName() relURL := "<" + rel.HTMLURL() + ">" for _, to := range tos { diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 2df6593e38e44..feaa22c4a6dfb 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -9,6 +9,7 @@ import ( "fmt" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/translation" ) // SendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created @@ -40,17 +41,16 @@ func SendRepoTransferNotifyMail(doer, newOwner *models.User, repo *models.Reposi // sendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created for each language func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *models.User, emails []string, repo *models.Repository) error { var ( + locale = translation.NewLocale(lang) content bytes.Buffer ) - // TODO: i18n - destination := "you" + destination := locale.Tr("mail.repo.transfer.to_you") if newOwner.IsOrganization() { destination = newOwner.DisplayName() } - // TODO: i18n - subject := fmt.Sprintf("%s would like to transfer \"%s\" to %s", doer.DisplayName(), repo.FullName(), destination) + subject := locale.Tr("mail.repo.transfer.subject", doer.DisplayName(), repo.FullName(), destination) data := map[string]interface{}{ "Doer": doer, "User": repo.Owner, From 66799a21d69e30132449036ba6dcd88777337cb1 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 22:29:02 +0100 Subject: [PATCH 15/22] linter ... --- services/mailer/mail.go | 2 +- services/mailer/mail_issue.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index d7e522b1d96eb..58f45c91cd5cf 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -169,7 +169,7 @@ func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) { func composeIssueCommentMessages(ctx *mailCommentContext, lang string, tos []string, fromMention bool, info string) []*Message { var ( - locale = translation.NewLocale(lang) + // locale = translation.NewLocale(lang) subject string link string diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index 519afa3b442f1..b477635be5095 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -24,6 +24,7 @@ type mailCommentContext struct { } const ( + // MailBatchSize set the batch size used in mailIssueCommentBatch MailBatchSize = 100 ) From f2c380879d203a053d90f392fc1e1f8723fef138 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 22:38:12 +0100 Subject: [PATCH 16/22] Tr extend --- options/locale/locale_en-US.ini | 3 ++- services/mailer/mail_repo.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 736b6dfc93f44..562f9229838e4 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -322,7 +322,8 @@ register_notify = Welcome to Gitea release.new.subject = %s in %s released -repo.transfer.subject = %s would like to transfer "%s" to %s +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 diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index feaa22c4a6dfb..45b5454c9e9cb 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -46,11 +46,12 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *models.User, ) destination := locale.Tr("mail.repo.transfer.to_you") + subject := locale.Tr("mail.repo.transfer.subject_to_you", doer.DisplayName(), repo.FullName()) if newOwner.IsOrganization() { destination = newOwner.DisplayName() + subject = locale.Tr("mail.repo.transfer.subject_to", doer.DisplayName(), repo.FullName(), destination) } - subject := locale.Tr("mail.repo.transfer.subject", doer.DisplayName(), repo.FullName(), destination) data := map[string]interface{}{ "Doer": doer, "User": repo.Owner, From 16b64b22e34f0a7a44ab85b2b8a2b3beea615433 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 21 Mar 2021 22:45:11 +0100 Subject: [PATCH 17/22] start tmpl edit ... --- templates/mail/notify/repo_transfer.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/mail/notify/repo_transfer.tmpl b/templates/mail/notify/repo_transfer.tmpl index 68ceded116738..e0dca8869d669 100644 --- a/templates/mail/notify/repo_transfer.tmpl +++ b/templates/mail/notify/repo_transfer.tmpl @@ -11,7 +11,7 @@
---
- View it on Gitea.
+ View it on {{AppName}}.