mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:01:08 +02:00
mana-search-go → mana-search mana-notify-go → mana-notify mana-crawler-go → mana-crawler mana-api-gateway-go → mana-api-gateway Legacy NestJS versions are deleted, suffix no longer needed. Updated all references in docker-compose, CLAUDE.md, package.json, Forgejo workflows, and service package.json files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
138 lines
4.2 KiB
Go
138 lines
4.2 KiB
Go
package template
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"log/slog"
|
|
"text/template"
|
|
|
|
"github.com/manacore/mana-notify/internal/db"
|
|
)
|
|
|
|
type Engine struct {
|
|
db *db.DB
|
|
}
|
|
|
|
func NewEngine(database *db.DB) *Engine {
|
|
return &Engine{db: database}
|
|
}
|
|
|
|
type Rendered struct {
|
|
Subject string
|
|
Body string
|
|
}
|
|
|
|
// RenderBySlug looks up a template by slug and renders it with the given data.
|
|
func (e *Engine) RenderBySlug(ctx context.Context, slug string, data map[string]any, locale string) (*Rendered, error) {
|
|
if locale == "" {
|
|
locale = "de-DE"
|
|
}
|
|
|
|
var subject, bodyTemplate *string
|
|
err := e.db.Pool.QueryRow(ctx,
|
|
`SELECT subject, body_template FROM notify.templates WHERE slug = $1 AND locale = $2 AND is_active = true`,
|
|
slug, locale,
|
|
).Scan(&subject, &bodyTemplate)
|
|
|
|
if err != nil {
|
|
// Try default locale fallback
|
|
err = e.db.Pool.QueryRow(ctx,
|
|
`SELECT subject, body_template FROM notify.templates WHERE slug = $1 AND is_active = true ORDER BY locale LIMIT 1`,
|
|
slug,
|
|
).Scan(&subject, &bodyTemplate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
rendered := &Rendered{}
|
|
|
|
if subject != nil {
|
|
s, err := renderTemplate(*subject, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rendered.Subject = s
|
|
}
|
|
|
|
if bodyTemplate != nil {
|
|
b, err := renderTemplate(*bodyTemplate, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rendered.Body = b
|
|
}
|
|
|
|
return rendered, nil
|
|
}
|
|
|
|
// RenderDirect renders a template string with data.
|
|
func RenderDirect(tmplStr string, data map[string]any) (string, error) {
|
|
return renderTemplate(tmplStr, data)
|
|
}
|
|
|
|
func renderTemplate(tmplStr string, data map[string]any) (string, error) {
|
|
tmpl, err := template.New("").Parse(tmplStr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, data); err != nil {
|
|
return "", err
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
// SeedDefaults inserts default system templates if they don't exist.
|
|
func (e *Engine) SeedDefaults(ctx context.Context) {
|
|
defaults := []struct {
|
|
slug string
|
|
channel string
|
|
subject string
|
|
body string
|
|
vars string
|
|
}{
|
|
{
|
|
slug: "auth-password-reset",
|
|
channel: "email",
|
|
subject: "Passwort zurücksetzen - ManaCore",
|
|
body: `<!DOCTYPE html><html><body><h1>Passwort zurücksetzen</h1><p>Hallo {{.userName}},</p><p>Klicke auf den folgenden Link, um dein Passwort zurückzusetzen:</p><p><a href="{{.resetUrl}}">Passwort zurücksetzen</a></p><p>Falls du diese Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren.</p></body></html>`,
|
|
vars: `{"resetUrl": "URL zum Zurücksetzen", "userName": "Name des Benutzers"}`,
|
|
},
|
|
{
|
|
slug: "auth-verification",
|
|
channel: "email",
|
|
subject: "E-Mail bestätigen - ManaCore",
|
|
body: `<!DOCTYPE html><html><body><h1>E-Mail bestätigen</h1><p>Hallo {{.userName}},</p><p>Bitte bestätige deine E-Mail-Adresse:</p><p><a href="{{.verificationUrl}}">E-Mail bestätigen</a></p></body></html>`,
|
|
vars: `{"verificationUrl": "Bestätigungs-URL", "userName": "Name des Benutzers"}`,
|
|
},
|
|
{
|
|
slug: "auth-welcome",
|
|
channel: "email",
|
|
subject: "Willkommen bei ManaCore!",
|
|
body: `<!DOCTYPE html><html><body><h1>Willkommen!</h1><p>Hallo {{.userName}},</p><p>Willkommen bei ManaCore! Du kannst dich jetzt anmelden:</p><p><a href="{{.loginUrl}}">Anmelden</a></p></body></html>`,
|
|
vars: `{"userName": "Name des Benutzers", "loginUrl": "Login-URL"}`,
|
|
},
|
|
{
|
|
slug: "calendar-reminder",
|
|
channel: "email",
|
|
subject: "Erinnerung: {{.eventTitle}}",
|
|
body: `<!DOCTYPE html><html><body><h1>{{.eventTitle}}</h1><p>Wann: {{.eventTime}}</p>{{if .eventLocation}}<p>Wo: {{.eventLocation}}</p>{{end}}<p><a href="{{.eventUrl}}">Termin anzeigen</a></p></body></html>`,
|
|
vars: `{"eventTitle": "Titel", "eventTime": "Zeit", "eventLocation": "Ort (optional)", "eventUrl": "Link"}`,
|
|
},
|
|
}
|
|
|
|
for _, d := range defaults {
|
|
_, err := e.db.Pool.Exec(ctx,
|
|
`INSERT INTO notify.templates (slug, channel, subject, body_template, locale, is_system, variables)
|
|
VALUES ($1, $2, $3, $4, 'de-DE', true, $5)
|
|
ON CONFLICT (slug, locale) DO NOTHING`,
|
|
d.slug, d.channel, d.subject, d.body, d.vars,
|
|
)
|
|
if err != nil {
|
|
slog.Warn("seed template failed", "slug", d.slug, "error", err)
|
|
}
|
|
}
|
|
|
|
slog.Info("default templates seeded")
|
|
}
|