feat(04-03): register plugin mail layouts and publish the mailer at boot

- Map short layout names onto dotted layout files and wrap text/HTML
- party.Activate publishes postcard.Mailer before Boot and names missing files
- Hello plugin sends through the app-scoped mailer

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-09-18 13:59:52 +02:00
parent b4fd8339fe
commit 1e515971c9
9 changed files with 251 additions and 4 deletions

View File

@@ -16,6 +16,7 @@ import (
"git.golem15.com/golem15/summercms/pact"
"git.golem15.com/golem15/summercms/party"
"git.golem15.com/golem15/summercms/phrasebook"
"git.golem15.com/golem15/summercms/postcard"
"git.golem15.com/golem15/summercms/surf"
)
@@ -142,6 +143,35 @@ func TestHelloPluginResolvesLang(t *testing.T) {
}
}
func TestHelloPluginSendsMail(t *testing.T) {
cfg, err := compass.Load("config")
if err != nil {
t.Fatal(err)
}
app := backpack.New(cfg)
if _, err := party.Activate(app, []string{"golem15.hello"}); err != nil {
t.Fatal(err)
}
mail, ok := app.Lookup[postcard.Mailer]()
if !ok {
t.Fatal("mailer not published")
}
if err := mail.Send(t.Context(), postcard.Message{
Template: "golem15.hello::mail.hello",
To: []string{"ada@example.test"},
Vars: map[string]any{"Name": "Ada", "URL": "https://example.test"},
}); err != nil {
t.Fatal(err)
}
if err := mail.Send(t.Context(), postcard.Message{
Template: "golem15.hello::mail.hello-en",
To: []string{"ada@example.test"},
Vars: map[string]any{"Name": "Ada", "URL": "https://example.test"},
}); err != nil {
t.Fatal(err)
}
}
func TestTypedItemRoute(t *testing.T) {
cfg, err := compass.Load("config")
if err != nil {

View File

@@ -10,8 +10,9 @@ import (
)
var (
_ pact.HasConfig = (*Plugin)(nil)
_ pact.HasLang = (*Plugin)(nil)
_ pact.HasConfig = (*Plugin)(nil)
_ pact.HasLang = (*Plugin)(nil)
_ pact.HasMailTemplates = (*Plugin)(nil)
)
//go:embed config
@@ -20,6 +21,9 @@ var configFS embed.FS
//go:embed lang
var langFS embed.FS
//go:embed views/mail
var mailFS embed.FS
// Plugin is the golem15.hello plugin.
type Plugin struct{}
@@ -32,6 +36,19 @@ func (p *Plugin) Boot(*backpack.App) error { return nil }
func (p *Plugin) ConfigFS() fs.FS { return configFS }
func (p *Plugin) LangFS() fs.FS { return langFS }
func (p *Plugin) MailTemplatesFS() fs.FS { return mailFS }
func (p *Plugin) MailTemplates() []string {
return []string{
"golem15.hello::mail.hello",
"golem15.hello::mail.hello-en",
}
}
func (p *Plugin) MailLayouts() map[string]string {
return map[string]string{
"hello": "golem15.hello::mail.layouts.hello",
}
}
func init() {
party.Register(&Plugin{})
}

View File

@@ -1,6 +1,6 @@
subject = "Hello {{ .Name }}"
description = "English hello greeting"
layout = "default"
layout = "hello"
==
Hello **{{ .Name }}**.

View File

@@ -1,6 +1,6 @@
subject = "Witaj {{ .Name }}"
description = "Hello greeting"
layout = "default"
layout = "hello"
==
Witaj **{{ .Name }}**.

View File

@@ -0,0 +1,14 @@
name = "Hello"
==
-- {{ .Content }} --
==
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">{{ .brandCss }}{{ .css }}</style>
</head>
<body>
<div class="layout-hello">{{ .Content }}</div>
</body>
</html>