Files
EagleCast/internal/notifs/notifs.go
T
h202-wq 66d9a033c9
publish-github-pages / deploy (push) Waiting to run
EagleCast
2026-07-09 10:03:32 -04:00

114 lines
2.8 KiB
Go

// package notifs is a special singleton, stateful globally accessible package
// that handles sending out arbitrary notifications to the admin and users.
// It's initialized once in the main package and is accessed globally across
// other packages.
package notifs
import (
"bytes"
"html/template"
"log"
"net/textproto"
"regexp"
"strings"
"source.offmarket.win/aleagle/eaglecast/internal/messenger/email"
"source.offmarket.win/aleagle/eaglecast/models"
)
const (
TplImport = "import-status"
TplCampaignStatus = "campaign-status"
TplSubscriberOptin = "subscriber-optin"
TplSubscriberData = "subscriber-data"
TplForgotPassword = "forgot-password"
)
type FuncPush func(msg models.Message) error
type FuncNotif func(toEmails []string, subject, tplName string, data any, headers textproto.MIMEHeader) error
type FuncNotifSystem func(subject, tplName string, data any, headers textproto.MIMEHeader) error
type Opt struct {
FromEmail string
SystemEmails []string
ContentType string
}
type Notifs struct {
em *email.Emailer
lo *log.Logger
opt Opt
}
var (
reTitle = regexp.MustCompile(`(?s)<title\s*data-i18n\s*>(.+?)</title>`)
Tpls *template.Template
no *Notifs
)
// Initialize returns a new Notifs instance.
func Initialize(opt Opt, tpls *template.Template, em *email.Emailer, lo *log.Logger) {
if no != nil {
lo.Fatal("notifs already initialized")
}
Tpls = tpls
no = &Notifs{
opt: opt,
em: em,
lo: lo,
}
}
// NotifySystem sends out an e-mail notification to the admin emails.
func NotifySystem(subject, tplName string, data any, hdr textproto.MIMEHeader) error {
return Notify(no.opt.SystemEmails, subject, tplName, data, hdr)
}
// Notify sends out an e-mail notification.
func Notify(toEmails []string, subject, tplName string, data any, hdr textproto.MIMEHeader) error {
if len(toEmails) == 0 {
return nil
}
var buf bytes.Buffer
if err := Tpls.ExecuteTemplate(&buf, tplName, data); err != nil {
no.lo.Printf("error compiling notification template '%s': %v", tplName, err)
return err
}
body := buf.Bytes()
subject, body = GetTplSubject(subject, body)
m := models.Message{
Messenger: "email",
ContentType: no.opt.ContentType,
From: no.opt.FromEmail,
To: toEmails,
Subject: subject,
Body: body,
Headers: hdr,
}
// Send the message.
if err := no.em.Push(m); err != nil {
no.lo.Printf("error sending admin notification (%s): %v", subject, err)
return err
}
return nil
}
// GetTplSubject extracts any custom i18n subject rendered in the given rendered
// template body. If it's not found, the incoming subject and body are returned.
func GetTplSubject(subject string, body []byte) (string, []byte) {
m := reTitle.FindSubmatch(body)
if len(m) != 2 {
return subject, body
}
return strings.TrimSpace(string(m[1])), reTitle.ReplaceAll(body, []byte(""))
}