mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-02-12 16:12:44 +00:00
Merge branch 'feat-inline-attachment' into develop
This commit is contained in:
@@ -27,8 +27,16 @@ var EmailOrderByMap = map[string]string{
|
||||
}
|
||||
|
||||
// AddAttachmentsToEmailRequest is a request to add attachments to a message
|
||||
// supports both old format (ids array) and new format (attachments array with isInline)
|
||||
type AddAttachmentsToEmailRequest struct {
|
||||
Attachments []string `json:"ids"` // attachment IDs
|
||||
IDs []string `json:"ids"` // deprecated: old format for backward compatibility
|
||||
Attachments []AttachmentWithInline `json:"attachments"` // new format with isInline support
|
||||
}
|
||||
|
||||
// AttachmentWithInline represents an attachment ID with inline flag
|
||||
type AttachmentWithInline struct {
|
||||
ID string `json:"id"`
|
||||
IsInline bool `json:"isInline"`
|
||||
}
|
||||
|
||||
// RemoveAttachmentFromEmailRequest is a request to remove an attachment from a message
|
||||
@@ -66,13 +74,24 @@ func (m *Email) AddAttachments(g *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// handle backward compatibility: if old format (ids) is used, convert to new format
|
||||
if len(request.IDs) > 0 && len(request.Attachments) == 0 {
|
||||
for _, idStr := range request.IDs {
|
||||
request.Attachments = append(request.Attachments, AttachmentWithInline{
|
||||
ID: idStr,
|
||||
IsInline: false, // default to false for backward compatibility
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(request.Attachments) == 0 {
|
||||
m.Response.BadRequestMessage(g, "No attachments provided")
|
||||
return
|
||||
}
|
||||
attachmentIDs := []*uuid.UUID{}
|
||||
for _, idParam := range request.Attachments {
|
||||
id, err := uuid.Parse(idParam)
|
||||
attachments := []service.AttachmentWithInline{}
|
||||
for _, att := range request.Attachments {
|
||||
id, err := uuid.Parse(att.ID)
|
||||
if err != nil {
|
||||
m.Logger.Debugw(errs.MsgFailedToParseUUID,
|
||||
"error", err,
|
||||
@@ -80,14 +99,17 @@ func (m *Email) AddAttachments(g *gin.Context) {
|
||||
m.Response.BadRequestMessage(g, "Invalid attachment ID")
|
||||
return
|
||||
}
|
||||
attachmentIDs = append(attachmentIDs, &id)
|
||||
attachments = append(attachments, service.AttachmentWithInline{
|
||||
ID: &id,
|
||||
IsInline: att.IsInline,
|
||||
})
|
||||
}
|
||||
// add attachments to email
|
||||
err := m.EmailService.AddAttachments(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
id,
|
||||
attachmentIDs,
|
||||
attachments,
|
||||
)
|
||||
// handle responses
|
||||
if ok := m.handleErrors(g, err); !ok {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
type EmailAttachment struct {
|
||||
EmailID *uuid.UUID `gorm:"primary_key;not null;index;type:uuid;unique_index:idx_message_attachment;"`
|
||||
AttachmentID *uuid.UUID `gorm:"primary_key;not null;index;type:uuid;unique_index:idx_message_attachment;"`
|
||||
IsInline bool `gorm:"not null;default:false;"`
|
||||
}
|
||||
|
||||
func (EmailAttachment) TableName() string {
|
||||
|
||||
@@ -22,8 +22,8 @@ type Email struct {
|
||||
AddTrackingPixel nullable.Nullable[bool] `json:"addTrackingPixel"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
|
||||
Attachments []*Attachment `json:"attachments"`
|
||||
Company *Company `json:"company"`
|
||||
Attachments []*EmailAttachment `json:"attachments"`
|
||||
Company *Company `json:"company"`
|
||||
}
|
||||
|
||||
// Validate checks if the mail has a valid state
|
||||
@@ -132,3 +132,10 @@ type EmailOverview struct {
|
||||
|
||||
Company *Company `json:"company"`
|
||||
}
|
||||
|
||||
// EmailAttachment represents an attachment associated with an email
|
||||
// with additional metadata about how it should be displayed
|
||||
type EmailAttachment struct {
|
||||
*Attachment
|
||||
IsInline bool `json:"isInline"` // if true, use Content-Disposition: inline and set Content-ID for cid: references
|
||||
}
|
||||
|
||||
@@ -54,11 +54,13 @@ func (m *Email) AddAttachment(
|
||||
ctx context.Context,
|
||||
emailID *uuid.UUID,
|
||||
attachmentID *uuid.UUID,
|
||||
isInline bool,
|
||||
) error {
|
||||
result := m.DB.Create(
|
||||
&database.EmailAttachment{
|
||||
EmailID: emailID,
|
||||
AttachmentID: attachmentID,
|
||||
IsInline: isInline,
|
||||
},
|
||||
)
|
||||
if result.Error != nil {
|
||||
@@ -103,6 +105,19 @@ func (m *Email) GetAttachmentIDsByEmailID(
|
||||
return attachmentIDs, nil
|
||||
}
|
||||
|
||||
// GetEmailAttachments gets all email-attachment relationships for an email including isInline status
|
||||
func (m *Email) GetEmailAttachments(
|
||||
ctx context.Context,
|
||||
emailID uuid.UUID,
|
||||
) ([]database.EmailAttachment, error) {
|
||||
var emailAttachments []database.EmailAttachment
|
||||
result := m.DB.Where("email_id = ?", emailID).Find(&emailAttachments)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return emailAttachments, nil
|
||||
}
|
||||
|
||||
// RemoveAttachment removes an attachments from a email by attachment ID
|
||||
func (m *Email) RemoveAttachmentsByAttachmentID(
|
||||
ctx context.Context,
|
||||
@@ -342,10 +357,8 @@ func ToEmail(row *database.Email) *model.Email {
|
||||
content := nullable.NewNullableWithValue(*vo.NewOptionalString1MBMust(row.Content))
|
||||
addTrackingPixel := nullable.NewNullableWithValue(row.AddTrackingPixel)
|
||||
|
||||
attachments := []*model.Attachment{}
|
||||
for _, attachment := range row.Attachments {
|
||||
attachments = append(attachments, ToAttachment(attachment))
|
||||
}
|
||||
// attachments are loaded separately via loadEmailAttachmentsWithContext
|
||||
// which properly loads EmailAttachment with isInline status from junction table
|
||||
return &model.Email{
|
||||
ID: id,
|
||||
CreatedAt: row.CreatedAt,
|
||||
@@ -357,7 +370,7 @@ func ToEmail(row *database.Email) *model.Email {
|
||||
Content: content,
|
||||
AddTrackingPixel: addTrackingPixel,
|
||||
CompanyID: companyID,
|
||||
Attachments: attachments,
|
||||
Attachments: []*model.EmailAttachment{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package seed
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/google/uuid"
|
||||
@@ -350,20 +351,35 @@ func SeedSettings(
|
||||
return nil
|
||||
}
|
||||
|
||||
// Migration approach
|
||||
// Migration migrates db
|
||||
func migrate(db *gorm.DB) error {
|
||||
// First add column as nullable
|
||||
// migration for attachments.embedded_content
|
||||
// first add column as nullable
|
||||
if err := db.Exec(`ALTER TABLE attachments ADD COLUMN embedded_content BOOLEAN`).Error; err != nil {
|
||||
// column might already exist, ignore error
|
||||
errMsg := strings.ToLower(err.Error())
|
||||
if !strings.Contains(errMsg, "duplicate") && !strings.Contains(errMsg, "already exists") {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
// update existing rows
|
||||
if err := db.Exec(`UPDATE attachments SET embedded_content = false WHERE embedded_content IS NULL`).Error; err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
// Update existing rows
|
||||
if err := db.Exec(`UPDATE attachments SET embedded_content = false`).Error; err != nil {
|
||||
return errs.Wrap(err)
|
||||
// migration for email_attachments.is_inline
|
||||
// first add column as nullable
|
||||
if err := db.Exec(`ALTER TABLE email_attachments ADD COLUMN is_inline BOOLEAN`).Error; err != nil {
|
||||
// column might already exist, ignore error
|
||||
errMsg := strings.ToLower(err.Error())
|
||||
if !strings.Contains(errMsg, "duplicate") && !strings.Contains(errMsg, "already exists") {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Then make it not nullable
|
||||
if err := db.Exec(`ALTER TABLE attachments MODIFY COLUMN embedded_content BOOLEAN NOT NULL DEFAULT false`).Error; err != nil {
|
||||
// update existing rows - default to false (regular attachments)
|
||||
if err := db.Exec(`UPDATE email_attachments SET is_inline = false WHERE is_inline IS NULL`).Error; err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -2129,19 +2129,82 @@ func (c *Campaign) sendCampaignMessages(
|
||||
m.SetBodyString("text/html", bodyBuffer.String())
|
||||
// attachments
|
||||
attachments := email.Attachments
|
||||
for _, attachment := range attachments {
|
||||
for _, emailAttachment := range attachments {
|
||||
attachment := emailAttachment.Attachment
|
||||
p, err := c.MailService.AttachmentService.GetPath(attachment)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get attachment path: %s", err)
|
||||
}
|
||||
if !attachment.EmbeddedContent.MustGet() {
|
||||
|
||||
// check if attachment should be inline (for cid: references)
|
||||
if emailAttachment.IsInline {
|
||||
// inline attachment - embedded in email body, can be referenced via cid:filename
|
||||
if !attachment.EmbeddedContent.MustGet() {
|
||||
// simple inline image - no template processing needed
|
||||
m.EmbedFile(p.String())
|
||||
} else {
|
||||
// inline image with template processing
|
||||
attachmentContent, err := os.ReadFile(p.String())
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
// setup attachment for executing as email template
|
||||
attachmentAsEmail := model.Email{
|
||||
ID: email.ID,
|
||||
CreatedAt: email.CreatedAt,
|
||||
UpdatedAt: email.UpdatedAt,
|
||||
Name: email.Name,
|
||||
MailEnvelopeFrom: email.MailEnvelopeFrom,
|
||||
MailHeaderFrom: email.MailHeaderFrom,
|
||||
MailHeaderSubject: email.MailHeaderSubject,
|
||||
Content: email.Content,
|
||||
AddTrackingPixel: email.AddTrackingPixel,
|
||||
CompanyID: email.CompanyID,
|
||||
Attachments: email.Attachments,
|
||||
Company: email.Company,
|
||||
}
|
||||
attachmentAsEmail.Content = nullable.NewNullableWithValue(
|
||||
*vo.NewUnsafeOptionalString1MB(string(attachmentContent)),
|
||||
)
|
||||
// generate custom campaign URL for attachment
|
||||
recipientID := campaignRecipient.ID.MustGet()
|
||||
customCampaignURL, err := c.GetLandingPageURLByCampaignRecipientID(ctx, session, &recipientID)
|
||||
if err != nil {
|
||||
c.Logger.Errorw("failed to get campaign url for attachment", "error", err)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
attachmentStr, err := c.TemplateService.CreateMailBodyWithCustomURL(
|
||||
ctx,
|
||||
urlIdentifier.Name.MustGet(),
|
||||
urlPath,
|
||||
domain,
|
||||
campaignRecipient,
|
||||
&attachmentAsEmail,
|
||||
nil,
|
||||
customCampaignURL,
|
||||
campaignCompanyID,
|
||||
)
|
||||
if err != nil {
|
||||
return errs.Wrap(fmt.Errorf("failed to setup attachment with embedded content: %s", err))
|
||||
}
|
||||
// use EmbedReader for inline images - sets Content-Disposition: inline and Content-ID header
|
||||
// the filename becomes the Content-ID, so use <img src="cid:filename.jpg"> in HTML
|
||||
m.EmbedReader(
|
||||
filepath.Base(p.String()),
|
||||
strings.NewReader(attachmentStr),
|
||||
)
|
||||
}
|
||||
} else if !attachment.EmbeddedContent.MustGet() {
|
||||
// regular attachment - shows in attachment list
|
||||
m.AttachFile(p.String())
|
||||
} else {
|
||||
// regular attachment with template processing
|
||||
attachmentContent, err := os.ReadFile(p.String())
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
// hacky setup of attachment for executing as email template
|
||||
// setup attachment for executing as email template
|
||||
attachmentAsEmail := model.Email{
|
||||
ID: email.ID,
|
||||
CreatedAt: email.CreatedAt,
|
||||
@@ -2156,7 +2219,6 @@ func (c *Campaign) sendCampaignMessages(
|
||||
Attachments: email.Attachments,
|
||||
Company: email.Company,
|
||||
}
|
||||
// really hacky / unsafe
|
||||
attachmentAsEmail.Content = nullable.NewNullableWithValue(
|
||||
*vo.NewUnsafeOptionalString1MB(string(attachmentContent)),
|
||||
)
|
||||
@@ -3716,14 +3778,77 @@ func (c *Campaign) sendSingleEmailSMTP(
|
||||
|
||||
// handle attachments
|
||||
attachments := email.Attachments
|
||||
for _, attachment := range attachments {
|
||||
for _, emailAttachment := range attachments {
|
||||
attachment := emailAttachment.Attachment
|
||||
p, err := c.MailService.AttachmentService.GetPath(attachment)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get attachment path: %s", err)
|
||||
}
|
||||
if !attachment.EmbeddedContent.MustGet() {
|
||||
|
||||
// check if attachment should be inline (for cid: references)
|
||||
if emailAttachment.IsInline {
|
||||
// inline attachment - embedded in email body, can be referenced via cid:filename
|
||||
if !attachment.EmbeddedContent.MustGet() {
|
||||
// simple inline image - no template processing needed
|
||||
m.EmbedFile(p.String())
|
||||
} else {
|
||||
// inline image with template processing
|
||||
attachmentContent, err := os.ReadFile(p.String())
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
// setup attachment for executing as email template
|
||||
attachmentAsEmail := model.Email{
|
||||
ID: email.ID,
|
||||
CreatedAt: email.CreatedAt,
|
||||
UpdatedAt: email.UpdatedAt,
|
||||
Name: email.Name,
|
||||
MailEnvelopeFrom: email.MailEnvelopeFrom,
|
||||
MailHeaderFrom: email.MailHeaderFrom,
|
||||
MailHeaderSubject: email.MailHeaderSubject,
|
||||
Content: email.Content,
|
||||
AddTrackingPixel: email.AddTrackingPixel,
|
||||
CompanyID: email.CompanyID,
|
||||
Attachments: email.Attachments,
|
||||
Company: email.Company,
|
||||
}
|
||||
attachmentAsEmail.Content = nullable.NewNullableWithValue(
|
||||
*vo.NewUnsafeOptionalString1MB(string(attachmentContent)),
|
||||
)
|
||||
// generate custom campaign URL for attachment
|
||||
recipientID := campaignRecipient.ID.MustGet()
|
||||
customCampaignURL, err := c.GetLandingPageURLByCampaignRecipientID(ctx, session, &recipientID)
|
||||
if err != nil {
|
||||
c.Logger.Errorw("failed to get campaign url for attachment", "error", err)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
attachmentStr, err := c.TemplateService.CreateMailBodyWithCustomURL(
|
||||
ctx,
|
||||
urlIdentifier.Name.MustGet(),
|
||||
urlPath,
|
||||
domain,
|
||||
campaignRecipient,
|
||||
&attachmentAsEmail,
|
||||
nil,
|
||||
customCampaignURL,
|
||||
campaignCompanyID,
|
||||
)
|
||||
if err != nil {
|
||||
return errs.Wrap(fmt.Errorf("failed to setup attachment with embedded content: %s", err))
|
||||
}
|
||||
// use EmbedReader for inline images - sets Content-Disposition: inline and Content-ID header
|
||||
// the filename becomes the Content-ID, so use <img src="cid:filename.jpg"> in HTML
|
||||
m.EmbedReader(
|
||||
filepath.Base(p.String()),
|
||||
strings.NewReader(attachmentStr),
|
||||
)
|
||||
}
|
||||
} else if !attachment.EmbeddedContent.MustGet() {
|
||||
// regular attachment - shows in attachment list
|
||||
m.AttachFile(p.String())
|
||||
} else {
|
||||
// regular attachment with template processing
|
||||
attachmentContent, err := os.ReadFile(p.String())
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
|
||||
@@ -37,12 +37,18 @@ type Email struct {
|
||||
AttachmentPath string
|
||||
}
|
||||
|
||||
// AttachmentWithInline represents an attachment with inline flag
|
||||
type AttachmentWithInline struct {
|
||||
ID *uuid.UUID
|
||||
IsInline bool
|
||||
}
|
||||
|
||||
// AddAttachments adds an attachments to a message
|
||||
func (m *Email) AddAttachments(
|
||||
ctx context.Context,
|
||||
session *model.Session,
|
||||
messageID *uuid.UUID,
|
||||
attachmentIDs []*uuid.UUID,
|
||||
attachments []AttachmentWithInline,
|
||||
) error {
|
||||
ae := NewAuditEvent("Email.AddAttachments", session)
|
||||
ae.Details["messageId"] = messageID.String()
|
||||
@@ -69,13 +75,13 @@ func (m *Email) AddAttachments(
|
||||
}
|
||||
// add attachment to message
|
||||
attachmentIdsStr := []string{}
|
||||
for _, attachmentID := range attachmentIDs {
|
||||
attachmentIdsStr = append(attachmentIdsStr, attachmentID.String())
|
||||
for _, attachment := range attachments {
|
||||
attachmentIdsStr = append(attachmentIdsStr, attachment.ID.String())
|
||||
// get the message to ensure it exists and the user is privliged
|
||||
_, err = m.AttachmentService.GetByID(
|
||||
ctx,
|
||||
session,
|
||||
attachmentID,
|
||||
attachment.ID,
|
||||
)
|
||||
if err != nil {
|
||||
m.Logger.Errorw("failed to add attachment to email", "error", err)
|
||||
@@ -84,7 +90,8 @@ func (m *Email) AddAttachments(
|
||||
err = m.EmailRepository.AddAttachment(
|
||||
ctx,
|
||||
messageID,
|
||||
attachmentID,
|
||||
attachment.ID,
|
||||
attachment.IsInline,
|
||||
)
|
||||
if err != nil {
|
||||
m.Logger.Errorw("failed to add attachment to email", "error", err)
|
||||
@@ -640,19 +647,73 @@ func (m *Email) SendTestEmail(
|
||||
msg.SetBodyString("text/html", bodyBuffer.String())
|
||||
// attachments
|
||||
attachments := email.Attachments
|
||||
for _, attachment := range attachments {
|
||||
for _, emailAttachment := range attachments {
|
||||
attachment := emailAttachment.Attachment
|
||||
p, err := m.AttachmentService.GetPath(attachment)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get attachment path: %s", err)
|
||||
}
|
||||
if !attachment.EmbeddedContent.MustGet() {
|
||||
|
||||
// check if attachment should be inline (for cid: references)
|
||||
if emailAttachment.IsInline {
|
||||
// inline attachment - embedded in email body, can be referenced via cid:filename
|
||||
if !attachment.EmbeddedContent.MustGet() {
|
||||
// simple inline image - no template processing needed
|
||||
msg.EmbedFile(p.String())
|
||||
} else {
|
||||
// inline image with template processing
|
||||
attachmentContent, err := os.ReadFile(p.String())
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
// setup attachment for executing as email template
|
||||
attachmentAsEmail := model.Email{
|
||||
ID: email.ID,
|
||||
CreatedAt: email.CreatedAt,
|
||||
UpdatedAt: email.UpdatedAt,
|
||||
Name: email.Name,
|
||||
MailEnvelopeFrom: email.MailEnvelopeFrom,
|
||||
MailHeaderFrom: email.MailHeaderFrom,
|
||||
MailHeaderSubject: email.MailHeaderSubject,
|
||||
Content: email.Content,
|
||||
AddTrackingPixel: email.AddTrackingPixel,
|
||||
CompanyID: email.CompanyID,
|
||||
Attachments: email.Attachments,
|
||||
Company: email.Company,
|
||||
}
|
||||
attachmentAsEmail.Content = nullable.NewNullableWithValue(
|
||||
*vo.NewUnsafeOptionalString1MB(string(attachmentContent)),
|
||||
)
|
||||
attachmentStr, err := m.TemplateService.CreateMailBody(
|
||||
ctx,
|
||||
"id",
|
||||
"/",
|
||||
testDomain,
|
||||
campaignRecipient,
|
||||
&attachmentAsEmail,
|
||||
nil,
|
||||
companyID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup attachment with embedded content: %s", err)
|
||||
}
|
||||
// use EmbedReader for inline images - sets Content-Disposition: inline and Content-ID header
|
||||
// the filename becomes the Content-ID, so use <img src="cid:filename.jpg"> in HTML
|
||||
msg.EmbedReader(
|
||||
filepath.Base(p.String()),
|
||||
strings.NewReader(attachmentStr),
|
||||
)
|
||||
}
|
||||
} else if !attachment.EmbeddedContent.MustGet() {
|
||||
// regular attachment - shows in attachment list
|
||||
msg.AttachFile(p.String())
|
||||
} else {
|
||||
// inline attachment - embedded in email body, can be referenced via cid:filename
|
||||
attachmentContent, err := os.ReadFile(p.String())
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
// hacky setup of attachment for executing as email template
|
||||
// setup attachment for executing as email template
|
||||
attachmentAsEmail := model.Email{
|
||||
ID: email.ID,
|
||||
CreatedAt: email.CreatedAt,
|
||||
@@ -667,7 +728,6 @@ func (m *Email) SendTestEmail(
|
||||
Attachments: email.Attachments,
|
||||
Company: email.Company,
|
||||
}
|
||||
// really hacky / unsafe
|
||||
attachmentAsEmail.Content = nullable.NewNullableWithValue(
|
||||
*vo.NewUnsafeOptionalString1MB(string(attachmentContent)),
|
||||
)
|
||||
@@ -684,7 +744,9 @@ func (m *Email) SendTestEmail(
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup attachment with embedded content: %s", err)
|
||||
}
|
||||
msg.AttachReadSeeker(
|
||||
// use EmbedReader for inline images - sets Content-Disposition: inline and Content-ID header
|
||||
// the filename becomes the Content-ID, so use <img src="cid:filename.jpg"> in HTML
|
||||
msg.EmbedReader(
|
||||
filepath.Base(p.String()),
|
||||
strings.NewReader(attachmentStr),
|
||||
)
|
||||
@@ -907,25 +969,25 @@ func (m *Email) loadEmailAttachmentsWithContext(
|
||||
email *model.Email,
|
||||
companyID *uuid.UUID,
|
||||
) error {
|
||||
// get all attachment IDs associated with this email
|
||||
attachmentIDs, err := m.EmailRepository.GetAttachmentIDsByEmailID(ctx, email.ID.MustGet())
|
||||
// get all email-attachment relationships with isInline status
|
||||
emailAttachments, err := m.EmailRepository.GetEmailAttachments(ctx, email.ID.MustGet())
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
// if no attachments, nothing to do
|
||||
if len(attachmentIDs) == 0 {
|
||||
email.Attachments = []*model.Attachment{}
|
||||
if len(emailAttachments) == 0 {
|
||||
email.Attachments = []*model.EmailAttachment{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get attachments with proper context filtering
|
||||
contextFilteredAttachments := []*model.Attachment{}
|
||||
for _, attachmentID := range attachmentIDs {
|
||||
attachment, err := m.AttachmentService.AttachmentRepository.GetByID(ctx, attachmentID)
|
||||
contextFilteredAttachments := []*model.EmailAttachment{}
|
||||
for _, ea := range emailAttachments {
|
||||
attachment, err := m.AttachmentService.AttachmentRepository.GetByID(ctx, ea.AttachmentID)
|
||||
if err != nil {
|
||||
// if attachment doesn't exist, log and continue
|
||||
m.Logger.Debugw("attachment not found", "attachmentID", attachmentID, "error", err)
|
||||
m.Logger.Debugw("attachment not found", "attachmentID", ea.AttachmentID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -947,7 +1009,10 @@ func (m *Email) loadEmailAttachmentsWithContext(
|
||||
}
|
||||
|
||||
if isAttachmentAccessible {
|
||||
contextFilteredAttachments = append(contextFilteredAttachments, attachment)
|
||||
contextFilteredAttachments = append(contextFilteredAttachments, &model.EmailAttachment{
|
||||
Attachment: attachment,
|
||||
IsInline: ea.IsInline,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user