thread-master/apps/backend/internal/module/notification/usecase/service.go

109 lines
2.7 KiB
Go
Raw Normal View History

2026-07-10 12:54:45 +00:00
package usecase
import (
"bytes"
"context"
"fmt"
"html/template"
"strings"
"apps/backend/internal/module/notification/domain"
"github.com/zeromicro/go-zero/core/logx"
)
// Config for notification service.
type Config struct {
Sender string
DevExposeCode bool
Brand domain.Brand
Providers []domain.EmailDelivery
}
// Service implements domain.UseCase.
type Service struct {
cfg Config
}
func NewService(cfg Config) *Service {
if cfg.Sender == "" {
cfg.Sender = "Harbor Desk <noreply@haixun.local>"
}
if cfg.Brand.Name == "" {
cfg.Brand = domain.DefaultBrand(cfg.Brand.Link)
}
if len(cfg.Providers) == 0 {
cfg.Providers = []domain.EmailDelivery{NewLogDelivery()}
cfg.DevExposeCode = true
}
return &Service{cfg: cfg}
}
func (s *Service) SenderFrom() string { return s.cfg.Sender }
func (s *Service) ExposeCodeInAPI() bool { return s.cfg.DevExposeCode }
func (s *Service) Brand() domain.Brand { return s.cfg.Brand }
func (s *Service) Enabled() bool {
for _, p := range s.cfg.Providers {
if p.Name() != "log" {
return true
}
}
return false
}
func (s *Service) GetEmailTemplate(kind domain.TemplateKind, lang domain.Language) (*domain.EmailTemplate, error) {
lang = domain.NormalizeLanguage(string(lang))
return domain.GetTemplate(kind, lang, s.cfg.Brand)
}
func (s *Service) RenderVerify(kind domain.TemplateKind, lang domain.Language, vars domain.VerifyTemplateVars) (string, string, error) {
lang = domain.NormalizeLanguage(string(lang))
tpl, err := s.GetEmailTemplate(kind, lang)
if err != nil {
return "", "", err
}
if vars.Username == "" {
vars.Username = "there"
}
// hermes may leave empty ResetURL button — still OK if placeholder replaced
t, err := template.New(string(kind)).Parse(tpl.Body)
if err != nil {
return "", "", err
}
var buf bytes.Buffer
if err := t.Execute(&buf, vars); err != nil {
return "", "", err
}
return tpl.Subject, buf.String(), nil
}
func (s *Service) SendEmail(ctx context.Context, req domain.EmailRequest) error {
if req.SenderEmail == "" {
req.SenderEmail = s.cfg.Sender
}
var last error
for _, p := range s.cfg.Providers {
if err := p.SendEmail(ctx, req); err != nil {
logx.WithContext(ctx).Errorf("notification send via %s failed: %v", p.Name(), err)
last = err
continue
}
logx.WithContext(ctx).Infof("notification sent via %s to %s subject=%q", p.Name(), maskEmail(req.RecipientEmail), req.Subject)
return nil
}
if last != nil {
return last
}
return fmt.Errorf("no email providers configured")
}
func maskEmail(e string) string {
e = strings.TrimSpace(e)
at := strings.IndexByte(e, '@')
if at <= 1 {
return "***"
}
return e[:1] + "***" + e[at:]
}