Serve brand assets as static files via /api/files/v1/static instead of S3

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-06-09 16:43:59 +02:00
parent c6c35a542e
commit 60435f277b
27 changed files with 125 additions and 241 deletions

View File

@@ -18,59 +18,33 @@ import (
"bytes"
"context"
"embed"
"errors"
"fmt"
htmltemplate "html/template"
"io/fs"
"mime"
"net/url"
"path/filepath"
texttemplate "text/template"
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
"go.probo.inc/probo/pkg/baseurl"
"go.probo.inc/probo/pkg/filemanager"
"go.probo.inc/probo/pkg/filevalidation"
"go.probo.inc/probo/pkg/brand"
)
//go:embed dist
var Templates embed.FS
var (
//go:embed assets
staticAssets embed.FS
staticAssetsValidator = filevalidation.NewValidator(
filevalidation.WithMaxFileSize(5*1024*1024),
filevalidation.WithCategories(
filevalidation.CategoryImage,
filevalidation.CategoryVideo,
),
)
staticAssetsDuration = 7 * 24 * time.Hour
)
type (
Asset struct {
Name string
ObjectKey string
BucketName string
MimeType string
}
PresenterConfig struct {
APIBaseURL string
BaseURL string
PoweredByLogo Asset
PoweredByLogoPath string
SenderCompanyName string
SenderCompanyWebsiteURL string
SenderCompanyLogo Asset
SenderCompanyLogoPath string
SenderCompanyHeadquarterAddress string
}
CommonVariables struct {
// Static variables
APIBaseURL string
BaseURL string
PoweredByLogoURL string
SenderCompanyName string
@@ -83,131 +57,37 @@ type (
}
Presenter struct {
fm *filemanager.Service
config PresenterConfig
RecipientFullName string
}
)
func (a *Asset) GetObjectKey() string {
return a.ObjectKey
}
func (a *Asset) GetName() string {
return a.Name
}
func (a *Asset) GetBucketName() string {
return a.BucketName
}
func (a *Asset) GetMimeType() string {
return a.MimeType
}
var _ filemanager.File = (*Asset)(nil)
func DefaultPresenterConfig(staticAssetsBucket string, baseURL string) PresenterConfig {
func DefaultPresenterConfig(baseURL string) PresenterConfig {
return PresenterConfig{
BaseURL: baseURL,
PoweredByLogo: Asset{
Name: "probo-gray-small.png",
ObjectKey: "probo-gray-small.png",
BucketName: staticAssetsBucket,
MimeType: "image/png",
},
SenderCompanyName: "Probo",
SenderCompanyWebsiteURL: "https://www.getprobo.com",
SenderCompanyLogo: Asset{
Name: "probo.png",
ObjectKey: "probo.png",
BucketName: staticAssetsBucket,
MimeType: "image/png",
},
APIBaseURL: baseURL, // always API base URL
BaseURL: baseURL, // can change to custom domain when needed
PoweredByLogoPath: brand.DefaultPoweredByLogoPath,
SenderCompanyName: "Probo",
SenderCompanyWebsiteURL: "https://www.getprobo.com",
SenderCompanyLogoPath: brand.DefaultSenderCompanyLogoPath,
SenderCompanyHeadquarterAddress: "Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US",
}
}
func NewPresenterFromConfig(fileService *filemanager.Service, cfg PresenterConfig, fullName string) *Presenter {
func NewPresenterFromConfig(cfg PresenterConfig, fullName string) *Presenter {
return &Presenter{
fm: fileService,
config: cfg,
RecipientFullName: fullName,
}
}
func NewPresenter(fileService *filemanager.Service, staticAssetsBucket string, baseURL string, fullName string) *Presenter {
func NewPresenter(baseURL string, fullName string) *Presenter {
return NewPresenterFromConfig(
fileService,
DefaultPresenterConfig(staticAssetsBucket, baseURL),
DefaultPresenterConfig(baseURL),
fullName,
)
}
func UploadStaticAssets(ctx context.Context, s3Client *s3.Client, staticAssetsBucket string) error {
subFS, err := fs.Sub(staticAssets, "assets")
if err != nil {
return fmt.Errorf("cannot create subtree file system: %w", err)
}
err = fs.WalkDir(subFS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
info, err := d.Info()
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil
}
return fmt.Errorf("cannot get dir entry info: %w", err)
}
ext := filepath.Ext(info.Name())
mimeType := mime.TypeByExtension(ext)
if err := staticAssetsValidator.Validate(info.Name(), mimeType, info.Size()); err != nil {
return fmt.Errorf("cannot validate file: %w", err)
}
file, err := subFS.Open(path)
if err != nil {
return err
}
defer func() { _ = file.Close() }()
_, err = s3Client.PutObject(
ctx,
&s3.PutObjectInput{
Bucket: new(staticAssetsBucket),
Key: new(path),
Body: file,
Metadata: map[string]string{
"type": "static-email-asset",
},
ContentType: new(mimeType),
CacheControl: new("max-age=3600, public"),
},
)
if err != nil {
return fmt.Errorf("cannot upload file to S3: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("cannot generate asset URLs: %w", err)
}
return nil
}
const (
subjectConfirmEmail = "Confirm your email address"
subjectPasswordReset = "Reset your password"
@@ -255,18 +135,13 @@ var (
mailingListUpdatesTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/mailing-list-updates.txt.tmpl"))
)
func (p *Presenter) getCommonVariables(ctx context.Context) (*CommonVariables, error) {
poweredByLogoURL, err := p.fm.GenerateFileUrl(ctx, &p.config.PoweredByLogo, staticAssetsDuration)
if err != nil {
return nil, fmt.Errorf("cannot generate probo logo URL: %w", err)
}
senderCompanyLogoURL, err := p.fm.GenerateFileUrl(ctx, &p.config.SenderCompanyLogo, staticAssetsDuration)
if err != nil {
return nil, fmt.Errorf("cannot generate sender logo URL: %w", err)
}
func (p *Presenter) getCommonVariables() (*CommonVariables, error) {
apiBaseURL := baseurl.MustParse(p.config.APIBaseURL)
poweredByLogoURL := apiBaseURL.AppendPath(p.config.PoweredByLogoPath).MustString()
senderCompanyLogoURL := apiBaseURL.AppendPath(p.config.SenderCompanyLogoPath).MustString()
return &CommonVariables{
APIBaseURL: p.config.APIBaseURL,
BaseURL: p.config.BaseURL,
PoweredByLogoURL: poweredByLogoURL,
SenderCompanyName: p.config.SenderCompanyName,
@@ -278,7 +153,7 @@ func (p *Presenter) getCommonVariables(ctx context.Context) (*CommonVariables, e
}
func (p *Presenter) RenderConfirmEmail(ctx context.Context, confirmationURLPath string, confirmationTokenParam string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -303,7 +178,7 @@ func (p *Presenter) RenderConfirmEmail(ctx context.Context, confirmationURLPath
}
func (p *Presenter) RenderPasswordReset(ctx context.Context, resetPasswordURLPath string, resetPasswordToken string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -328,7 +203,7 @@ func (p *Presenter) RenderPasswordReset(ctx context.Context, resetPasswordURLPat
}
func (p *Presenter) RenderInvitation(ctx context.Context, invitationURLPath string, invitationToken string, organizationName string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -361,7 +236,7 @@ func (p *Presenter) RenderDocumentApproval(
organizationName string,
documentName string,
) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -394,7 +269,7 @@ func (p *Presenter) RenderDocumentSigning(
signingURLQuery url.Values,
organizationName string,
) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -420,7 +295,7 @@ func (p *Presenter) RenderDocumentSigning(
}
func (p *Presenter) RenderDocumentExport(ctx context.Context, downloadUrl string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -439,7 +314,7 @@ func (p *Presenter) RenderDocumentExport(ctx context.Context, downloadUrl string
}
func (p *Presenter) RenderFrameworkExport(ctx context.Context, downloadUrl string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -458,7 +333,7 @@ func (p *Presenter) RenderFrameworkExport(ctx context.Context, downloadUrl strin
}
func (p *Presenter) RenderTrustCenterAccess(ctx context.Context, organizationName string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -481,7 +356,7 @@ func (p *Presenter) RenderTrustCenterDocumentAccessRejected(
fileNames []string,
organizationName string,
) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -502,7 +377,7 @@ func (p *Presenter) RenderTrustCenterDocumentAccessRejected(
}
func (p *Presenter) RenderMagicLink(ctx context.Context, magicLinkUrlPath string, tokenString string, tokenDuration time.Duration, organizationName string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -525,7 +400,7 @@ func (p *Presenter) RenderMagicLink(ctx context.Context, magicLinkUrlPath string
}
func (p *Presenter) RenderElectronicSignatureCertificate(ctx context.Context, signerName string, documentName string, subject string) (textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -546,7 +421,7 @@ func (p *Presenter) RenderElectronicSignatureCertificate(ctx context.Context, si
}
func (p *Presenter) RenderMailingListSubscription(ctx context.Context, organizationName string, confirmURL string, unsubscribeURL string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -572,7 +447,7 @@ func (p *Presenter) RenderMailingListSubscription(ctx context.Context, organizat
}
func (p *Presenter) RenderMailingListUnsubscription(ctx context.Context, organizationName string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}
@@ -594,7 +469,7 @@ func (p *Presenter) RenderMailingListUnsubscription(ctx context.Context, organiz
}
func (p *Presenter) RenderMailingListNews(ctx context.Context, organizationName string, newsTitle string, newsBody string, compliancePageURL string, unsubscribeURL string) (subject string, textBody string, htmlBody *string, err error) {
vars, err := p.getCommonVariables(ctx)
vars, err := p.getCommonVariables()
if err != nil {
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
}

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

25
pkg/brand/brand.go Normal file
View File

@@ -0,0 +1,25 @@
package brand
import (
"embed"
"io/fs"
)
var (
//go:embed assets
staticAssets embed.FS
Assets fs.FS
DefaultPoweredByLogoPath string = "/api/files/v1/static/probo-gray-small.png"
DefaultSenderCompanyLogoPath string = "/api/files/v1/static/probo.png"
)
func init() {
var err error
Assets, err = fs.Sub(staticAssets, "assets")
if err != nil {
panic(err)
}
}

View File

@@ -264,7 +264,7 @@ func (h *completionCertificateHandler) generateCertificate(
return nil, nil, fmt.Errorf("cannot resolve presenter config: %w", err)
}
emailPresenter := emails.NewPresenterFromConfig(h.fileManager, presenterCfg, ref.UnrefOrZero(signature.SignerFullName))
emailPresenter := emails.NewPresenterFromConfig(presenterCfg, ref.UnrefOrZero(signature.SignerFullName))
docName := ref.UnrefOrZero(signature.DocumentName)
if docName == "" {

View File

@@ -464,7 +464,7 @@ func (s *Service) GenerateCertificateFileURL(
return "", err
}
url, err := s.fileManager.GenerateFileUrl(ctx, &file, expiresIn)
url, err := s.fileManager.GenerateFileURL(ctx, &file, expiresIn)
if err != nil {
return "", fmt.Errorf("cannot generate certificate file URL: %w", err)
}
@@ -501,7 +501,7 @@ func (s *Service) GenerateSignatureFileURL(
return "", err
}
url, err := s.fileManager.GenerateFileUrl(ctx, &file, expiresIn)
url, err := s.fileManager.GenerateFileURL(ctx, &file, expiresIn)
if err != nil {
return "", fmt.Errorf("cannot generate signature file URL: %w", err)
}

View File

@@ -82,11 +82,11 @@ func (s *Service) GeneratePublicPresignedURL(ctx context.Context, fileID gid.GID
return "", err
}
return s.fileManager.GenerateFileUrl(ctx, file, expiresIn)
return s.fileManager.GenerateFileURL(ctx, file, expiresIn)
}
// GeneratePresignedURL returns a short-lived S3 presigned URL for an already-loaded file.
// Used by the authenticated /api/files/v1/{id} HTTP handler to avoid a second DB round-trip.
func (s *Service) GeneratePresignedURL(ctx context.Context, file *coredata.File, expiresIn time.Duration) (string, error) {
return s.fileManager.GenerateFileUrl(ctx, file, expiresIn)
return s.fileManager.GenerateFileURL(ctx, file, expiresIn)
}

View File

@@ -152,7 +152,7 @@ func (s *Service) PutFile(
return *headOutput.ContentLength, nil
}
func (s *Service) GenerateFileUrl(
func (s *Service) GenerateFileURL(
ctx context.Context,
file File,
expiresIn time.Duration,

View File

@@ -129,7 +129,7 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
return fmt.Errorf("cannot update identity: %w", err)
}
emailPresenter := emails.NewPresenter(s.fm, s.bucket, s.baseURL, identity.FullName)
emailPresenter := emails.NewPresenter(s.baseURL, identity.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderConfirmEmail(ctx, "/auth/verify-email", confirmationToken)
if err != nil {

View File

@@ -314,7 +314,7 @@ func (s AuthService) SendPasswordResetInstructionByEmail(
return fmt.Errorf("cannot load identity: %w", err)
}
emailPresenter := emails.NewPresenter(s.fm, s.bucket, s.baseURL, identity.FullName)
emailPresenter := emails.NewPresenter(s.baseURL, identity.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderPasswordReset(
ctx,
@@ -387,7 +387,7 @@ func (s AuthService) CreateIdentityWithPassword(
return nil, nil, fmt.Errorf("cannot generate confirmation token: %w", err)
}
emailPresenter := emails.NewPresenter(s.fm, s.bucket, s.baseURL, req.FullName)
emailPresenter := emails.NewPresenter(s.baseURL, req.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderConfirmEmail(ctx, "/auth/verify-email", confirmationToken)
if err != nil {
@@ -594,7 +594,7 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques
return fmt.Errorf("cannot load organization: %w", err)
}
emailPresenterCfg := emails.DefaultPresenterConfig(s.bucket, s.baseURL)
emailPresenterCfg := emails.DefaultPresenterConfig(s.baseURL)
if req.CompliancePageID != nil {
var err error
@@ -605,7 +605,7 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques
}
}
emailPresenter := emails.NewPresenterFromConfig(s.fm, emailPresenterCfg, fullName)
emailPresenter := emails.NewPresenterFromConfig(emailPresenterCfg, fullName)
subject, textBody, htmlBody, err := emailPresenter.RenderMagicLink(
ctx,

View File

@@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"net/url"
"path/filepath"
"time"
"go.gearno.de/kit/pg"
@@ -77,7 +78,7 @@ func (s *CompliancePageService) GenerateLogoURL(
return nil, nil
}
presignedURL, err := s.fm.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.fm.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -91,7 +92,7 @@ func (s *CompliancePageService) EmailPresenterConfig(ctx context.Context, compli
organization = &coredata.Organization{}
customDomain *coredata.CustomDomain
logoFile = &coredata.File{}
emailPresenterCfg = emails.DefaultPresenterConfig(s.bucket, s.baseURL)
emailPresenterCfg = emails.DefaultPresenterConfig(s.baseURL)
)
scope := coredata.NewScopeFromObjectID(compliancePageID)
@@ -152,14 +153,7 @@ func (s *CompliancePageService) EmailPresenterConfig(ctx context.Context, compli
}
// If logo exists, then we will brand the emails with the org as a sender
emailPresenterCfg.SenderCompanyLogo = emails.Asset{
Name: logoFile.FileName,
ObjectKey: logoFile.FileKey,
BucketName: logoFile.BucketName,
MimeType: logoFile.MimeType,
}
emailPresenterCfg.SenderCompanyLogoPath = filepath.Join("/api/files/v1/public/", logoFile.ID.String())
emailPresenterCfg.SenderCompanyName = organization.Name
if organization.WebsiteURL != nil {

View File

@@ -514,7 +514,7 @@ func (s *OrganizationService) InviteUser(
return fmt.Errorf("cannot generate invitation token: %w", err)
}
emailPresenter := emails.NewPresenter(s.fm, s.bucket, s.baseURL, profile.FullName)
emailPresenter := emails.NewPresenter(s.baseURL, profile.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderInvitation(
ctx,
@@ -1409,7 +1409,7 @@ func (s OrganizationService) GenerateLogoURL(
return nil, fmt.Errorf("cannot generate logo URL: %w", err)
}
presignedURL, err := s.fm.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.fm.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -1455,7 +1455,7 @@ func (s OrganizationService) GenerateHorizontalLogoURL(
return nil, err
}
presignedURL, err := s.fm.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.fm.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}

View File

@@ -18,6 +18,7 @@ import (
"context"
"errors"
"fmt"
"path/filepath"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/packages/emails"
@@ -60,7 +61,7 @@ func (s *Service) mailingListEmailConfig(
organization = &coredata.Organization{}
customDomain *coredata.CustomDomain
logoFile = &coredata.File{}
defaultCfg = emails.DefaultPresenterConfig(s.bucket, s.apiBaseURL.String())
defaultCfg = emails.DefaultPresenterConfig(s.apiBaseURL.String())
)
scope := coredata.NewScopeFromObjectID(mailingListID)
@@ -128,7 +129,7 @@ func (s *Service) presenterConfigFromTrustCenter(
customDomain *coredata.CustomDomain,
logoFile *coredata.File,
) (emails.PresenterConfig, string, error) {
cfg := emails.DefaultPresenterConfig(s.bucket, s.apiBaseURL.String())
cfg := emails.DefaultPresenterConfig(s.apiBaseURL.String())
compliancePageBase := s.apiBaseURL.WithPath("/trust/" + compliancePage.ID.String())
@@ -149,12 +150,7 @@ func (s *Service) presenterConfigFromTrustCenter(
cfg.BaseURL = compliancePageURL
if compliancePage.LogoFileID != nil && logoFile != nil && logoFile.FileKey != "" {
cfg.SenderCompanyLogo = emails.Asset{
Name: logoFile.FileName,
ObjectKey: logoFile.FileKey,
BucketName: logoFile.BucketName,
MimeType: logoFile.MimeType,
}
cfg.SenderCompanyLogoPath = filepath.Join("/api/files/v1/public/", logoFile.ID.String())
cfg.SenderCompanyName = organization.Name
if organization.WebsiteURL != nil {

View File

@@ -722,7 +722,7 @@ func (s *Service) CreateUpdateEmails(
return fmt.Errorf("cannot generate unsubscribe URL: %w", err)
}
subject, textBody, htmlBody, err := emails.NewPresenterFromConfig(s.fm, presenterCfg, sub.FullName).
subject, textBody, htmlBody, err := emails.NewPresenterFromConfig(presenterCfg, sub.FullName).
RenderMailingListNews(ctx, orgName, updateTitle, updateBody, compliancePageURL, unsubscribeURL)
if err != nil {
return fmt.Errorf("cannot render mailing list update email: %w", err)
@@ -776,7 +776,7 @@ func (s *Service) buildConfirmationMail(
return nil, fmt.Errorf("cannot get subscription confirmation email config: %w", err)
}
subject, textBody, htmlBody, err := emails.NewPresenterFromConfig(s.fm, presenterCfg, fullName).
subject, textBody, htmlBody, err := emails.NewPresenterFromConfig(presenterCfg, fullName).
RenderMailingListSubscription(ctx, orgName, confirmURL, unsubscribeURL)
if err != nil {
return nil, fmt.Errorf("cannot render subscription confirmation email: %w", err)
@@ -807,7 +807,7 @@ func (s *Service) buildUnsubscriptionMail(
return nil, fmt.Errorf("cannot get unsubscription email config: %w", err)
}
subject, textBody, htmlBody, err := emails.NewPresenterFromConfig(s.fm, presenterCfg, fullName).
subject, textBody, htmlBody, err := emails.NewPresenterFromConfig(presenterCfg, fullName).
RenderMailingListUnsubscription(ctx, orgName)
if err != nil {
return nil, fmt.Errorf("cannot render unsubscription email: %w", err)

View File

@@ -820,7 +820,7 @@ func (s *DocumentApprovalService) sendApprovalEmails(
approvalEmails := make(coredata.Emails, 0, len(profiles))
for _, profile := range profiles {
emailPresenter := emails.NewPresenter(s.svc.fileManager, s.svc.bucket, s.svc.baseURL, profile.FullName)
emailPresenter := emails.NewPresenter(s.svc.baseURL, profile.FullName)
var (
emailLinkURLPath = approvalURLPath

View File

@@ -743,7 +743,7 @@ func (s *DocumentService) SendSigningNotifications(
}
for _, signatory := range signatories {
emailPresenter := emails.NewPresenter(s.svc.fileManager, s.svc.bucket, s.svc.baseURL, signatory.FullName)
emailPresenter := emails.NewPresenter(s.svc.baseURL, signatory.FullName)
var (
employeeDocumentsURLPath = "/organizations/" + organizationID.String() + "/employee"
@@ -2629,7 +2629,7 @@ func (s *DocumentService) SendExportEmail(
return fmt.Errorf("cannot generate download URL: %w", err)
}
emailPresenter := emails.NewPresenter(s.svc.fileManager, s.svc.bucket, s.svc.baseURL, recipientName)
emailPresenter := emails.NewPresenter(s.svc.baseURL, recipientName)
subject, textBody, htmlBody, err := emailPresenter.RenderDocumentExport(
ctx,

View File

@@ -202,7 +202,7 @@ func (s FileService) GenerateFileURL(
return "", fmt.Errorf("cannot get file: %w", err)
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return "", fmt.Errorf("cannot generate file URL: %w", err)
}

View File

@@ -676,7 +676,7 @@ func (s FrameworkService) SendExportEmail(
return fmt.Errorf("cannot generate download URL: %w", err)
}
emailPresenter := emails.NewPresenter(s.svc.fileManager, s.svc.bucket, s.svc.baseURL, recipientName)
emailPresenter := emails.NewPresenter(s.svc.baseURL, recipientName)
subject, textBody, htmlBody, err := emailPresenter.RenderFrameworkExport(
ctx,
@@ -866,7 +866,7 @@ func (s FrameworkService) GenerateLightLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -908,7 +908,7 @@ func (s FrameworkService) GenerateDarkLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}

View File

@@ -450,7 +450,7 @@ func (s OrganizationService) GenerateLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -492,7 +492,7 @@ func (s OrganizationService) GenerateHorizontalLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}

View File

@@ -364,7 +364,7 @@ func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, scope cor
return fmt.Errorf("cannot get compliance page email presenter config: %w", err)
}
emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, profile.FullName)
emailPresenter := emails.NewPresenterFromConfig(emailPresenterCfg, profile.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterAccess(ctx, organization.Name)
if err != nil {

View File

@@ -313,7 +313,7 @@ func (s TrustCenterFileService) GenerateFileURL(
return "", err
}
fileURL, err := s.svc.fileManager.GenerateFileUrl(ctx, storedFile, duration)
fileURL, err := s.svc.fileManager.GenerateFileURL(ctx, storedFile, duration)
if err != nil {
return "", fmt.Errorf("cannot generate file URL: %w", err)
}

View File

@@ -500,7 +500,7 @@ func (s TrustCenterService) GenerateNDAFileURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -546,7 +546,7 @@ func (s TrustCenterService) GenerateLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -592,7 +592,7 @@ func (s TrustCenterService) GenerateDarkLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -606,7 +606,7 @@ func (s *TrustCenterService) EmailPresenterConfig(ctx context.Context, scope cor
organization = &coredata.Organization{}
customDomain *coredata.CustomDomain
logoFile = &coredata.File{}
emailPresenterCfg = emails.DefaultPresenterConfig(s.svc.bucket, s.svc.baseURL)
emailPresenterCfg = emails.DefaultPresenterConfig(s.svc.baseURL)
)
err := s.svc.pg.WithConn(
@@ -664,13 +664,7 @@ func (s *TrustCenterService) EmailPresenterConfig(ctx context.Context, scope cor
return emailPresenterCfg, nil
}
emailPresenterCfg.SenderCompanyLogo = emails.Asset{
Name: logoFile.FileName,
ObjectKey: logoFile.FileKey,
BucketName: logoFile.BucketName,
MimeType: logoFile.MimeType,
}
emailPresenterCfg.SenderCompanyLogoPath = filepath.Join("/api/files/v1/public/", logoFile.ID.String())
emailPresenterCfg.SenderCompanyName = organization.Name
if organization.WebsiteURL != nil {

View File

@@ -41,7 +41,6 @@ import (
"go.gearno.de/kit/worker"
"go.gearno.de/x/ref"
"go.opentelemetry.io/otel/trace"
"go.probo.inc/probo/packages/emails"
"go.probo.inc/probo/pkg/accessreview"
"go.probo.inc/probo/pkg/agentrun"
"go.probo.inc/probo/pkg/awsconfig"
@@ -436,14 +435,6 @@ func (impl *Implm) Run(
}
}
if err := emails.UploadStaticAssets(
ctx,
s3Client,
impl.cfg.AWS.Bucket,
); err != nil {
return fmt.Errorf("cannot upload email static assets: %w", err)
}
iamService, err := iam.NewService(
ctx,
pgClient,

View File

@@ -16,11 +16,14 @@ package files_v1
import (
"errors"
"fmt"
"io/fs"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/brand"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/file"
"go.probo.inc/probo/pkg/gid"
@@ -57,6 +60,7 @@ func NewMux(
r := chi.NewRouter()
r.Get("/static/{file}", h.handleGetStaticFile)
r.Get("/public/{fileID}", h.handleGetPublicFile)
r.Group(func(r chi.Router) {
@@ -70,6 +74,17 @@ func NewMux(
return r
}
func (h *Handler) handleGetStaticFile(w http.ResponseWriter, r *http.Request) {
file := chi.URLParam(r, "file")
if _, statErr := fs.Stat(brand.Assets, file); statErr == nil {
http.ServeFileFS(w, r, brand.Assets, file)
return
}
jsonutil.RenderNotFound(w, fmt.Errorf("file not found"))
}
func (h *Handler) handleGetPublicFile(w http.ResponseWriter, r *http.Request) {
fileIDStr := chi.URLParam(r, "fileID")

View File

@@ -85,7 +85,7 @@ func (s FrameworkService) GenerateLightLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -128,7 +128,7 @@ func (s FrameworkService) GenerateDarkLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}

View File

@@ -457,7 +457,7 @@ func (s *TrustCenterAccessService) sendAccessEmail(
return fmt.Errorf("cannot get compliance page email presenter config: %w", err)
}
emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, profile.FullName)
emailPresenter := emails.NewPresenterFromConfig(emailPresenterCfg, profile.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterAccess(ctx, organization.Name)
if err != nil {
@@ -604,7 +604,7 @@ func (s *TrustCenterAccessService) sendDocumentAccessRejectedEmail(
return fmt.Errorf("cannot get compliance page email presenter config: %w", err)
}
emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, profile.FullName)
emailPresenter := emails.NewPresenterFromConfig(emailPresenterCfg, profile.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterDocumentAccessRejected(
ctx,

View File

@@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"net/url"
"path/filepath"
"time"
"go.gearno.de/kit/pg"
@@ -147,7 +148,7 @@ func (s TrustCenterService) GenerateNDAFileURL(
return "", err
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return "", fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -194,7 +195,7 @@ func (s TrustCenterService) GenerateLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -241,7 +242,7 @@ func (s TrustCenterService) GenerateDarkLogoURL(
return nil, nil
}
presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn)
presignedURL, err := s.svc.fileManager.GenerateFileURL(ctx, file, expiresIn)
if err != nil {
return nil, fmt.Errorf("cannot generate file URL: %w", err)
}
@@ -259,7 +260,7 @@ func (s *TrustCenterService) EmailPresenterConfig(
organization = &coredata.Organization{}
customDomain *coredata.CustomDomain
logoFile = &coredata.File{}
emailPresenterCfg = emails.DefaultPresenterConfig(s.svc.bucket, s.svc.baseURL)
emailPresenterCfg = emails.DefaultPresenterConfig(s.svc.baseURL)
)
err := s.svc.pg.WithConn(
@@ -318,14 +319,7 @@ func (s *TrustCenterService) EmailPresenterConfig(
}
// If logo exists, then we will brand the emails with the org as a sender
emailPresenterCfg.SenderCompanyLogo = emails.Asset{
Name: logoFile.FileName,
ObjectKey: logoFile.FileKey,
BucketName: logoFile.BucketName,
MimeType: logoFile.MimeType,
}
emailPresenterCfg.SenderCompanyLogoPath = filepath.Join("/api/files/v1/public/", logoFile.ID.String())
emailPresenterCfg.SenderCompanyName = organization.Name
if organization.WebsiteURL != nil {