mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-03 21:08:25 +00:00 
			
		
		
		
	Refactor mail template and support preview (#34990)
This commit is contained in:
		@@ -9,6 +9,7 @@ import (
 | 
				
			|||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync/atomic"
 | 
				
			||||||
	texttmpl "text/template"
 | 
						texttmpl "text/template"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
@@ -16,6 +17,12 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MailTemplates struct {
 | 
				
			||||||
 | 
						TemplateNames    []string
 | 
				
			||||||
 | 
						BodyTemplates    *template.Template
 | 
				
			||||||
 | 
						SubjectTemplates *texttmpl.Template
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`)
 | 
					var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject
 | 
					// mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject
 | 
				
			||||||
@@ -52,16 +59,17 @@ func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template,
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Mailer provides the templates required for sending notification mails.
 | 
					// LoadMailTemplates provides the templates required for sending notification mails.
 | 
				
			||||||
func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) {
 | 
					func LoadMailTemplates(ctx context.Context, loadedTemplates *atomic.Pointer[MailTemplates]) {
 | 
				
			||||||
 | 
						assetFS := AssetFS()
 | 
				
			||||||
 | 
						refreshTemplates := func(firstRun bool) {
 | 
				
			||||||
 | 
							var templateNames []string
 | 
				
			||||||
		subjectTemplates := texttmpl.New("")
 | 
							subjectTemplates := texttmpl.New("")
 | 
				
			||||||
		bodyTemplates := template.New("")
 | 
							bodyTemplates := template.New("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		subjectTemplates.Funcs(mailSubjectTextFuncMap())
 | 
							subjectTemplates.Funcs(mailSubjectTextFuncMap())
 | 
				
			||||||
		bodyTemplates.Funcs(NewFuncMap())
 | 
							bodyTemplates.Funcs(NewFuncMap())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assetFS := AssetFS()
 | 
					 | 
				
			||||||
	refreshTemplates := func(firstRun bool) {
 | 
					 | 
				
			||||||
		if !firstRun {
 | 
							if !firstRun {
 | 
				
			||||||
			log.Trace("Reloading mail templates")
 | 
								log.Trace("Reloading mail templates")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -81,6 +89,7 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) {
 | 
				
			|||||||
			if firstRun {
 | 
								if firstRun {
 | 
				
			||||||
				log.Trace("Adding mail template %s: %s by %s", tmplName, assetPath, layerName)
 | 
									log.Trace("Adding mail template %s: %s by %s", tmplName, assetPath, layerName)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								templateNames = append(templateNames, tmplName)
 | 
				
			||||||
			if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil {
 | 
								if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil {
 | 
				
			||||||
				if firstRun {
 | 
									if firstRun {
 | 
				
			||||||
					log.Fatal("Failed to parse mail template, err: %v", err)
 | 
										log.Fatal("Failed to parse mail template, err: %v", err)
 | 
				
			||||||
@@ -88,6 +97,12 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) {
 | 
				
			|||||||
				log.Error("Failed to parse mail template, err: %v", err)
 | 
									log.Error("Failed to parse mail template, err: %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							loaded := &MailTemplates{
 | 
				
			||||||
 | 
								TemplateNames:    templateNames,
 | 
				
			||||||
 | 
								BodyTemplates:    bodyTemplates,
 | 
				
			||||||
 | 
								SubjectTemplates: subjectTemplates,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							loadedTemplates.Store(loaded)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	refreshTemplates(true)
 | 
						refreshTemplates(true)
 | 
				
			||||||
@@ -99,6 +114,4 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) {
 | 
				
			|||||||
			refreshTemplates(false)
 | 
								refreshTemplates(false)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return subjectTemplates, bodyTemplates
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										58
									
								
								routers/web/devtest/mail_preview.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								routers/web/devtest/mail_preview.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package devtest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/templates"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/mailer"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/yaml.v3"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func MailPreviewRender(ctx *context.Context) {
 | 
				
			||||||
 | 
						tmplName := ctx.PathParam("*")
 | 
				
			||||||
 | 
						mockDataContent, err := templates.AssetFS().ReadFile("mail/" + tmplName + ".mock.yml")
 | 
				
			||||||
 | 
						mockData := map[string]any{}
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							err = yaml.Unmarshal(mockDataContent, &mockData)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								http.Error(ctx.Resp, "Failed to parse mock data: "+err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mockData["locale"] = ctx.Locale
 | 
				
			||||||
 | 
						err = mailer.LoadedTemplates().BodyTemplates.ExecuteTemplate(ctx.Resp, tmplName, mockData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							_, _ = ctx.Resp.Write([]byte(err.Error()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func prepareMailPreviewRender(ctx *context.Context, tmplName string) {
 | 
				
			||||||
 | 
						tmplSubject := mailer.LoadedTemplates().SubjectTemplates.Lookup(tmplName)
 | 
				
			||||||
 | 
						if tmplSubject == nil {
 | 
				
			||||||
 | 
							ctx.Data["RenderMailSubject"] = "default subject"
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							var buf strings.Builder
 | 
				
			||||||
 | 
							err := tmplSubject.Execute(&buf, nil)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.Data["RenderMailSubject"] = err.Error()
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								ctx.Data["RenderMailSubject"] = buf.String()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Data["RenderMailTemplateName"] = tmplName
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func MailPreview(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["MailTemplateNames"] = mailer.LoadedTemplates().TemplateNames
 | 
				
			||||||
 | 
						tmplName := ctx.FormString("tmpl")
 | 
				
			||||||
 | 
						if tmplName != "" {
 | 
				
			||||||
 | 
							prepareMailPreviewRender(ctx, tmplName)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.HTML(http.StatusOK, "devtest/mail-preview")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1659,6 +1659,8 @@ func registerWebRoutes(m *web.Router) {
 | 
				
			|||||||
		m.Group("/devtest", func() {
 | 
							m.Group("/devtest", func() {
 | 
				
			||||||
			m.Any("", devtest.List)
 | 
								m.Any("", devtest.List)
 | 
				
			||||||
			m.Any("/fetch-action-test", devtest.FetchActionTest)
 | 
								m.Any("/fetch-action-test", devtest.FetchActionTest)
 | 
				
			||||||
 | 
								m.Any("/mail-preview", devtest.MailPreview)
 | 
				
			||||||
 | 
								m.Any("/mail-preview/*", devtest.MailPreviewRender)
 | 
				
			||||||
			m.Any("/{sub}", devtest.TmplCommon)
 | 
								m.Any("/{sub}", devtest.TmplCommon)
 | 
				
			||||||
			m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView)
 | 
								m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView)
 | 
				
			||||||
			m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs)
 | 
								m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ import (
 | 
				
			|||||||
	"mime"
 | 
						"mime"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	texttmpl "text/template"
 | 
						"sync/atomic"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
@@ -23,6 +23,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/storage"
 | 
						"code.gitea.io/gitea/modules/storage"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/templates"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/typesniffer"
 | 
						"code.gitea.io/gitea/modules/typesniffer"
 | 
				
			||||||
	sender_service "code.gitea.io/gitea/services/mailer/sender"
 | 
						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
 | 
					const mailMaxSubjectRunes = 256 // There's no actual limit for subject in RFC 5322
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var loadedTemplates atomic.Pointer[templates.MailTemplates]
 | 
				
			||||||
	bodyTemplates       *template.Template
 | 
					
 | 
				
			||||||
	subjectTemplates    *texttmpl.Template
 | 
					var subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)
 | 
				
			||||||
	subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)
 | 
					
 | 
				
			||||||
)
 | 
					func LoadedTemplates() *templates.MailTemplates {
 | 
				
			||||||
 | 
						return loadedTemplates.Load()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SendTestMail sends a test mail
 | 
					// SendTestMail sends a test mail
 | 
				
			||||||
func SendTestMail(email string) error {
 | 
					func SendTestMail(email string) error {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,7 +119,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var mailSubject bytes.Buffer
 | 
						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())
 | 
							subject = sanitizeSubject(mailSubject.String())
 | 
				
			||||||
		if subject == "" {
 | 
							if subject == "" {
 | 
				
			||||||
			subject = fallback
 | 
								subject = fallback
 | 
				
			||||||
@@ -134,7 +134,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var mailBody bytes.Buffer
 | 
						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)
 | 
							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
 | 
						template = typeName + "/" + name
 | 
				
			||||||
	ok := bodyTemplates.Lookup(template) != nil
 | 
						ok := LoadedTemplates().BodyTemplates.Lookup(template) != nil
 | 
				
			||||||
	if !ok && typeName != "issue" {
 | 
						if !ok && typeName != "issue" {
 | 
				
			||||||
		template = "issue/" + name
 | 
							template = "issue/" + name
 | 
				
			||||||
		ok = bodyTemplates.Lookup(template) != nil
 | 
							ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		template = typeName + "/default"
 | 
							template = typeName + "/default"
 | 
				
			||||||
		ok = bodyTemplates.Lookup(template) != nil
 | 
							ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		template = "issue/default"
 | 
							template = "issue/default"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -79,7 +79,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var mailBody bytes.Buffer
 | 
						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)
 | 
							log.Error("ExecuteTemplate [%s]: %v", string(tplNewReleaseMail)+"/body", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -78,7 +78,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
 | 
				
			|||||||
		"Destination": destination,
 | 
							"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
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,7 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var mailBody bytes.Buffer
 | 
						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)
 | 
							log.Error("ExecuteTemplate [%s]: %v", string(tplTeamInviteMail)+"/body", err)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/markup"
 | 
						"code.gitea.io/gitea/modules/markup"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/storage"
 | 
						"code.gitea.io/gitea/modules/storage"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/templates"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/test"
 | 
						"code.gitea.io/gitea/modules/test"
 | 
				
			||||||
	"code.gitea.io/gitea/services/attachment"
 | 
						"code.gitea.io/gitea/services/attachment"
 | 
				
			||||||
	sender_service "code.gitea.io/gitea/services/mailer/sender"
 | 
						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
 | 
						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) {
 | 
					func TestComposeIssueComment(t *testing.T) {
 | 
				
			||||||
	doer, _, issue, comment := prepareMailerTest(t)
 | 
						doer, _, issue, comment := prepareMailerTest(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,8 +115,7 @@ func TestComposeIssueComment(t *testing.T) {
 | 
				
			|||||||
	setting.IncomingEmail.Enabled = true
 | 
						setting.IncomingEmail.Enabled = true
 | 
				
			||||||
	defer func() { setting.IncomingEmail.Enabled = false }()
 | 
						defer func() { setting.IncomingEmail.Enabled = false }()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	subjectTemplates = texttmpl.Must(texttmpl.New("issue/comment").Parse(subjectTpl))
 | 
						prepareMailTemplates("issue/comment", subjectTpl, bodyTpl)
 | 
				
			||||||
	bodyTemplates = template.Must(template.New("issue/comment").Parse(bodyTpl))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
 | 
						recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
 | 
				
			||||||
	msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
 | 
						msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
 | 
				
			||||||
@@ -153,8 +160,7 @@ func TestComposeIssueComment(t *testing.T) {
 | 
				
			|||||||
func TestMailMentionsComment(t *testing.T) {
 | 
					func TestMailMentionsComment(t *testing.T) {
 | 
				
			||||||
	doer, _, issue, comment := prepareMailerTest(t)
 | 
						doer, _, issue, comment := prepareMailerTest(t)
 | 
				
			||||||
	comment.Poster = doer
 | 
						comment.Poster = doer
 | 
				
			||||||
	subjectTemplates = texttmpl.Must(texttmpl.New("issue/comment").Parse(subjectTpl))
 | 
						prepareMailTemplates("issue/comment", subjectTpl, bodyTpl)
 | 
				
			||||||
	bodyTemplates = template.Must(template.New("issue/comment").Parse(bodyTpl))
 | 
					 | 
				
			||||||
	mails := 0
 | 
						mails := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) {
 | 
						defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) {
 | 
				
			||||||
@@ -169,9 +175,7 @@ func TestMailMentionsComment(t *testing.T) {
 | 
				
			|||||||
func TestComposeIssueMessage(t *testing.T) {
 | 
					func TestComposeIssueMessage(t *testing.T) {
 | 
				
			||||||
	doer, _, issue, _ := prepareMailerTest(t)
 | 
						doer, _, issue, _ := prepareMailerTest(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	subjectTemplates = texttmpl.Must(texttmpl.New("issue/new").Parse(subjectTpl))
 | 
						prepareMailTemplates("issue/new", subjectTpl, bodyTpl)
 | 
				
			||||||
	bodyTemplates = template.Must(template.New("issue/new").Parse(bodyTpl))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
 | 
						recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
 | 
				
			||||||
	msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
 | 
						msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
 | 
				
			||||||
		Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
 | 
							Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
 | 
				
			||||||
@@ -200,15 +204,14 @@ func TestTemplateSelection(t *testing.T) {
 | 
				
			|||||||
	doer, repo, issue, comment := prepareMailerTest(t)
 | 
						doer, repo, issue, comment := prepareMailerTest(t)
 | 
				
			||||||
	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
 | 
						recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	subjectTemplates = texttmpl.Must(texttmpl.New("issue/default").Parse("issue/default/subject"))
 | 
						prepareMailTemplates("issue/default", "issue/default/subject", "issue/default/body")
 | 
				
			||||||
	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
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bodyTemplates = template.Must(template.New("issue/default").Parse("issue/default/body"))
 | 
						texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/new").Parse("issue/new/subject"))
 | 
				
			||||||
	template.Must(bodyTemplates.New("issue/new").Parse("issue/new/body"))
 | 
						texttmpl.Must(LoadedTemplates().SubjectTemplates.New("pull/comment").Parse("pull/comment/subject"))
 | 
				
			||||||
	template.Must(bodyTemplates.New("pull/comment").Parse("pull/comment/body"))
 | 
						texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/close").Parse("")) // Must default to a fallback subject
 | 
				
			||||||
	template.Must(bodyTemplates.New("issue/close").Parse("issue/close/body"))
 | 
						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) {
 | 
						expect := func(t *testing.T, msg *sender_service.Message, expSubject, expBody string) {
 | 
				
			||||||
		subject := msg.ToMessage().GetGenHeader("Subject")
 | 
							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,
 | 
						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,
 | 
							actionType activities_model.ActionType, fromMention bool, tplSubject, tplBody, expSubject, expBody string,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		subjectTemplates = texttmpl.Must(texttmpl.New("issue/default").Parse(tplSubject))
 | 
							prepareMailTemplates("issue/default", tplSubject, tplBody)
 | 
				
			||||||
		bodyTemplates = template.Must(template.New("issue/default").Parse(tplBody))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
 | 
							recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
 | 
				
			||||||
		msg := testComposeIssueCommentMessage(t, &mailComment{
 | 
							msg := testComposeIssueCommentMessage(t, &mailComment{
 | 
				
			||||||
			Issue: issue, Doer: doer, ActionType: actionType,
 | 
								Issue: issue, Doer: doer, ActionType: actionType,
 | 
				
			||||||
@@ -512,8 +513,7 @@ func TestEmbedBase64Images(t *testing.T) {
 | 
				
			|||||||
	att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64)
 | 
						att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("ComposeMessage", func(t *testing.T) {
 | 
						t.Run("ComposeMessage", func(t *testing.T) {
 | 
				
			||||||
		subjectTemplates = texttmpl.Must(texttmpl.New("issue/new").Parse(subjectTpl))
 | 
							prepareMailTemplates("issue/new", subjectTpl, bodyTpl)
 | 
				
			||||||
		bodyTemplates = template.Must(template.New("issue/new").Parse(bodyTpl))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		issue.Content = fmt.Sprintf(`MSG-BEFORE <image src="attachments/%s"> MSG-AFTER`, att1.UUID)
 | 
							issue.Content = fmt.Sprintf(`MSG-BEFORE <image src="attachments/%s"> MSG-AFTER`, att1.UUID)
 | 
				
			||||||
		require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content"))
 | 
							require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,7 +39,7 @@ func sendUserMail(language string, u *user_model.User, tpl templates.TplName, co
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var content bytes.Buffer
 | 
						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)
 | 
							log.Error("Template: %v", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -90,7 +90,7 @@ func SendActivateEmailMail(u *user_model.User, email string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var content bytes.Buffer
 | 
						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)
 | 
							log.Error("Template: %v", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -118,7 +118,7 @@ func SendRegisterNotifyMail(u *user_model.User) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var content bytes.Buffer
 | 
						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)
 | 
							log.Error("Template: %v", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -149,7 +149,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var content bytes.Buffer
 | 
						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)
 | 
							log.Error("Template: %v", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@ func NewContext(ctx context.Context) {
 | 
				
			|||||||
		sender = &sender_service.SMTPSender{}
 | 
							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 {
 | 
						mailQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "mail", func(items ...*sender_service.Message) []*sender_service.Message {
 | 
				
			||||||
		for _, msg := range items {
 | 
							for _, msg := range items {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,2 +1,3 @@
 | 
				
			|||||||
{{template "base/head" ctx.RootData}}
 | 
					{{template "base/head" ctx.RootData}}
 | 
				
			||||||
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}">
 | 
					<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}">
 | 
				
			||||||
 | 
					{{template "base/alert" .}}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										27
									
								
								templates/devtest/mail-preview.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								templates/devtest/mail-preview.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					{{template "devtest/devtest-header"}}
 | 
				
			||||||
 | 
					<div class="page-content devtest ui container">
 | 
				
			||||||
 | 
						<div class="flex-text-block tw-flex-wrap">
 | 
				
			||||||
 | 
							{{range $templateName := .MailTemplateNames}}
 | 
				
			||||||
 | 
								<a class="ui button" href="?tmpl={{$templateName}}">{{$templateName}}</a>
 | 
				
			||||||
 | 
							{{else}}
 | 
				
			||||||
 | 
								<p>Mailer service is not enabled or no template is found</p>
 | 
				
			||||||
 | 
							{{end}}
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{{if .RenderMailTemplateName}}
 | 
				
			||||||
 | 
						<div class="tw-my-2">
 | 
				
			||||||
 | 
							<div>Preview of: {{.RenderMailTemplateName}}</div>
 | 
				
			||||||
 | 
							<div>Subject: {{.RenderMailSubject}}</div>
 | 
				
			||||||
 | 
							<iframe src="{{AppSubUrl}}/devtest/mail-preview/{{.RenderMailTemplateName}}" class="mail-preview-body"></iframe>
 | 
				
			||||||
 | 
							<style>
 | 
				
			||||||
 | 
								.mail-preview-body {
 | 
				
			||||||
 | 
									border: 1px solid #ccc;
 | 
				
			||||||
 | 
									width: 100%;
 | 
				
			||||||
 | 
									height: 400px;
 | 
				
			||||||
 | 
									overflow: auto;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							</style>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						{{end}}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{template "devtest/devtest-footer"}}
 | 
				
			||||||
							
								
								
									
										3
									
								
								templates/mail/auth/activate.mock.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								templates/mail/auth/activate.mock.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					DisplayName: User Display Name
 | 
				
			||||||
 | 
					Code: The-Activation-Code
 | 
				
			||||||
 | 
					ActiveCodeLives: 24h
 | 
				
			||||||
		Reference in New Issue
	
	Block a user