Refactor mail template and support preview (#34990)
Some checks failed
release-nightly / nightly-binary (push) Has been cancelled
release-nightly / nightly-docker-rootful (push) Has been cancelled
release-nightly / nightly-docker-rootless (push) Has been cancelled

This commit is contained in:
wxiaoguang
2025-07-09 10:25:25 +08:00
committed by GitHub
parent 2cc3368610
commit 55f350542c
14 changed files with 156 additions and 49 deletions

View File

@@ -15,7 +15,7 @@ import (
"mime"
"regexp"
"strings"
texttmpl "text/template"
"sync/atomic"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/typesniffer"
sender_service "code.gitea.io/gitea/services/mailer/sender"
@@ -31,11 +32,13 @@ import (
const mailMaxSubjectRunes = 256 // There's no actual limit for subject in RFC 5322
var (
bodyTemplates *template.Template
subjectTemplates *texttmpl.Template
subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)
)
var loadedTemplates atomic.Pointer[templates.MailTemplates]
var subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)
func LoadedTemplates() *templates.MailTemplates {
return loadedTemplates.Load()
}
// SendTestMail sends a test mail
func SendTestMail(email string) error {

View File

@@ -119,7 +119,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang
}
var mailSubject bytes.Buffer
if err := subjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil {
if err := LoadedTemplates().SubjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil {
subject = sanitizeSubject(mailSubject.String())
if subject == "" {
subject = fallback
@@ -134,7 +134,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang
var mailBody bytes.Buffer
if err := bodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil {
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil {
log.Error("ExecuteTemplate [%s]: %v", tplName+"/body", err)
}
@@ -260,14 +260,14 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act
}
template = typeName + "/" + name
ok := bodyTemplates.Lookup(template) != nil
ok := LoadedTemplates().BodyTemplates.Lookup(template) != nil
if !ok && typeName != "issue" {
template = "issue/" + name
ok = bodyTemplates.Lookup(template) != nil
ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
}
if !ok {
template = typeName + "/default"
ok = bodyTemplates.Lookup(template) != nil
ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
}
if !ok {
template = "issue/default"

View File

@@ -79,7 +79,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re
var mailBody bytes.Buffer
if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewReleaseMail), mailMeta); err != nil {
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, string(tplNewReleaseMail), mailMeta); err != nil {
log.Error("ExecuteTemplate [%s]: %v", string(tplNewReleaseMail)+"/body", err)
return
}

View File

@@ -78,7 +78,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
"Destination": destination,
}
if err := bodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil {
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil {
return err
}

View File

@@ -62,7 +62,7 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod
}
var mailBody bytes.Buffer
if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplTeamInviteMail), mailMeta); err != nil {
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, string(tplTeamInviteMail), mailMeta); err != nil {
log.Error("ExecuteTemplate [%s]: %v", string(tplTeamInviteMail)+"/body", err)
return err
}

View File

