Skip to content

Commit 80d6c6d

Browse files
6543zeripath
andauthored
[refactor] mailer service (#15072)
* 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 <[email protected]> * localize subjects * linter ... * Tr extend * start tmpl edit ... * Apply suggestions from code review * use translation.Locale * improve mailIssueCommentBatch Signed-off-by: Andrew Thornton <[email protected]> * add i18n to datas Signed-off-by: Andrew Thornton <[email protected]> * a comment Co-authored-by: Andrew Thornton <[email protected]>
1 parent cc2d540 commit 80d6c6d

File tree

15 files changed

+183
-143
lines changed

15 files changed

+183
-143
lines changed

models/user.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,6 @@ func (u *User) GenerateEmailActivateCode(email string) string {
331331
return code
332332
}
333333

334-
// GenerateActivateCode generates an activate code based on user information.
335-
func (u *User) GenerateActivateCode() string {
336-
return u.GenerateEmailActivateCode(u.Email)
337-
}
338-
339334
// GetFollowers returns range of user's followers.
340335
func (u *User) GetFollowers(listOptions ListOptions) ([]*User, error) {
341336
sess := x.

modules/notification/mail/mail.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,14 @@ func (m *mailNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *model
104104
// mail only sent to added assignees and not self-assignee
105105
if !removed && doer.ID != assignee.ID && assignee.EmailNotifications() == models.EmailNotificationsEnabled {
106106
ct := fmt.Sprintf("Assigned #%d.", issue.Index)
107-
mailer.SendIssueAssignedMail(issue, doer, ct, comment, []string{assignee.Email})
107+
mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*models.User{assignee})
108108
}
109109
}
110110

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

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

155155
func (m *mailNotifier) NotifyPullRevieweDismiss(doer *models.User, review *models.Review, comment *models.Comment) {
156-
if err := mailer.MailParticipantsComment(comment, models.ActionPullReviewDismissed, review.Issue, []*models.User{}); err != nil {
156+
if err := mailer.MailParticipantsComment(comment, models.ActionPullReviewDismissed, review.Issue, nil); err != nil {
157157
log.Error("MailParticipantsComment: %v", err)
158158
}
159159
}

options/locale/locale_en-US.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,14 @@ reset_password = Recover your account
320320
register_success = Registration successful
321321
register_notify = Welcome to Gitea
322322
323+
release.new.subject = %s in %s released
324+
325+
repo.transfer.subject_to = %s would like to transfer "%s" to %s
326+
repo.transfer.subject_to_you = %s would like to transfer "%s" to you
327+
repo.transfer.to_you = you
328+
329+
repo.collaborator.added.subject = %s added you to %s
330+
323331
[modal]
324332
yes = Yes
325333
no = No

routers/admin/users.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func NewUserPost(ctx *context.Context) {
154154

155155
// Send email notification.
156156
if form.SendNotify {
157-
mailer.SendRegisterNotifyMail(ctx.Locale, u)
157+
mailer.SendRegisterNotifyMail(u)
158158
}
159159

160160
ctx.Flash.Success(ctx.Tr("admin.users.new_success", u.Name))

routers/api/v1/admin/user.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func CreateUser(ctx *context.APIContext) {
114114

115115
// Send email notification.
116116
if form.SendNotify {
117-
mailer.SendRegisterNotifyMail(ctx.Locale, u)
117+
mailer.SendRegisterNotifyMail(u)
118118
}
119119
ctx.JSON(http.StatusCreated, convert.ToUser(u, ctx.User))
120120
}

routers/user/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,7 @@ func ForgotPasswdPost(ctx *context.Context) {
13971397
return
13981398
}
13991399

1400-
mailer.SendResetPasswordMail(ctx.Locale, u)
1400+
mailer.SendResetPasswordMail(u)
14011401

14021402
if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
14031403
log.Error("Set cache(MailResendLimit) fail: %v", err)

routers/user/setting/account.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func EmailPost(ctx *context.Context) {
132132
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
133133
return
134134
}
135-
mailer.SendActivateEmailMail(ctx.Locale, ctx.User, email)
135+
mailer.SendActivateEmailMail(ctx.User, email)
136136
address = email.Email
137137
}
138138

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

195195
// Send confirmation email
196196
if setting.Service.RegisterEmailConfirm {
197-
mailer.SendActivateEmailMail(ctx.Locale, ctx.User, email)
197+
mailer.SendActivateEmailMail(ctx.User, email)
198198
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
199199
log.Error("Set cache(MailResendLimit) fail: %v", err)
200200
}

services/mailer/mail.go

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"code.gitea.io/gitea/modules/markup/markdown"
2323
"code.gitea.io/gitea/modules/setting"
2424
"code.gitea.io/gitea/modules/timeutil"
25+
"code.gitea.io/gitea/modules/translation"
2526

2627
"gopkg.in/gomail.v2"
2728
)
@@ -57,17 +58,21 @@ func SendTestMail(email string) error {
5758
return gomail.Send(Sender, NewMessage([]string{email}, "Gitea Test Email!", "Gitea Test Email!").ToMessage())
5859
}
5960

60-
// SendUserMail sends a mail to the user
61-
func SendUserMail(language string, u *models.User, tpl base.TplName, code, subject, info string) {
61+
// sendUserMail sends a mail to the user
62+
func sendUserMail(language string, u *models.User, tpl base.TplName, code, subject, info string) {
63+
locale := translation.NewLocale(language)
6264
data := map[string]interface{}{
6365
"DisplayName": u.DisplayName(),
6466
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, language),
6567
"ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, language),
6668
"Code": code,
69+
"i18n": locale,
70+
"Language": locale.Language(),
6771
}
6872

6973
var content bytes.Buffer
7074

75+
// TODO: i18n templates?
7176
if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil {
7277
log.Error("Template: %v", err)
7378
return
@@ -79,33 +84,32 @@ func SendUserMail(language string, u *models.User, tpl base.TplName, code, subje
7984
SendAsync(msg)
8085
}
8186

82-
// Locale represents an interface to translation
83-
type Locale interface {
84-
Language() string
85-
Tr(string, ...interface{}) string
86-
}
87-
8887
// SendActivateAccountMail sends an activation mail to the user (new user registration)
89-
func SendActivateAccountMail(locale Locale, u *models.User) {
90-
SendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateActivateCode(), locale.Tr("mail.activate_account"), "activate account")
88+
func SendActivateAccountMail(locale translation.Locale, u *models.User) {
89+
sendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateEmailActivateCode(u.Email), locale.Tr("mail.activate_account"), "activate account")
9190
}
9291

9392
// SendResetPasswordMail sends a password reset mail to the user
94-
func SendResetPasswordMail(locale Locale, u *models.User) {
95-
SendUserMail(locale.Language(), u, mailAuthResetPassword, u.GenerateActivateCode(), locale.Tr("mail.reset_password"), "recover account")
93+
func SendResetPasswordMail(u *models.User) {
94+
locale := translation.NewLocale(u.Language)
95+
sendUserMail(u.Language, u, mailAuthResetPassword, u.GenerateEmailActivateCode(u.Email), locale.Tr("mail.reset_password"), "recover account")
9696
}
9797

9898
// SendActivateEmailMail sends confirmation email to confirm new email address
99-
func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAddress) {
99+
func SendActivateEmailMail(u *models.User, email *models.EmailAddress) {
100+
locale := translation.NewLocale(u.Language)
100101
data := map[string]interface{}{
101102
"DisplayName": u.DisplayName(),
102103
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale.Language()),
103104
"Code": u.GenerateEmailActivateCode(email.Email),
104105
"Email": email.Email,
106+
"i18n": locale,
107+
"Language": locale.Language(),
105108
}
106109

107110
var content bytes.Buffer
108111

112+
// TODO: i18n templates?
109113
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
110114
log.Error("Template: %v", err)
111115
return
@@ -118,19 +122,19 @@ func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAdd
118122
}
119123

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

127128
data := map[string]interface{}{
128129
"DisplayName": u.DisplayName(),
129130
"Username": u.Name,
131+
"i18n": locale,
132+
"Language": locale.Language(),
130133
}
131134

132135
var content bytes.Buffer
133136

137+
// TODO: i18n templates?
134138
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil {
135139
log.Error("Template: %v", err)
136140
return
@@ -144,17 +148,21 @@ func SendRegisterNotifyMail(locale Locale, u *models.User) {
144148

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

154+
subject := locale.Tr("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName)
150155
data := map[string]interface{}{
151156
"Subject": subject,
152157
"RepoName": repoName,
153158
"Link": repo.HTMLURL(),
159+
"i18n": locale,
160+
"Language": locale.Language(),
154161
}
155162

156163
var content bytes.Buffer
157164

165+
// TODO: i18n templates?
158166
if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
159167
log.Error("Template: %v", err)
160168
return
@@ -166,7 +174,7 @@ func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) {
166174
SendAsync(msg)
167175
}
168176

169-
func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMention bool, info string) []*Message {
177+
func composeIssueCommentMessages(ctx *mailCommentContext, lang string, tos []string, fromMention bool, info string) []*Message {
170178

171179
var (
172180
subject string
@@ -192,7 +200,6 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent
192200

193201
// This is the body of the new issue or comment, not the mail body
194202
body := string(markup.RenderByType(markdown.MarkupName, []byte(ctx.Content), ctx.Issue.Repo.HTMLURL(), ctx.Issue.Repo.ComposeMetas()))
195-
196203
actType, actName, tplName := actionToTemplate(ctx.Issue, ctx.ActionType, commentType, reviewType)
197204

198205
if actName != "new" {
@@ -208,6 +215,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent
208215
}
209216
}
210217
}
218+
locale := translation.NewLocale(lang)
211219

212220
mailMeta := map[string]interface{}{
213221
"FallbackSubject": fallback,
@@ -224,13 +232,16 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent
224232
"ActionType": actType,
225233
"ActionName": actName,
226234
"ReviewComments": reviewComments,
235+
"i18n": locale,
236+
"Language": locale.Language(),
227237
}
228238

229239
var mailSubject bytes.Buffer
240+
// TODO: i18n templates?
230241
if err := subjectTemplates.ExecuteTemplate(&mailSubject, string(tplName), mailMeta); err == nil {
231242
subject = sanitizeSubject(mailSubject.String())
232243
} else {
233-
log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/subject", err)
244+
log.Error("ExecuteTemplate [%s]: %v", tplName+"/subject", err)
234245
}
235246

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

244255
var mailBody bytes.Buffer
245256

257+
// TODO: i18n templates?
246258
if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplName), mailMeta); err != nil {
247259
log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/body", err)
248260
}
@@ -276,14 +288,21 @@ func sanitizeSubject(subject string) string {
276288
}
277289

278290
// SendIssueAssignedMail composes and sends issue assigned email
279-
func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
280-
SendAsyncs(composeIssueCommentMessages(&mailCommentContext{
281-
Issue: issue,
282-
Doer: doer,
283-
ActionType: models.ActionType(0),
284-
Content: content,
285-
Comment: comment,
286-
}, tos, false, "issue assigned"))
291+
func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, recipients []*models.User) {
292+
langMap := make(map[string][]string)
293+
for _, user := range recipients {
294+
langMap[user.Language] = append(langMap[user.Language], user.Email)
295+
}
296+
297+
for lang, tos := range langMap {
298+
SendAsyncs(composeIssueCommentMessages(&mailCommentContext{
299+
Issue: issue,
300+
Doer: doer,
301+
ActionType: models.ActionType(0),
302+
Content: content,
303+
Comment: comment,
304+
}, lang, tos, false, "issue assigned"))
305+
}
287306
}
288307

289308
// actionToTemplate returns the type and name of the action facing the user

services/mailer/mail_comment.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,23 @@ import (
99
"code.gitea.io/gitea/modules/log"
1010
)
1111

12-
// MailParticipantsComment sends new comment emails to repository watchers
13-
// and mentioned people.
12+
// MailParticipantsComment sends new comment emails to repository watchers and mentioned people.
1413
func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) error {
15-
return mailParticipantsComment(c, opType, issue, mentions)
16-
}
17-
18-
func mailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) (err error) {
19-
mentionedIDs := make([]int64, len(mentions))
20-
for i, u := range mentions {
21-
mentionedIDs[i] = u.ID
22-
}
23-
if err = mailIssueCommentToParticipants(
14+
if err := mailIssueCommentToParticipants(
2415
&mailCommentContext{
2516
Issue: issue,
2617
Doer: c.Poster,
2718
ActionType: opType,
2819
Content: c.Content,
2920
Comment: c,
30-
}, mentionedIDs); err != nil {
21+
}, mentions); err != nil {
3122
log.Error("mailIssueCommentToParticipants: %v", err)
3223
}
3324
return nil
3425
}
3526

3627
// MailMentionsComment sends email to users mentioned in a code comment
3728
func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*models.User) (err error) {
38-
mentionedIDs := make([]int64, len(mentions))
39-
for i, u := range mentions {
40-
mentionedIDs[i] = u.ID
41-
}
4229
visited := make(map[int64]bool, len(mentions)+1)
4330
visited[c.Poster.ID] = true
4431
if err = mailIssueCommentBatch(
@@ -48,7 +35,7 @@ func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*
4835
ActionType: models.ActionCommentPull,
4936
Content: c.Content,
5037
Comment: c,
51-
}, mentionedIDs, visited, true); err != nil {
38+
}, mentions, visited, true); err != nil {
5239
log.Error("mailIssueCommentBatch: %v", err)
5340
}
5441
return nil

0 commit comments

Comments
 (0)