1
0
mirror of https://github.com/muety/wakapi.git synced 2023-08-10 21:12:56 +03:00

refactor: mail service abstraction layer

This commit is contained in:
Ferdinand Mütsch
2021-04-30 15:14:29 +02:00
parent 29c04c3ac5
commit a4e7158db2
10 changed files with 128 additions and 144 deletions

View File

@ -5,11 +5,12 @@ import (
"fmt"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/routes"
"github.com/muety/wakapi/services"
"html/template"
"io/ioutil"
"time"
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/views"
)
@ -22,31 +23,76 @@ const (
subjectReport = "Wakapi Your Latest Report"
)
type PasswordResetTplData struct {
ResetLink string
type SendingService interface {
Send(*models.Mail) error
}
type ImportNotificationTplData struct {
PublicUrl string
Duration string
NumHeartbeats int
type MailService struct {
config *conf.Config
sendingService SendingService
}
type ReportTplData struct {
Report *models.Report
}
// Factory
func NewMailService() services.IMailService {
config := conf.Get()
var sendingService SendingService
sendingService = &NoopSendingService{}
if config.Mail.Enabled {
if config.Mail.Provider == conf.MailProviderMailWhale {
return NewMailWhaleService(config.Mail.MailWhale, config.Server.PublicUrl)
sendingService = NewMailWhaleSendingService(config.Mail.MailWhale)
} else if config.Mail.Provider == conf.MailProviderSmtp {
return NewSMTPMailService(config.Mail.Smtp, config.Server.PublicUrl)
sendingService = NewSMTPSendingService(config.Mail.Smtp)
}
}
return &NoopMailService{}
return &MailService{sendingService: sendingService, config: config}
}
func (m *MailService) SendPasswordReset(recipient *models.User, resetLink string) error {
tpl, err := getPasswordResetTemplate(PasswordResetTplData{ResetLink: resetLink})
if err != nil {
return err
}
mail := &models.Mail{
From: models.MailAddress(m.config.Mail.Sender),
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
Subject: subjectPasswordReset,
}
mail.WithHTML(tpl.String())
return m.sendingService.Send(mail)
}
func (m *MailService) SendImportNotification(recipient *models.User, duration time.Duration, numHeartbeats int) error {
tpl, err := getImportNotificationTemplate(ImportNotificationTplData{
PublicUrl: m.config.Server.PublicUrl,
Duration: fmt.Sprintf("%.0f seconds", duration.Seconds()),
NumHeartbeats: numHeartbeats,
})
if err != nil {
return err
}
mail := &models.Mail{
From: models.MailAddress(m.config.Mail.Sender),
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
Subject: subjectImportNotification,
}
mail.WithHTML(tpl.String())
return m.sendingService.Send(mail)
}
func (m *MailService) SendReport(recipient *models.User, report *models.Report) error {
tpl, err := getReportTemplate(ReportTplData{report})
if err != nil {
return err
}
mail := &models.Mail{
From: models.MailAddress(m.config.Mail.Sender),
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
Subject: subjectReport,
}
mail.WithHTML(tpl.String())
return m.sendingService.Send(mail)
}
func getPasswordResetTemplate(data PasswordResetTplData) (*bytes.Buffer, error) {

View File

@ -11,8 +11,7 @@ import (
"time"
)
type MailWhaleMailService struct {
publicUrl string
type MailWhaleSendingService struct {
config conf.MailwhaleMailConfig
httpClient *http.Client
}
@ -26,57 +25,28 @@ type MailWhaleSendRequest struct {
TemplateVars map[string]string `json:"template_vars"`
}
func NewMailWhaleService(config conf.MailwhaleMailConfig, publicUrl string) *MailWhaleMailService {
return &MailWhaleMailService{
publicUrl: publicUrl,
config: config,
func NewMailWhaleSendingService(config conf.MailwhaleMailConfig) *MailWhaleSendingService {
return &MailWhaleSendingService{
config: config,
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
}
}
func (s *MailWhaleMailService) SendPasswordReset(recipient *models.User, resetLink string) error {
template, err := getPasswordResetTemplate(PasswordResetTplData{ResetLink: resetLink})
if err != nil {
return err
}
return s.send(recipient.Email, subjectPasswordReset, template.String(), true)
}
func (s *MailWhaleMailService) SendImportNotification(recipient *models.User, duration time.Duration, numHeartbeats int) error {
template, err := getImportNotificationTemplate(ImportNotificationTplData{
PublicUrl: s.publicUrl,
Duration: fmt.Sprintf("%.0f seconds", duration.Seconds()),
NumHeartbeats: numHeartbeats,
})
if err != nil {
return err
}
return s.send(recipient.Email, subjectImportNotification, template.String(), true)
}
func (s *MailWhaleMailService) SendReport(recipient *models.User, report *models.Report) error {
template, err := getReportTemplate(ReportTplData{report})
if err != nil {
return err
}
return s.send(recipient.Email, subjectReport, template.String(), true)
}
func (s *MailWhaleMailService) send(to, subject, body string, isHtml bool) error {
if to == "" {
func (s *MailWhaleSendingService) Send(mail *models.Mail) error {
if len(mail.To) == 0 {
return errors.New("not sending mail as recipient mail address seems to be invalid")
}
sendRequest := &MailWhaleSendRequest{
To: []string{to},
Subject: subject,
To: mail.To.Strings(),
Subject: mail.Subject,
}
if isHtml {
sendRequest.Html = body
if mail.Type == models.HtmlType {
sendRequest.Html = mail.Body
} else {
sendRequest.Text = body
sendRequest.Text = mail.Body
}
payload, _ := json.Marshal(sendRequest)

View File

@ -3,24 +3,11 @@ package mail
import (
"github.com/emvi/logbuch"
"github.com/muety/wakapi/models"
"time"
)
type NoopMailService struct{}
type NoopSendingService struct{}
const notImplemented = "noop mail service doing nothing instead of sending password reset mail to %s"
func (n *NoopMailService) SendReport(recipient *models.User, report *models.Report) error {
logbuch.Info(notImplemented, recipient.ID)
return nil
}
func (n *NoopMailService) SendPasswordReset(recipient *models.User, resetLink string) error {
logbuch.Info(notImplemented, recipient.ID)
return nil
}
func (n *NoopMailService) SendImportNotification(recipient *models.User, duration time.Duration, numHeartbeats int) error {
logbuch.Info(notImplemented, recipient.ID)
func (n *NoopSendingService) Send(mail *models.Mail) error {
logbuch.Info("noop mail service doing nothing instead of sending password reset mail to [%v]", mail.To.Strings())
return nil
}

View File

@ -2,29 +2,21 @@ package mail
import (
"errors"
"fmt"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"io"
"time"
)
type SMTPMailService struct {
publicUrl string
config conf.SMTPMailConfig
auth sasl.Client
type SMTPSendingService struct {
config conf.SMTPMailConfig
auth sasl.Client
}
func (s *SMTPMailService) SendReport(recipient *models.User, report *models.Report) error {
panic("implement me") // TODO
}
func NewSMTPMailService(config conf.SMTPMailConfig, publicUrl string) *SMTPMailService {
return &SMTPMailService{
publicUrl: publicUrl,
config: config,
func NewSMTPSendingService(config conf.SMTPMailConfig) *SMTPSendingService {
return &SMTPSendingService{
config: config,
auth: sasl.NewPlainClient(
"",
config.Username,
@ -33,51 +25,15 @@ func NewSMTPMailService(config conf.SMTPMailConfig, publicUrl string) *SMTPMailS
}
}
func (s *SMTPMailService) SendPasswordReset(recipient *models.User, resetLink string) error {
template, err := getPasswordResetTemplate(PasswordResetTplData{ResetLink: resetLink})
if err != nil {
return err
}
mail := &models.Mail{
From: models.MailAddress(s.config.Sender),
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
Subject: subjectPasswordReset,
}
mail.WithHTML(template.String())
return s.send(s.config.ConnStr(), s.config.TLS, s.auth, mail.From.Raw(), mail.To.RawStrings(), mail.Reader())
}
func (s *SMTPMailService) SendImportNotification(recipient *models.User, duration time.Duration, numHeartbeats int) error {
template, err := getImportNotificationTemplate(ImportNotificationTplData{
PublicUrl: s.publicUrl,
Duration: fmt.Sprintf("%.0f seconds", duration.Seconds()),
NumHeartbeats: numHeartbeats,
})
if err != nil {
return err
}
mail := &models.Mail{
From: models.MailAddress(s.config.Sender),
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
Subject: subjectImportNotification,
}
mail.WithHTML(template.String())
return s.send(s.config.ConnStr(), s.config.TLS, s.auth, mail.From.Raw(), mail.To.RawStrings(), mail.Reader())
}
func (s *SMTPMailService) send(addr string, tls bool, a sasl.Client, from string, to []string, r io.Reader) error {
func (s *SMTPSendingService) Send(mail *models.Mail) error {
dial := smtp.Dial
if tls {
if s.config.TLS {
dial = func(addr string) (*smtp.Client, error) {
return smtp.DialTLS(addr, nil)
}
}
c, err := dial(addr)
c, err := dial(s.config.ConnStr())
if err != nil {
return err
}
@ -89,18 +45,18 @@ func (s *SMTPMailService) send(addr string, tls bool, a sasl.Client, from string
return err
}
}
if a != nil {
if s.auth != nil {
if ok, _ := c.Extension("AUTH"); !ok {
return errors.New("smtp: server doesn't support AUTH")
}
if err = c.Auth(a); err != nil {
if err = c.Auth(s.auth); err != nil {
return err
}
}
if err = c.Mail(from, nil); err != nil {
if err = c.Mail(mail.From.Raw(), nil); err != nil {
return err
}
for _, addr := range to {
for _, addr := range mail.To.RawStrings() {
if err = c.Rcpt(addr); err != nil {
return err
}
@ -109,7 +65,7 @@ func (s *SMTPMailService) send(addr string, tls bool, a sasl.Client, from string
if err != nil {
return err
}
_, err = io.Copy(w, r)
_, err = io.Copy(w, mail.Reader())
if err != nil {
return err
}

17
services/mail/types.go Normal file
View File

@ -0,0 +1,17 @@
package mail
import "github.com/muety/wakapi/models"
type PasswordResetTplData struct {
ResetLink string
}
type ImportNotificationTplData struct {
PublicUrl string
Duration string
NumHeartbeats int
}
type ReportTplData struct {
Report *models.Report
}