Extract email presentation configuration layer to streamline email branding

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-01-30 17:31:26 +04:00
parent b18ebe96f8
commit e13ff134ed
17 changed files with 600 additions and 331 deletions

View File

@@ -19,7 +19,6 @@ import (
"encoding/json"
"errors"
"fmt"
"net/url"
"time"
"go.gearno.de/kit/log"
@@ -478,36 +477,6 @@ func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Co
return fmt.Errorf("cannot load organization: %w", err)
}
baseURLParsed, err := url.Parse(s.svc.baseURL)
if err != nil {
return fmt.Errorf("cannot parse base URL: %w", err)
}
hostname := baseURLParsed.Host
scheme := baseURLParsed.Scheme
path := "/trust/" + trustCenter.Slug
if organization.CustomDomainID != nil {
customDomain, err := s.svc.Organizations.GetOrganizationCustomDomain(ctx, organization.ID)
if err != nil {
return fmt.Errorf("cannot load custom domain: %w", err)
}
if customDomain == nil || customDomain.SSLStatus != coredata.CustomDomainSSLStatusActive {
return fmt.Errorf("custom domain is not active")
}
hostname = customDomain.Domain
scheme = "https"
path = ""
}
accessURL := url.URL{
Scheme: scheme,
Host: hostname,
Path: path,
}
now := time.Now()
access.UpdatedAt = now
@@ -515,30 +484,21 @@ func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Co
return fmt.Errorf("cannot update trust center access with expiration: %w", err)
}
return s.sendTrustCenterAccessEmail(ctx, tx, access.Name, access.Email, organization.Name, accessURL.String())
}
emailPresenterCfg, err := s.svc.TrustCenters.EmailPresenterConfig(ctx, trustCenter.ID)
if err != nil {
return fmt.Errorf("cannot get compliance page email presenter config: %w", err)
}
func (s *TrustCenterAccessService) sendTrustCenterAccessEmail(
ctx context.Context,
tx pg.Conn,
name string,
email mail.Addr,
companyName string,
accessURL string,
) error {
subject, textBody, htmlBody, err := emails.RenderTrustCenterAccess(
s.svc.baseURL,
name,
companyName,
accessURL,
)
emailPresenter := emails.NewPresenterFromConfig(emailPresenterCfg, access.Name)
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterAccess(organization.Name)
if err != nil {
return fmt.Errorf("cannot render trust center access email: %w", err)
}
accessEmail := coredata.NewEmail(
name,
email,
access.Name,
access.Email,
subject,
textBody,
htmlBody,
@@ -648,11 +608,16 @@ func (s *TrustCenterAccessService) sendDocumentAccessRejectedEmail(
}
}
subject, textBody, htmlBody, err := emails.RenderTrustCenterDocumentAccessRejected(
s.svc.baseURL,
access.Name,
organization.Name,
emailPresenterCfg, err := s.svc.TrustCenters.EmailPresenterConfig(ctx, trustCenter.ID)
if err != nil {
return fmt.Errorf("cannot get compliance page email presenter config: %w", err)
}
emailPresenter := emails.NewPresenterFromConfig(emailPresenterCfg, access.Name)
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterDocumentAccessRejected(
fileNames,
organization.Name,
)
if err != nil {
return fmt.Errorf("cannot render trust center documents access rejected email: %w", err)

View File

@@ -16,10 +16,13 @@ package trust
import (
"context"
"errors"
"fmt"
"net/url"
"time"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/packages/emails"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
)
@@ -216,3 +219,92 @@ func (s TrustCenterService) GenerateDarkLogoURL(
return &presignedURL, nil
}
func (s *TrustCenterService) EmailPresenterConfig(ctx context.Context, compliancePageID gid.GID) (emails.PresenterConfig, error) {
var (
compliancePage = &coredata.TrustCenter{}
organization = &coredata.Organization{}
customDomain *coredata.CustomDomain
logoFile = &coredata.File{}
emailPresenterCfg = emails.DefaultPresenterConfig(s.svc.baseURL)
)
scope := coredata.NewScopeFromObjectID(compliancePageID)
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
return fmt.Errorf("cannot load compliance page: %w", err)
}
if compliancePage.LogoFileID != nil {
if err := logoFile.LoadByID(ctx, conn, scope, *compliancePage.LogoFileID); err != nil {
return fmt.Errorf("cannot load logoFile: %w", err)
}
}
if err := organization.LoadByID(ctx, conn, scope, compliancePage.OrganizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err)
}
customDomain = &coredata.CustomDomain{}
if err := customDomain.LoadByOrganizationID(ctx, conn, scope, s.svc.encryptionKey, organization.ID); err != nil {
if !errors.Is(err, coredata.ErrResourceNotFound) {
return fmt.Errorf("cannot load custom domain: %w", err)
}
}
return nil
},
)
if err != nil {
return emailPresenterCfg, err
}
parsedBaseURL, err := url.Parse(s.svc.baseURL)
if err != nil {
return emailPresenterCfg, fmt.Errorf("cannot parse base URL: %w", err)
}
baseURL := url.URL{
Scheme: parsedBaseURL.Scheme,
Host: parsedBaseURL.Host,
Path: "/trust/" + compliancePage.Slug,
}
if customDomain != nil && customDomain.SSLStatus == coredata.CustomDomainSSLStatusActive {
baseURL.Host = customDomain.Domain
baseURL.Scheme = "https"
baseURL.Path = ""
}
emailPresenterCfg.BaseURL = baseURL.String()
if compliancePage.LogoFileID != nil {
if logoFile.FileKey == "" {
return emailPresenterCfg, nil
}
// If logo exists, then we will brand the emails with the org as a sender
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, logoFile, 1*time.Hour)
if err != nil {
return emailPresenterCfg, fmt.Errorf("cannot generate file URL: %w", err)
}
emailPresenterCfg.SenderCompanyLogoURL = presignedURL
emailPresenterCfg.SenderCompanyName = organization.Name
if organization.WebsiteURL != nil {
emailPresenterCfg.SenderCompanyWebsiteURL = *organization.WebsiteURL
}
if organization.HeadquarterAddress != nil {
emailPresenterCfg.SenderCompanyHeadquarterAddress = *organization.HeadquarterAddress
}
}
return emailPresenterCfg, nil
}