109 lines
2.7 KiB
Go
109 lines
2.7 KiB
Go
|
|
package template
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"fmt"
|
||
|
|
htmltemplate "html/template"
|
||
|
|
"strings"
|
||
|
|
texttemplate "text/template"
|
||
|
|
|
||
|
|
"gateway/internal/model/notification/domain/enum"
|
||
|
|
domtpl "gateway/internal/model/notification/domain/template"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Renderer renders notification templates from a registry.
|
||
|
|
type Renderer struct {
|
||
|
|
registry domtpl.Registry
|
||
|
|
fallbacks []string
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewRenderer builds a Renderer. Implements domain/template.Renderer.
|
||
|
|
func NewRenderer(registry domtpl.Registry, fallbacks ...string) *Renderer {
|
||
|
|
if registry == nil {
|
||
|
|
registry = DefaultRegistry()
|
||
|
|
}
|
||
|
|
return &Renderer{
|
||
|
|
registry: registry,
|
||
|
|
fallbacks: fallbacks,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Render produces subject/body/sms text for the given kind and locale.
|
||
|
|
func (r *Renderer) Render(kind enum.NotifyKind, locale string, data map[string]any) (*domtpl.Rendered, error) {
|
||
|
|
spec, err := r.registry.Lookup(kind, locale, r.fallbacks...)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := validateRequiredVars(spec.RequiredVars, data); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
subjectTpl, bodyTpl, smsTpl, err := resolveSpec(spec)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
out := &domtpl.Rendered{}
|
||
|
|
if subjectTpl != "" {
|
||
|
|
subject, err := executeTextTemplate("subject", subjectTpl, data)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("template: render email subject: %w", err)
|
||
|
|
}
|
||
|
|
out.Subject = subject
|
||
|
|
}
|
||
|
|
if bodyTpl != "" {
|
||
|
|
body, err := executeHTMLTemplate("body", bodyTpl, data)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("template: render email body: %w", err)
|
||
|
|
}
|
||
|
|
out.Body = body
|
||
|
|
}
|
||
|
|
if smsTpl != "" {
|
||
|
|
sms, err := executeTextTemplate("sms", smsTpl, data)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("template: render sms: %w", err)
|
||
|
|
}
|
||
|
|
out.SMSText = sms
|
||
|
|
}
|
||
|
|
|
||
|
|
if out.Subject == "" && out.Body == "" && out.SMSText == "" {
|
||
|
|
return nil, fmt.Errorf("template: kind %q has no renderable fields for locale %q", kind, locale)
|
||
|
|
}
|
||
|
|
|
||
|
|
return out, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func validateRequiredVars(required []string, data map[string]any) error {
|
||
|
|
for _, key := range required {
|
||
|
|
if _, ok := data[key]; !ok {
|
||
|
|
return fmt.Errorf("template: missing required variable %q", key)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func executeTextTemplate(name, tpl string, data map[string]any) (string, error) {
|
||
|
|
t, err := texttemplate.New(name).Parse(tpl)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
var buf bytes.Buffer
|
||
|
|
if err := t.Execute(&buf, data); err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
return strings.TrimSpace(buf.String()), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func executeHTMLTemplate(name, tpl string, data map[string]any) (string, error) {
|
||
|
|
t, err := htmltemplate.New(name).Parse(tpl)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
var buf bytes.Buffer
|
||
|
|
if err := t.Execute(&buf, data); err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
return strings.TrimSpace(buf.String()), nil
|
||
|
|
}
|