diff --git a/packages/emails/emails.go b/packages/emails/emails.go index 75ff8139a..f2e23ff3a 100644 --- a/packages/emails/emails.go +++ b/packages/emails/emails.go @@ -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) } diff --git a/packages/emails/assets/probo-gray-small.png b/pkg/brand/assets/probo-gray-small.png similarity index 100% rename from packages/emails/assets/probo-gray-small.png rename to pkg/brand/assets/probo-gray-small.png diff --git a/packages/emails/assets/probo.png b/pkg/brand/assets/probo.png similarity index 100% rename from packages/emails/assets/probo.png rename to pkg/brand/assets/probo.png diff --git a/pkg/brand/brand.go b/pkg/brand/brand.go new file mode 100644 index 000000000..14c680209 --- /dev/null +++ b/pkg/brand/brand.go @@ -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) + } +} diff --git a/pkg/esign/completion_certificate_worker.go b/pkg/esign/completion_certificate_worker.go index 7d6ce5187..9fbef7e57 100644 --- a/pkg/esign/completion_certificate_worker.go +++ b/pkg/esign/completion_certificate_worker.go @@ -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 == "" { diff --git a/pkg/esign/service.go b/pkg/esign/service.go index c1e6d647e..a883c467d 100644 --- a/pkg/esign/service.go +++ b/pkg/esign/service.go @@ -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) } diff --git a/pkg/file/service.go b/pkg/file/service.go index 0cdd2d84e..25b83a5c1 100644 --- a/pkg/file/service.go +++ b/pkg/file/service.go @@ -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) } diff --git a/pkg/filemanager/service.go b/pkg/filemanager/service.go index 281c50239..de0a73cc5 100644 --- a/pkg/filemanager/service.go +++ b/pkg/filemanager/service.go @@ -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, diff --git a/pkg/iam/account_service.go b/pkg/iam/account_service.go index 1e5e9b5c9..cd08a39ac 100644 --- a/pkg/iam/account_service.go +++ b/pkg/iam/account_service.go @@ -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 { diff --git a/pkg/iam/auth_service.go b/pkg/iam/auth_service.go index b81ebf15a..6f5eb4920 100644 --- a/pkg/iam/auth_service.go +++ b/pkg/iam/auth_service.go @@ -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, diff --git a/pkg/iam/compliance_page_service.go b/pkg/iam/compliance_page_service.go index 0f695c488..33286e620 100644 --- a/pkg/iam/compliance_page_service.go +++ b/pkg/iam/compliance_page_service.go @@ -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 { diff --git a/pkg/iam/organization_service.go b/pkg/iam/organization_service.go index ff7d51258..ffdd9b339 100644 --- a/pkg/iam/organization_service.go +++ b/pkg/iam/organization_service.go @@ -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) } diff --git a/pkg/mailman/compliance_mailing_list.go b/pkg/mailman/compliance_mailing_list.go index 0b9304d46..263228943 100644 --- a/pkg/mailman/compliance_mailing_list.go +++ b/pkg/mailman/compliance_mailing_list.go @@ -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 { diff --git a/pkg/mailman/service.go b/pkg/mailman/service.go index eabd71780..509d2ef01 100644 --- a/pkg/mailman/service.go +++ b/pkg/mailman/service.go @@ -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) diff --git a/pkg/probo/document_approval_service.go b/pkg/probo/document_approval_service.go index 550ac261d..6d75ba381 100644 --- a/pkg/probo/document_approval_service.go +++ b/pkg/probo/document_approval_service.go @@ -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 diff --git a/pkg/probo/document_service.go b/pkg/probo/document_service.go index 705ca17a8..b8932a306 100644 --- a/pkg/probo/document_service.go +++ b/pkg/probo/document_service.go @@ -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, diff --git a/pkg/probo/file_service.go b/pkg/probo/file_service.go index 9660c86cb..15356acd7 100644 --- a/pkg/probo/file_service.go +++ b/pkg/probo/file_service.go @@ -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) } diff --git a/pkg/probo/framework_service.go b/pkg/probo/framework_service.go index fdd0801d1..e39a15dd7 100644 --- a/pkg/probo/framework_service.go +++ b/pkg/probo/framework_service.go @@ -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) } diff --git a/pkg/probo/organization_service.go b/pkg/probo/organization_service.go index 318b7d68f..13f3ebc27 100644 --- a/pkg/probo/organization_service.go +++ b/pkg/probo/organization_service.go @@ -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) } diff --git a/pkg/probo/trust_center_access_service.go b/pkg/probo/trust_center_access_service.go index de59d595d..72d5ed354 100644 --- a/pkg/probo/trust_center_access_service.go +++ b/pkg/probo/trust_center_access_service.go @@ -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 { diff --git a/pkg/probo/trust_center_file_service.go b/pkg/probo/trust_center_file_service.go index 510685cb9..f44bd0c54 100644 --- a/pkg/probo/trust_center_file_service.go +++ b/pkg/probo/trust_center_file_service.go @@ -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) } diff --git a/pkg/probo/trust_center_service.go b/pkg/probo/trust_center_service.go index 010d4d87e..af171ee91 100644 --- a/pkg/probo/trust_center_service.go +++ b/pkg/probo/trust_center_service.go @@ -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 { diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index 54a59f394..834fb276b 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -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, diff --git a/pkg/server/api/files/v1/handler.go b/pkg/server/api/files/v1/handler.go index 2a3393c5a..2a1c7eb44 100644 --- a/pkg/server/api/files/v1/handler.go +++ b/pkg/server/api/files/v1/handler.go @@ -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") diff --git a/pkg/trust/framework_service.go b/pkg/trust/framework_service.go index 14da35336..902859498 100644 --- a/pkg/trust/framework_service.go +++ b/pkg/trust/framework_service.go @@ -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) } diff --git a/pkg/trust/trust_center_access_service.go b/pkg/trust/trust_center_access_service.go index cfc5b6082..6d4c5b106 100644 --- a/pkg/trust/trust_center_access_service.go +++ b/pkg/trust/trust_center_access_service.go @@ -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, diff --git a/pkg/trust/trust_center_service.go b/pkg/trust/trust_center_service.go index 792154ba5..cb40e5ce4 100644 --- a/pkg/trust/trust_center_service.go +++ b/pkg/trust/trust_center_service.go @@ -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 {