diff --git a/backend/app/server.go b/backend/app/server.go index 5e2161f..6de7181 100644 --- a/backend/app/server.go +++ b/backend/app/server.go @@ -712,12 +712,6 @@ func (s *Server) emitTrainingMilestoneIfNeeded( if !isTraining { return nil } - // an anonymous campaign stores its events without a recipient, so a milestone - // cannot be deduplicated per recipient and would be recorded again on every - // visit - if campaign.IsAnonymous.MustGet() { - return nil - } milestoneName := trainingMilestoneEventName(pageType) if milestoneName == "" { return nil @@ -1303,33 +1297,16 @@ func (s *Server) checkAndServePhishingPage( return true, fmt.Errorf("user submitted phishing data too large: %s", err) } } - var event *model.CampaignEvent - // only save data if red team flag is set - if !campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadata(c, campaign) - event = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: clientIP, - UserAgent: userAgent, - EventID: submitDataEventID, - Data: submittedData, - Metadata: metadata, - } - } else { - ua := vo.NewEmptyOptionalString255() - data := vo.NewEmptyOptionalString1MB() - event = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: ua, - EventID: submitDataEventID, - Data: data, - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadata(c, campaign) + event := &model.CampaignEvent{ + ID: &newEventID, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: clientIP, + UserAgent: userAgent, + EventID: submitDataEventID, + Data: submittedData, + Metadata: metadata, } err = s.repositories.Campaign.SaveEvent(c, event) if err != nil { @@ -1466,31 +1443,16 @@ func (s *Server) checkAndServePhishingPage( userAgent := vo.NewOptionalString255Must(utils.Substring(c.Request.UserAgent(), 0, MAX_USER_AGENT_SAVED)) syntheticData := vo.NewOptionalString1MBMust("synthetic_from_page_visit") - var syntheticReadEvent *model.CampaignEvent - if !campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadata(c, campaign) - syntheticReadEvent = &model.CampaignEvent{ - ID: &syntheticReadEventID, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: clientIP, - UserAgent: userAgent, - EventID: messageReadEventID, - Data: syntheticData, - Metadata: metadata, - } - } else { - ua := vo.NewEmptyOptionalString255() - syntheticReadEvent = &model.CampaignEvent{ - ID: &syntheticReadEventID, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: ua, - EventID: messageReadEventID, - Data: syntheticData, - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadata(c, campaign) + syntheticReadEvent := &model.CampaignEvent{ + ID: &syntheticReadEventID, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: clientIP, + UserAgent: userAgent, + EventID: messageReadEventID, + Data: syntheticData, + Metadata: metadata, } // save the synthetic message read event @@ -1522,31 +1484,16 @@ func (s *Server) checkAndServePhishingPage( eventID := cache.EventIDByName[eventName] clientIP := vo.NewOptionalString64Must(utils.ExtractClientIP(c.Request, s.trustedProxies)) userAgent := vo.NewOptionalString255Must(utils.Substring(c.Request.UserAgent(), 0, MAX_USER_AGENT_SAVED)) - var visitEvent *model.CampaignEvent - if !campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadata(c, campaign) - visitEvent = &model.CampaignEvent{ - ID: &visitEventID, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: clientIP, - UserAgent: userAgent, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: metadata, - } - } else { - ua := vo.NewEmptyOptionalString255() - visitEvent = &model.CampaignEvent{ - ID: &visitEventID, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: ua, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadata(c, campaign) + visitEvent := &model.CampaignEvent{ + ID: &visitEventID, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: clientIP, + UserAgent: userAgent, + EventID: eventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: metadata, } // save the visit event unless it's the final page repeat @@ -1830,31 +1777,16 @@ func (s *Server) checkAndServePhishingPage( userAgent := vo.NewOptionalString255Must(utils.Substring(c.Request.UserAgent(), 0, MAX_USER_AGENT_SAVED)) syntheticData := vo.NewOptionalString1MBMust("synthetic_from_page_visit") - var syntheticReadEvent *model.CampaignEvent - if !campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadata(c, campaign) - syntheticReadEvent = &model.CampaignEvent{ - ID: &syntheticReadEventID, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: clientIP, - UserAgent: userAgent, - EventID: messageReadEventID, - Data: syntheticData, - Metadata: metadata, - } - } else { - ua := vo.NewEmptyOptionalString255() - syntheticReadEvent = &model.CampaignEvent{ - ID: &syntheticReadEventID, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: ua, - EventID: messageReadEventID, - Data: syntheticData, - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadata(c, campaign) + syntheticReadEvent := &model.CampaignEvent{ + ID: &syntheticReadEventID, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: clientIP, + UserAgent: userAgent, + EventID: messageReadEventID, + Data: syntheticData, + Metadata: metadata, } // save the synthetic message read event @@ -1887,31 +1819,16 @@ func (s *Server) checkAndServePhishingPage( eventID := uuid.New() clientIP := vo.NewOptionalString64Must(utils.ExtractClientIP(c.Request, s.trustedProxies)) userAgent := vo.NewOptionalString255Must(utils.Substring(c.Request.UserAgent(), 0, MAX_USER_AGENT_SAVED)) - var event *model.CampaignEvent - if !campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadata(c, campaign) - event = &model.CampaignEvent{ - ID: &eventID, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: clientIP, - UserAgent: userAgent, - EventID: campaignEventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: metadata, - } - } else { - ua := vo.NewEmptyOptionalString255() - event = &model.CampaignEvent{ - ID: &eventID, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: ua, - EventID: campaignEventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadata(c, campaign) + event := &model.CampaignEvent{ + ID: &eventID, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: clientIP, + UserAgent: userAgent, + EventID: campaignEventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: metadata, } // only log the page visit if it is not after the final page if currentPageType != data.PAGE_TYPE_DONE { @@ -2105,31 +2022,16 @@ func (s *Server) renderDenyPage( eventID := cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_DENY_PAGE_VISITED] clientIP := vo.NewOptionalString64Must(utils.ExtractClientIP(c.Request, s.trustedProxies)) userAgent := vo.NewOptionalString255Must(utils.Substring(c.Request.UserAgent(), 0, MAX_USER_AGENT_SAVED)) - var event *model.CampaignEvent - if !campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadata(c, campaign) - event = &model.CampaignEvent{ - ID: &denyPageVisitEventID, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: clientIP, - UserAgent: userAgent, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: metadata, - } - } else { - ua := vo.NewEmptyOptionalString255() - event = &model.CampaignEvent{ - ID: &denyPageVisitEventID, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: ua, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadata(c, campaign) + event := &model.CampaignEvent{ + ID: &denyPageVisitEventID, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: clientIP, + UserAgent: userAgent, + EventID: eventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: metadata, } err = s.repositories.Campaign.SaveEvent(c, event) diff --git a/backend/database/campaign.go b/backend/database/campaign.go index 1b5349c..4c4d3d3 100644 --- a/backend/database/campaign.go +++ b/backend/database/campaign.go @@ -55,9 +55,11 @@ type Campaign struct { ConstraintEndTime *string `gorm:"index;"` SaveSubmittedData bool `gorm:"not null;default:false"` SaveBrowserMetadata bool `gorm:"not null;default:false"` - IsAnonymous bool `gorm:"not null;default:false"` - IsTest bool `gorm:"not null;default:false"` - Obfuscate bool `gorm:"not null;default:false"` + // IsAnonymous is reserved for a campaign mode that records events without + // a recipient relation. nothing reads it and the model rejects true. + IsAnonymous bool `gorm:"not null;default:false"` + IsTest bool `gorm:"not null;default:false"` + Obfuscate bool `gorm:"not null;default:false"` // deprecated: webhook settings moved to campaign_webhooks junction table // kept for backward compatibility during migration diff --git a/backend/model/campaign.go b/backend/model/campaign.go index 85d17b1..7defb95 100644 --- a/backend/model/campaign.go +++ b/backend/model/campaign.go @@ -46,9 +46,11 @@ type Campaign struct { SaveSubmittedData nullable.Nullable[bool] `json:"saveSubmittedData"` SaveBrowserMetadata nullable.Nullable[bool] `json:"saveBrowserMetadata"` - IsAnonymous nullable.Nullable[bool] `json:"isAnonymous"` - IsTest nullable.Nullable[bool] `json:"isTest"` - Obfuscate nullable.Nullable[bool] `json:"obfuscate"` + // IsAnonymous is reserved for a campaign mode that records events without + // a recipient relation. no code acts on it and Validate rejects true. + IsAnonymous nullable.Nullable[bool] `json:"isAnonymous"` + IsTest nullable.Nullable[bool] `json:"isTest"` + Obfuscate nullable.Nullable[bool] `json:"obfuscate"` // IsTraining is snapshotted from the template; read-only in campaign requests. IsTraining nullable.Nullable[bool] `json:"isTraining"` // deprecated: use Webhooks array instead for multiple webhooks with per-webhook settings @@ -145,6 +147,9 @@ func (c *Campaign) Validate() error { if err := validate.NullableFieldRequired("sortOrder", c.SortOrder); err != nil { return err } + if v, err := c.IsAnonymous.Get(); err == nil && v { + return validate.WrapErrorWithField(errors.New("anonymous campaigns are not supported"), "isAnonymous") + } // if a start or end is set, then end must be equal or after the start if c.SendStartAt.IsSpecified() && !c.SendStartAt.IsNull() || (c.SendEndAt.IsSpecified() && !c.SendEndAt.IsNull()) { if err := validate.NullableFieldRequired("sendStartAt", c.SendStartAt); err != nil { diff --git a/backend/proxy/proxy.go b/backend/proxy/proxy.go index aae3d08..dd641c8 100644 --- a/backend/proxy/proxy.go +++ b/backend/proxy/proxy.go @@ -4373,30 +4373,16 @@ func (m *ProxyHandler) registerPageVisitEvent(req *http.Request, session *servic userAgent := vo.NewOptionalString255Must(utils.Substring(session.UserAgent, 0, 255)) syntheticData := vo.NewOptionalString1MBMust("synthetic_from_page_visit") - var syntheticReadEvent *model.CampaignEvent - if !session.Campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, session.Campaign) - syntheticReadEvent = &model.CampaignEvent{ - ID: &syntheticReadEventID, - CampaignID: session.CampaignID, - RecipientID: session.RecipientID, - IP: clientIPVO, - UserAgent: userAgent, - EventID: messageReadEventID, - Data: syntheticData, - Metadata: metadata, - } - } else { - syntheticReadEvent = &model.CampaignEvent{ - ID: &syntheticReadEventID, - CampaignID: session.CampaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: vo.NewEmptyOptionalString255(), - EventID: messageReadEventID, - Data: syntheticData, - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, session.Campaign) + syntheticReadEvent := &model.CampaignEvent{ + ID: &syntheticReadEventID, + CampaignID: session.CampaignID, + RecipientID: session.RecipientID, + IP: clientIPVO, + UserAgent: userAgent, + EventID: messageReadEventID, + Data: syntheticData, + Metadata: metadata, } // save the synthetic message read event @@ -4451,30 +4437,16 @@ func (m *ProxyHandler) registerPageVisitEvent(req *http.Request, session *servic clientIPVO := vo.NewOptionalString64Must(clientIP) userAgent := vo.NewOptionalString255Must(utils.Substring(session.UserAgent, 0, 255)) - var visitEvent *model.CampaignEvent - if !session.Campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, session.Campaign) - visitEvent = &model.CampaignEvent{ - ID: &visitEventID, - CampaignID: session.CampaignID, - RecipientID: session.RecipientID, - IP: clientIPVO, - UserAgent: userAgent, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: metadata, - } - } else { - visitEvent = &model.CampaignEvent{ - ID: &visitEventID, - CampaignID: session.CampaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: vo.NewEmptyOptionalString255(), - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, session.Campaign) + visitEvent := &model.CampaignEvent{ + ID: &visitEventID, + CampaignID: session.CampaignID, + RecipientID: session.RecipientID, + IP: clientIPVO, + UserAgent: userAgent, + EventID: eventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: metadata, } // save the visit event @@ -5088,31 +5060,16 @@ func (m *ProxyHandler) registerDenyPageVisitEventDirect(req *http.Request, reqCt clientIP := vo.NewOptionalString64Must(utils.ExtractClientIP(req, m.trustedProxies)) userAgent := vo.NewOptionalString255Must(utils.Substring(reqCtx.OriginalUserAgent, 0, 1000)) // MAX_USER_AGENT_SAVED equivalent - var event *model.CampaignEvent - if !campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, campaign) - event = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: campaignID, - RecipientID: recipientID, - IP: clientIP, - UserAgent: userAgent, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: metadata, - } - } else { - ua := vo.NewEmptyOptionalString255() - event = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: ua, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, campaign) + event := &model.CampaignEvent{ + ID: &newEventID, + CampaignID: campaignID, + RecipientID: recipientID, + IP: clientIP, + UserAgent: userAgent, + EventID: eventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: metadata, } err := m.CampaignRepository.SaveEvent(req.Context(), event) @@ -5162,31 +5119,16 @@ func (m *ProxyHandler) registerEvasionPageVisitEventDirect(req *http.Request, re clientIP := vo.NewOptionalString64Must(utils.ExtractClientIP(req, m.trustedProxies)) userAgent := vo.NewOptionalString255Must(utils.Substring(reqCtx.OriginalUserAgent, 0, 1000)) // MAX_USER_AGENT_SAVED equivalent - var event *model.CampaignEvent - if !campaign.IsAnonymous.MustGet() { - metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, campaign) - event = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: campaignID, - RecipientID: recipientID, - IP: clientIP, - UserAgent: userAgent, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: metadata, - } - } else { - ua := vo.NewEmptyOptionalString255() - event = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: ua, - EventID: eventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } + metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, campaign) + event := &model.CampaignEvent{ + ID: &newEventID, + CampaignID: campaignID, + RecipientID: recipientID, + IP: clientIP, + UserAgent: userAgent, + EventID: eventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: metadata, } err := m.CampaignRepository.SaveEvent(req.Context(), event) diff --git a/backend/service/campaign.go b/backend/service/campaign.go index f9650b0..92ed2e0 100644 --- a/backend/service/campaign.go +++ b/backend/service/campaign.go @@ -1607,38 +1607,23 @@ func (c *Campaign) SaveTrackingPixelLoaded( } trackingPixelLoadedEventID := cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_MESSAGE_READ] newEventID := uuid.New() - var campaignEvent *model.CampaignEvent - if campaign.IsAnonymous.MustGet() { - userAgent := vo.NewEmptyOptionalString255() - campaignEvent = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: userAgent, - EventID: trackingPixelLoadedEventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } - } else { - ip := vo.NewOptionalString64Must(utils.ExtractClientIP(ctx.Request, c.TrustedProxies)) - ua := ctx.Request.UserAgent() - if len(ua) > 255 { - ua = strings.TrimSpace(ua[:255]) - } - userAgent := vo.NewOptionalString255Must(ua) - // extract metadata (ja4, platform, accept-language) - metadata := model.ExtractCampaignEventMetadata(ctx, campaign) - campaignEvent = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: ip, - UserAgent: userAgent, - EventID: cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_MESSAGE_READ], - Data: vo.NewEmptyOptionalString1MB(), - Metadata: metadata, - } + ip := vo.NewOptionalString64Must(utils.ExtractClientIP(ctx.Request, c.TrustedProxies)) + ua := ctx.Request.UserAgent() + if len(ua) > 255 { + ua = strings.TrimSpace(ua[:255]) + } + userAgent := vo.NewOptionalString255Must(ua) + // extract metadata (ja4, platform, accept-language) + metadata := model.ExtractCampaignEventMetadata(ctx, campaign) + campaignEvent := &model.CampaignEvent{ + ID: &newEventID, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: ip, + UserAgent: userAgent, + EventID: trackingPixelLoadedEventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: metadata, } err = c.CampaignRepository.SaveEvent(ctx, campaignEvent) if err != nil { @@ -1720,49 +1705,33 @@ func (c *Campaign) SaveRecipientReported( // de-duplicate: a recipient that already reported is not recorded again so a // button that fires more than once does not spam the timeline - if !campaign.IsAnonymous.MustGet() { - alreadyReported, err := c.CampaignRepository.HasEvent(ctx.Request.Context(), &campaignID, &recipientID, reportedEventID) - if err != nil { - c.Logger.Errorw("failed to check existing reported event", "error", err) - return errs.Wrap(err) - } - if alreadyReported { - c.Logger.Debugw("recipient already reported", "campaignRecipientID", campaignRecipientID.String()) - return nil - } + alreadyReported, err := c.CampaignRepository.HasEvent(ctx.Request.Context(), &campaignID, &recipientID, reportedEventID) + if err != nil { + c.Logger.Errorw("failed to check existing reported event", "error", err) + return errs.Wrap(err) + } + if alreadyReported { + c.Logger.Debugw("recipient already reported", "campaignRecipientID", campaignRecipientID.String()) + return nil } newEventID := uuid.New() - var campaignEvent *model.CampaignEvent - if campaign.IsAnonymous.MustGet() { - campaignEvent = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: vo.NewEmptyOptionalString255(), - EventID: reportedEventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } - } else { - ip := vo.NewOptionalString64Must(utils.ExtractClientIP(ctx.Request, c.TrustedProxies)) - ua := ctx.Request.UserAgent() - if len(ua) > 255 { - ua = strings.TrimSpace(ua[:255]) - } - userAgent := vo.NewOptionalString255Must(ua) - metadata := model.ExtractCampaignEventMetadata(ctx, campaign) - campaignEvent = &model.CampaignEvent{ - ID: &newEventID, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: ip, - UserAgent: userAgent, - EventID: reportedEventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: metadata, - } + ip := vo.NewOptionalString64Must(utils.ExtractClientIP(ctx.Request, c.TrustedProxies)) + ua := ctx.Request.UserAgent() + if len(ua) > 255 { + ua = strings.TrimSpace(ua[:255]) + } + userAgent := vo.NewOptionalString255Must(ua) + metadata := model.ExtractCampaignEventMetadata(ctx, campaign) + campaignEvent := &model.CampaignEvent{ + ID: &newEventID, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: ip, + UserAgent: userAgent, + EventID: reportedEventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: metadata, } err = c.CampaignRepository.SaveEvent(ctx, campaignEvent) if err != nil { @@ -3228,37 +3197,15 @@ func (c *Campaign) saveSendingResult( } campaignID := campaignRecipient.CampaignID.MustGet() recipientID := campaignRecipient.RecipientID.MustGet() - campaign, err := c.CampaignRepository.GetByID( - ctx, - &campaignID, - &repository.CampaignOption{}, - ) - if err != nil { - return errs.Wrap(err) - } - var campaignEvent *model.CampaignEvent - if !campaign.IsAnonymous.MustGet() { - campaignEvent = &model.CampaignEvent{ - ID: &id, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: vo.NewOptionalString64Must(""), - UserAgent: vo.NewOptionalString255Must(""), - EventID: eventID, - Data: data, - Metadata: vo.NewEmptyOptionalString1MB(), - } - } else { - campaignEvent = &model.CampaignEvent{ - ID: &id, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewOptionalString64Must(""), - UserAgent: vo.NewOptionalString255Must(""), - EventID: eventID, - Data: data, - Metadata: vo.NewEmptyOptionalString1MB(), - } + campaignEvent := &model.CampaignEvent{ + ID: &id, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: vo.NewOptionalString64Must(""), + UserAgent: vo.NewOptionalString255Must(""), + EventID: eventID, + Data: data, + Metadata: vo.NewEmptyOptionalString1MB(), } err = c.CampaignRepository.SaveEvent(ctx, campaignEvent) if err != nil { @@ -4301,31 +4248,16 @@ func (c *Campaign) SetSentAtByCampaignRecipientID( campaignID := campaignRecipient.CampaignID.MustGet() recipientID := campaignRecipient.RecipientID.MustGet() - var campaignEvent *model.CampaignEvent - details := vo.NewOptionalString1MBMust("manually set as sent") - if campaign.IsAnonymous.MustGet() { - campaignEvent = &model.CampaignEvent{ - ID: &id, - CampaignID: &campaignID, - RecipientID: nil, - IP: vo.NewOptionalString64Must(""), - UserAgent: vo.NewOptionalString255Must(""), - EventID: cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_MESSAGE_SENT], - Data: details, - Metadata: vo.NewEmptyOptionalString1MB(), - } - } else { - campaignEvent = &model.CampaignEvent{ - ID: &id, - CampaignID: &campaignID, - RecipientID: &recipientID, - IP: vo.NewOptionalString64Must(""), - UserAgent: vo.NewOptionalString255Must(""), - EventID: cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_MESSAGE_SENT], - Data: details, - Metadata: vo.NewEmptyOptionalString1MB(), - } + campaignEvent := &model.CampaignEvent{ + ID: &id, + CampaignID: &campaignID, + RecipientID: &recipientID, + IP: vo.NewOptionalString64Must(""), + UserAgent: vo.NewOptionalString255Must(""), + EventID: cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_MESSAGE_SENT], + Data: details, + Metadata: vo.NewEmptyOptionalString1MB(), } err = c.CampaignRepository.SaveEvent(ctx, campaignEvent) @@ -5820,29 +5752,15 @@ func (c *Campaign) ProcessReportedCSV( // create campaign event for reported eventID := uuid.New() - var campaignEvent *model.CampaignEvent - if campaign.IsAnonymous.MustGet() { - campaignEvent = &model.CampaignEvent{ - ID: &eventID, - CampaignID: campaignID, - RecipientID: nil, - IP: vo.NewEmptyOptionalString64(), - UserAgent: vo.NewEmptyOptionalString255(), - EventID: reportedEventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } - } else { - campaignEvent = &model.CampaignEvent{ - ID: &eventID, - CampaignID: campaignID, - RecipientID: &recipientID, - IP: vo.NewEmptyOptionalString64(), - UserAgent: vo.NewEmptyOptionalString255(), - EventID: reportedEventID, - Data: vo.NewEmptyOptionalString1MB(), - Metadata: vo.NewEmptyOptionalString1MB(), - } + campaignEvent := &model.CampaignEvent{ + ID: &eventID, + CampaignID: campaignID, + RecipientID: &recipientID, + IP: vo.NewEmptyOptionalString64(), + UserAgent: vo.NewEmptyOptionalString255(), + EventID: reportedEventID, + Data: vo.NewEmptyOptionalString1MB(), + Metadata: vo.NewEmptyOptionalString1MB(), } // save the event with custom timestamp @@ -5973,11 +5891,10 @@ func (c *Campaign) buildReportHTMLWithData( } } - // Only query per-recipient data for non-anonymous, non-anonymized campaigns + // the per recipient table is only available while the recipient relation exists var recipients []model.ReportRecipient - isAnon, _ := campaign.IsAnonymous.Get() isAnonymized := campaign.AnonymizedAt.IsSpecified() && !campaign.AnonymizedAt.IsNull() - if !isAnon && !isAnonymized { + if !isAnonymized { recipients, err = c.CampaignRepository.GetReportRecipients(ctx, campaignID) if err != nil { c.Logger.Warnw("failed to get report recipients, continuing without detail table", "error", err) diff --git a/frontend/src/routes/campaign/+page.svelte b/frontend/src/routes/campaign/+page.svelte index dec9722..f6a1c1a 100644 --- a/frontend/src/routes/campaign/+page.svelte +++ b/frontend/src/routes/campaign/+page.svelte @@ -2729,13 +2729,6 @@ > - - {#if formValues.webhooks.length > 0} Webhooks: