Migrate from templates to Gomponents (#103)

This commit is contained in:
Mike Stefanello 2025-03-05 20:01:58 -05:00 committed by GitHub
parent 0bf9ab7189
commit 051d032038
104 changed files with 2768 additions and 2824 deletions

View file

@ -1,11 +1,12 @@
package services
import (
"bytes"
"errors"
"fmt"
"github.com/mikestefanello/pagoda/config"
"github.com/mikestefanello/pagoda/pkg/log"
"maragu.dev/gomponents"
"github.com/labstack/echo/v4"
)
@ -14,36 +15,31 @@ type (
// MailClient provides a client for sending email
// This is purposely not completed because there are many different methods and services
// for sending email, many of which are very different. Choose what works best for you
// and populate the methods below
// and populate the methods below. For now, emails will just be logged.
MailClient struct {
// config stores application configuration
// config stores application configuration.
config *config.Config
// templates stores the template renderer
templates *TemplateRenderer
}
// mail represents an email to be sent
// mail represents an email to be sent.
mail struct {
client *MailClient
from string
to string
subject string
body string
template string
templateData any
client *MailClient
from string
to string
subject string
body string
component gomponents.Node
}
)
// NewMailClient creates a new MailClient
func NewMailClient(cfg *config.Config, templates *TemplateRenderer) (*MailClient, error) {
// NewMailClient creates a new MailClient.
func NewMailClient(cfg *config.Config) (*MailClient, error) {
return &MailClient{
config: cfg,
templates: templates,
config: cfg,
}, nil
}
// Compose creates a new email
// Compose creates a new email.
func (m *MailClient) Compose() *mail {
return &mail{
client: m,
@ -51,39 +47,33 @@ func (m *MailClient) Compose() *mail {
}
}
// skipSend determines if mail sending should be skipped
// skipSend determines if mail sending should be skipped.
func (m *MailClient) skipSend() bool {
return m.config.App.Environment != config.EnvProduction
}
// send attempts to send the email
// send attempts to send the email.
func (m *MailClient) send(email *mail, ctx echo.Context) error {
switch {
case email.to == "":
return errors.New("email cannot be sent without a to address")
case email.body == "" && email.template == "":
return errors.New("email cannot be sent without a body or template")
case email.body == "" && email.component == nil:
return errors.New("email cannot be sent without a body or component to render")
}
// Check if a template was supplied
if email.template != "" {
// Parse and execute template
buf, err := m.templates.
Parse().
Group("mail").
Key(email.template).
Base(email.template).
Files(fmt.Sprintf("emails/%s", email.template)).
Execute(email.templateData)
if err != nil {
// Check if a component was supplied.
if email.component != nil {
// Render the component and use as the body.
// TODO pool the buffers?
buf := bytes.NewBuffer(nil)
if err := email.component.Render(buf); err != nil {
return err
}
email.body = buf.String()
}
// Check if mail sending should be skipped
// Check if mail sending should be skipped.
if m.skipSend() {
log.Ctx(ctx).Debug("skipping email delivery",
"to", email.to,
@ -91,52 +81,47 @@ func (m *MailClient) send(email *mail, ctx echo.Context) error {
return nil
}
// TODO: Finish based on your mail sender of choice!
// TODO: Finish based on your mail sender of choice or stop logging below!
log.Ctx(ctx).Info("sending email",
"to", email.to,
"subject", email.subject,
"body", email.body,
)
return nil
}
// From sets the email from address
// From sets the email from address.
func (m *mail) From(from string) *mail {
m.from = from
return m
}
// To sets the email address this email will be sent to
// To sets the email address this email will be sent to.
func (m *mail) To(to string) *mail {
m.to = to
return m
}
// Subject sets the subject line of the email
// Subject sets the subject line of the email.
func (m *mail) Subject(subject string) *mail {
m.subject = subject
return m
}
// Body sets the body of the email
// This is not required and will be ignored if a template via Template()
// Body sets the body of the email.
// This is not required and will be ignored if a component is set via Component().
func (m *mail) Body(body string) *mail {
m.body = body
return m
}
// Template sets the template to be used to produce the body of the email
// The template name should only include the filename without the extension or directory.
// The template must reside within the emails sub-directory.
// The funcmap will be automatically added to the template.
// Use TemplateData() to supply the data that will be passed in to the template.
func (m *mail) Template(template string) *mail {
m.template = template
// Component sets a renderable component to use as the body of the email.
func (m *mail) Component(component gomponents.Node) *mail {
m.component = component
return m
}
// TemplateData sets the data that will be passed to the template specified when calling Template()
func (m *mail) TemplateData(data any) *mail {
m.templateData = data
return m
}
// Send attempts to send the email
// Send attempts to send the email.
func (m *mail) Send(ctx echo.Context) error {
return m.client.send(m, ctx)
}