emailer.go

70 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
package application

import (
	"bytes"
	"embed"
	"errors"
	"fmt"
	"html/template"
	"io/fs"
	"log"
	"path/filepath"
	"strings"
)

// Emailer is the interface for sending templated emails
type Emailer interface {
	Send(to, subject, templateName string, data map[string]any) error
}

// BaseEmailer provides common email template rendering functionality.
// Embed this in your emailer implementations.
type BaseEmailer struct {
	emails *template.Template
}

// Init initializes the base emailer with templates.
// Call this in your emailer's constructor.
func (b *BaseEmailer) Init(emails embed.FS, funcs template.FuncMap) {
	b.emails = template.New("")
	if funcs != nil {
		b.emails = b.emails.Funcs(funcs)
	}

	err := fs.WalkDir(emails, "emails", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() || !strings.HasSuffix(path, ".html") {
			return nil
		}

		content, err := emails.ReadFile(path)
		if err != nil {
			return err
		}

		name := filepath.Base(path)
		_, err = b.emails.New(name).Parse(string(content))
		return err
	})

	if err != nil {
		log.Printf("Warning: failed to parse email templates: %v", err)
		b.emails = template.New("")
	}
}

// Render renders an email template to a string
func (b *BaseEmailer) Render(name string, data map[string]any) (string, error) {
	if b.emails == nil {
		return "", errors.New("email templates not initialized")
	}

	var buf bytes.Buffer
	if err := b.emails.ExecuteTemplate(&buf, name, data); err != nil {
		return "", fmt.Errorf("execute template %s: %w", name, err)
	}

	return buf.String(), nil
}