Skip to content

Commit 2ccfa2f

Browse files
authored
fix(supabase): resolve delete-user FK violation + remove duplicate migration (#2841)
* fix(supabase): add ON DELETE CASCADE on smtp_senders FK and update delete_user_data RPC The delete-user edge function failed with FK violation on smtp_senders_mining_source_id_fkey because: - smtp_senders.mining_source_id FK had no ON DELETE CASCADE - private.delete_user_data RPC did not delete from smtp_senders before mining_sources This migration (1) adds ON DELETE CASCADE to the FK and (2) updates the RPC body to explicitly clean up all user-owned tables added since 2025-01-16, making the RPC self-documenting and resilient to future FK additions. * fix(supabase): correct broken DELETEs in delete_user_data RPC The migration added in decc735 had 4 bugs in the SMS cleanup block that would break the RPC at execution time: 1. sms_campaign_unsubscribes used non-existent recipient_id column. The table only has user_id, phone, and campaign_id. Replaced with a direct WHERE user_id = owner_id filter. 2. sms_campaign_link_clicks USING-joined private.sms_campaign_recipients on r.user_id, but sms_campaign_recipients has no user_id column. Re-routed the join through private.sms_campaigns (which has user_id and CASCADEs down to recipients/link_clicks). 3. sms_campaign_recipient_gateways had the same broken r.user_id join. Re-routed through sms_campaigns the same way. 4. DELETE FROM sms_campaign_recipients WHERE user_id = owner_id would trigger plpgsql parameter substitution (no such column exists), producing WHERE $1 = owner_id (always true) and silently deleting every row in the table across all users. Re-routed through sms_campaigns. All re-routed deletes are technically redundant (sms_campaigns has ON DELETE CASCADE down the chain), but kept explicit to match the original RPC's defensive style. sms_campaign_unsubscribes is the only SMS table whose campaign_id FK is ON DELETE SET NULL, so it genuinely needs the explicit user_id delete. * fix(supabase): alias tables in delete_user_data to avoid plpgsql param shadowing The unqualified 'user_id' references in the function body would resolve to the function parameter (not the column) in plpgsql since plpgsql.variable_conflict defaults to 'error' and the search order is variables > columns. This would have caused the DELETE to compare the parameter to itself (always true or column-not-found), deleting rows belonging to other users. Add explicit table aliases to all new DELETEs, matching the pattern already used in the original RPC body for messages, tags, mining_sources, and engagement. * docs(supabase): note CASCADE side effect for manual mining-source deletion * fix(supabase): remove duplicate sms_campaigns migration Two migrations with identical content (587 bytes each) caused npx supabase db reset to fail. The 20260311000001 version runs before private.sms_campaigns is created (in 20260317000000), producing: ERROR: relation "private.sms_campaigns" does not exist The 20260328100000 version runs at the correct position. Keep the 20260328100000 version (md5 matches).
1 parent b06b4ea commit 2ccfa2f

2 files changed

Lines changed: 75 additions & 16 deletions

File tree

supabase/migrations/20260311000001_remove_sender_phone_and_simplify_simple_sms_gateway.sql

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
-- Fix: delete-user edge function failed with FK violation on
2+
-- smtp_senders_mining_source_id_fkey. The smtp_senders table was added
3+
-- in 20260530120000_add_smtp_senders.sql with a mining_source_id FK
4+
-- lacking ON DELETE CASCADE. The private.delete_user_data RPC tried to
5+
-- delete mining_sources before clearing smtp_senders, violating the FK.
6+
--
7+
-- This migration (1) adds ON DELETE CASCADE to the FK so the order
8+
-- becomes safe, and (2) updates the RPC to explicitly delete from
9+
-- smtp_senders and all other user-owned tables added since the RPC
10+
-- was last updated on 2025-01-16.
11+
--
12+
-- Side effect: deleting a mining source now cascades to its linked
13+
-- smtp_senders (previously a foreign key violation).
14+
15+
ALTER TABLE private.smtp_senders
16+
DROP CONSTRAINT smtp_senders_mining_source_id_fkey;
17+
18+
ALTER TABLE private.smtp_senders
19+
ADD CONSTRAINT smtp_senders_mining_source_id_fkey
20+
FOREIGN KEY (mining_source_id) REFERENCES private.mining_sources(id)
21+
ON DELETE CASCADE;
22+
23+
CREATE OR REPLACE FUNCTION private.delete_user_data(user_id uuid)
24+
RETURNS void
25+
LANGUAGE plpgsql
26+
SET search_path = ''
27+
AS $$
28+
DECLARE
29+
owner_id uuid;
30+
BEGIN
31+
owner_id = delete_user_data.user_id;
32+
33+
-- All user_id references below are qualified with the table alias
34+
-- (e.g. "s.user_id") on purpose. The function parameter is named
35+
-- "user_id", and in plpgsql an unqualified identifier resolves to
36+
-- variables/parameters before columns, so writing the bare column
37+
-- name would compare the parameter to itself and either error or
38+
-- delete the wrong rows. Do not "clean up" the aliases.
39+
-- Tables referencing other user-owned tables; delete dependents first.
40+
DELETE FROM private.smtp_senders s WHERE s.user_id = owner_id;
41+
DELETE FROM private.email_campaign_links l
42+
USING private.email_campaign_recipients r
43+
WHERE l.recipient_id = r.id AND r.user_id = owner_id;
44+
DELETE FROM private.email_campaign_events e
45+
USING private.email_campaign_recipients r
46+
WHERE e.recipient_id = r.id AND r.user_id = owner_id;
47+
DELETE FROM private.email_campaign_recipients r WHERE r.user_id = owner_id;
48+
DELETE FROM private.email_campaigns ec WHERE ec.user_id = owner_id;
49+
-- sms_campaign_recipients has no user_id column; join via sms_campaigns
50+
-- (which has ON DELETE CASCADE down to recipients, link_clicks, and
51+
-- recipient_gateways). sms_campaign_unsubscribes does NOT cascade
52+
-- through campaign_id (FK is ON DELETE SET NULL), so it must be
53+
-- deleted explicitly by user_id.
54+
DELETE FROM private.sms_campaign_link_clicks c
55+
USING private.sms_campaigns camp
56+
WHERE c.campaign_id = camp.id AND camp.user_id = owner_id;
57+
DELETE FROM private.sms_campaign_unsubscribes u WHERE u.user_id = owner_id;
58+
DELETE FROM private.sms_campaign_recipient_gateways g
59+
USING private.sms_campaigns camp
60+
WHERE g.campaign_id = camp.id AND camp.user_id = owner_id;
61+
DELETE FROM private.sms_campaign_recipients sr
62+
USING private.sms_campaigns camp
63+
WHERE sr.campaign_id = camp.id AND camp.user_id = owner_id;
64+
DELETE FROM private.sms_campaigns sc WHERE sc.user_id = owner_id;
65+
DELETE FROM private.sms_fleet_gateways f WHERE f.user_id = owner_id;
66+
DELETE FROM private.whatsapp_sessions w WHERE w.user_id = owner_id;
67+
68+
-- Original RPC body (unchanged)
69+
DELETE FROM private.messages msg WHERE msg.user_id = owner_id;
70+
DELETE FROM private.tags t WHERE t.user_id = owner_id;
71+
DELETE FROM private.mining_sources ms WHERE ms.user_id = owner_id;
72+
DELETE FROM private.engagement eg WHERE eg.user_id = owner_id;
73+
PERFORM private.delete_contacts(owner_id, NULL, TRUE);
74+
END;
75+
$$;

0 commit comments

Comments
 (0)