From 0c0fb5cefb70e0a072c58304e3ee4ba2fc10ded8 Mon Sep 17 00:00:00 2001 From: Matthew Meszaros Date: Tue, 18 Aug 2026 07:42:07 -0700 Subject: [PATCH] feat: scope the bulk contact update's add_campaigns and remove_campaigns SQL and its campaign readback in internal/repository/pg_contact.go by campaigns.organization_id instead of cam.user_id = caller, because campaigns are organization assets and are listed and opened org-wide, so a teammate attaching contacts to a campaign someone else created (through bulk edit or the import wizard's attach-to-campaign path) got a silent no-op with no rows inserted, matching the earlier fix that moved the contacts list itself to organization scoping while leaving categories user-scoped as that change noted --- internal/repository/pg_contact.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/repository/pg_contact.go b/internal/repository/pg_contact.go index c40031e1..a897128a 100644 --- a/internal/repository/pg_contact.go +++ b/internal/repository/pg_contact.go @@ -1613,16 +1613,18 @@ func (r *contactRepository) BulkUpdate(ctx context.Context, userID string, orgID *data.Subscribe, orgID, data.Contacts) } + // Campaigns are organization assets: scoping them by the caller made a + // teammate's "add to campaign" a silent no-op on campaigns they did not create. if len(data.RemoveCampaigns) > 0 { b.Queue(`DELETE FROM campaign_leads cl USING contacts c, campaigns cam WHERE cl.contact_id = c.id AND cl.campaign_id = cam.id AND c.organization_id = $1 - AND cam.user_id = $4 + AND cam.organization_id = $1 AND cl.contact_id = ANY($2) AND cl.campaign_id = ANY($3)`, - orgID, data.Contacts, data.RemoveCampaigns, userID) + orgID, data.Contacts, data.RemoveCampaigns) } if len(data.AddCampaigns) > 0 { @@ -1633,9 +1635,9 @@ func (r *contactRepository) BulkUpdate(ctx context.Context, userID string, orgID WHERE c.organization_id = $1 AND c.id = ANY($2) AND cam.id = ANY($3::uuid[]) - AND cam.user_id = $4 + AND cam.organization_id = $1 ON CONFLICT DO NOTHING`, - orgID, data.Contacts, data.AddCampaigns, userID) + orgID, data.Contacts, data.AddCampaigns) } if len(data.RemoveCategories) > 0 { @@ -1712,7 +1714,7 @@ func (r *contactRepository) BulkUpdate(ctx context.Context, userID string, orgID SELECT json_agg(json_build_object('id', cam.id, 'name', cam.name)) FROM campaign_leads cl JOIN campaigns cam ON cl.campaign_id = cam.id - WHERE cl.contact_id =c.id AND cam.user_id = $2 + WHERE cl.contact_id =c.id AND cam.organization_id = $3 ), '[]'::json ) AS campaigns,