@@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/services/attachment"
sender_service "code.gitea.io/gitea/services/mailer/sender"
@@ -95,6 +96,13 @@ func prepareMailerBase64Test(t *testing.T) (doer *user_model.User, repo *repo_mo
return user, repo, issue, att1, att2
}
func prepareMailTemplates(name, subjectTmpl, bodyTmpl string) {
loadedTemplates.Store(&templates.MailTemplates{
SubjectTemplates: texttmpl.Must(texttmpl.New(name).Parse(subjectTmpl)),
BodyTemplates: template.Must(template.New(name).Parse(bodyTmpl)),
})
}
func TestComposeIssueComment(t *testing.T) {
doer, _, issue, comment := prepareMailerTest(t)
@@ -107,8 +115,7 @@ func TestComposeIssueComment(t *testing.T) {
setting.IncomingEmail.Enabled = true
defer func() { setting.IncomingEmail.Enabled = false }()
subjectTemplates = texttmpl.Must(texttmpl.New("issue/comment").Parse(subjectTpl))
bodyTemplates = template.Must(template.New("issue/comment").Parse(bodyTpl))
prepareMailTemplates("issue/comment", subjectTpl, bodyTpl)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
@@ -153,8 +160,7 @@ func TestComposeIssueComment(t *testing.T) {
func TestMailMentionsComment(t *testing.T) {
doer, _, issue, comment := prepareMailerTest(t)
comment.Poster = doer
subjectTemplates = texttmpl.Must(texttmpl.New("issue/comment").Parse(subjectTpl))
bodyTemplates = template.Must(template.New("issue/comment").Parse(bodyTpl))
prepareMailTemplates("issue/comment", subjectTpl, bodyTpl)
mails := 0
defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) {
@@ -169,9 +175,7 @@ func TestMailMentionsComment(t *testing.T) {
func TestComposeIssueMessage(t *testing.T) {
doer, _, issue, _ := prepareMailerTest(t)
subjectTemplates = texttmpl.Must(texttmpl.New("issue/new").Parse(subjectTpl))
bodyTemplates = template.Must(template.New("issue/new").Parse(bodyTpl))
prepareMailTemplates("issue/new", subjectTpl, bodyTpl)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
@@ -200,15 +204,14 @@ func TestTemplateSelection(t *testing.T) {
doer, repo, issue, comment := prepareMailerTest(t)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
subjectTemplates = texttmpl.Must(texttmpl.New("issue/default").Parse("issue/default/subject"))
texttmpl.Must(subjectTemplates.New("issue/new").Parse("issue/new/subject"))
texttmpl.Must(subjectTemplates.New("pull/comment").Parse("pull/comment/subject"))
texttmpl.Must(subjectTemplates.New("issue/close").Parse("")) // Must default to fallback subject
prepareMailTemplates("issue/default", "issue/default/subject", "issue/default/body")
bodyTemplates = template.Must(template.New("issue/default").Parse("issue/default/body"))
template.Must(bodyTemplates.New("issue/new").Parse("issue/new/body"))
template.Must(bodyTemplates.New("pull/comment").Parse("pull/comment/body"))
template.Must(bodyTemplates.New("issue/close").Parse("issue/close/body"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/new").Parse("issue/new/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("pull/comment").Parse("pull/comment/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/close").Parse("")) // Must default to a fallback subject
template.Must(LoadedTemplates().BodyTemplates.New("issue/new").Parse("issue/new/body"))
template.Must(LoadedTemplates().BodyTemplates.New("pull/comment").Parse("pull/comment/body"))
template.Must(LoadedTemplates().BodyTemplates.New("issue/close").Parse("issue/close/body"))
expect := func(t *testing.T, msg *sender_service.Message, expSubject, expBody string) {
subject := msg.ToMessage().GetGenHeader("Subject")
@@ -253,9 +256,7 @@ func TestTemplateServices(t *testing.T) {
expect := func(t *testing.T, issue *issues_model.Issue, comment *issues_model.Comment, doer *user_model.User,
actionType activities_model.ActionType, fromMention bool, tplSubject, tplBody, expSubject, expBody string,
) {
subjectTemplates = texttmpl.Must(texttmpl.New("issue/default").Parse(tplSubject))
bodyTemplates = template.Must(template.New("issue/default").Parse(tplBody))
prepareMailTemplates("issue/default", tplSubject, tplBody)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
msg := testComposeIssueCommentMessage(t, &mailComment{
Issue: issue, Doer: doer, ActionType: actionType,
@@ -512,8 +513,7 @@ func TestEmbedBase64Images(t *testing.T) {
att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64)
t.Run("ComposeMessage", func(t *testing.T) {
subjectTemplates = texttmpl.Must(texttmpl.New("issue/new").Parse(subjectTpl))
bodyTemplates = template.Must(template.New("issue/new").Parse(bodyTpl))
prepareMailTemplates("issue/new", subjectTpl, bodyTpl)
issue.Content = fmt.Sprintf(`MSG-BEFORE <image src="attachments/%s"> MSG-AFTER`, att1.UUID)
require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content"))

View File

@@ -39,7 +39,7 @@ func sendUserMail(language string, u *user_model.User, tpl templates.TplName, co
var content bytes.Buffer
if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil {
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil {
log.Error("Template: %v", err)
return
}
@@ -90,7 +90,7 @@ func SendActivateEmailMail(u *user_model.User, email string) {
var content bytes.Buffer
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
log.Error("Template: %v", err)
return
}
@@ -118,7 +118,7 @@ func SendRegisterNotifyMail(u *user_model.User) {
var content bytes.Buffer
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil {
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil {
log.Error("Template: %v", err)
return
}
@@ -149,7 +149,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository)
var content bytes.Buffer
if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
log.Error("Template: %v", err)
return
}

View File

@@ -43,7 +43,7 @@ func NewContext(ctx context.Context) {
sender = &sender_service.SMTPSender{}
}
subjectTemplates, bodyTemplates = templates.Mailer(ctx)
templates.LoadMailTemplates(ctx, &loadedTemplates)
mailQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "mail", func(items ...*sender_service.Message) []*sender_service.Message {
for _, msg := range items {