mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
The webhook routes inbound messages by phone_number_id and uses
`.single()` to find the owning config. If two users save the same
phone_number_id, `.single()` errors PGRST116 ("multiple rows") and every
inbound message is silently dropped while the log misleadingly reports
"No config found".
Three layers of fix:
- POST /api/whatsapp/config now rejects with 409 when another user has
already claimed the phone_number_id. The check uses a service-role
client because RLS hides the conflicting row from the saving user.
- Webhook lookup no longer uses `.single()`. It distinguishes 0 rows
(true "no config") from ≥2 rows (duplicate claim) so logs name the
real cause and list conflicting user_ids.
- Migration 013 adds UNIQUE(phone_number_id) to whatsapp_config so the
same can't happen via direct DB writes or a race. Fails loudly with a
copy-pasteable resolution hint if duplicates already exist, rather
than auto-deduping (which would destroy tokens).
Closes #136.
85 lines
3.0 KiB
SQL
85 lines
3.0 KiB
SQL
-- ============================================================
|
|
-- whatsapp_config: enforce one user per phone_number_id
|
|
--
|
|
-- The webhook routes inbound messages by `phone_number_id` and uses
|
|
-- `.single()` to find the owning config row. If two users have saved
|
|
-- the same `phone_number_id`, `.single()` errors PGRST116 ("multiple
|
|
-- rows returned") and the webhook silently drops every inbound
|
|
-- message — see issue #136.
|
|
--
|
|
-- wacrm is single-tenant per WhatsApp number by design (RLS on
|
|
-- conversations / messages is `auth.uid() = user_id`, so another user
|
|
-- physically cannot read a conversation routed to a different owner).
|
|
-- A UNIQUE constraint at the DB level makes that intent enforceable
|
|
-- and stops races between the app-level check and the insert.
|
|
--
|
|
-- ─── On existing data ───────────────────────────────────────────
|
|
-- If duplicates already exist in production, this migration FAILS
|
|
-- LOUDLY rather than silently dropping rows. Auto-deduping would
|
|
-- destroy user data (encrypted tokens, connection state) — the
|
|
-- operator has to choose which user keeps the number. To resolve:
|
|
--
|
|
-- SELECT phone_number_id, array_agg(user_id) AS owners
|
|
-- FROM whatsapp_config
|
|
-- GROUP BY phone_number_id
|
|
-- HAVING count(*) > 1;
|
|
--
|
|
-- Then DELETE the duplicate rows you don't want to keep and re-run
|
|
-- migrations.
|
|
--
|
|
-- Idempotent — safe to run multiple times once the constraint is in
|
|
-- place.
|
|
-- ============================================================
|
|
|
|
-- 1. Fail loudly if duplicates exist. Spelling out the conflicting
|
|
-- phone_number_id and the user_ids that own it gives the operator
|
|
-- a copy-pasteable starting point.
|
|
DO $$
|
|
DECLARE
|
|
conflict_count INT;
|
|
sample TEXT;
|
|
BEGIN
|
|
SELECT count(*) INTO conflict_count
|
|
FROM (
|
|
SELECT phone_number_id
|
|
FROM whatsapp_config
|
|
GROUP BY phone_number_id
|
|
HAVING count(*) > 1
|
|
) dupes;
|
|
|
|
IF conflict_count > 0 THEN
|
|
SELECT string_agg(
|
|
phone_number_id || ' -> [' || array_to_string(owners, ', ') || ']',
|
|
E'\n '
|
|
)
|
|
INTO sample
|
|
FROM (
|
|
SELECT phone_number_id, array_agg(user_id::text) AS owners
|
|
FROM whatsapp_config
|
|
GROUP BY phone_number_id
|
|
HAVING count(*) > 1
|
|
) dupe_detail;
|
|
|
|
RAISE EXCEPTION
|
|
E'Cannot add UNIQUE(phone_number_id) on whatsapp_config — % phone_number_id value(s) are claimed by more than one user:\n %\nDelete the duplicate rows you do not want to keep (see migration comment), then re-run migrations.',
|
|
conflict_count,
|
|
sample;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- 2. Add the UNIQUE constraint. PostgreSQL has no "ADD CONSTRAINT IF
|
|
-- NOT EXISTS", so guard via pg_constraint.
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_constraint
|
|
WHERE conname = 'whatsapp_config_phone_number_id_key'
|
|
AND conrelid = 'whatsapp_config'::regclass
|
|
) THEN
|
|
ALTER TABLE whatsapp_config
|
|
ADD CONSTRAINT whatsapp_config_phone_number_id_key
|
|
UNIQUE (phone_number_id);
|
|
END IF;
|
|
END $$;
|