-- Ensure ak_users has a primary key and chat tables reference it consistently -- This script can be executed on Supabase/PostgreSQL to harden chat user relations -- -- Steps: -- 1. Add a primary key on public.ak_users(id) if it is missing. -- 2. Backfill missing ak_users rows from auth.users so foreign keys can validate. -- 3. Recreate chat_* foreign keys that point to ak_users. begin; -- 1) Guard against duplicate or null ids before creating the primary key. DO $$ DECLARE duplicate_ids integer; null_ids integer; BEGIN SELECT count(*) INTO duplicate_ids FROM ( SELECT id FROM public.ak_users GROUP BY id HAVING count(*) > 1 ) dup; SELECT count(*) INTO null_ids FROM public.ak_users WHERE id IS NULL; IF duplicate_ids > 0 THEN RAISE EXCEPTION 'Cannot add primary key on public.ak_users(id): % duplicate id(s) detected', duplicate_ids; ELSIF null_ids > 0 THEN RAISE EXCEPTION 'Cannot add primary key on public.ak_users(id): % row(s) have NULL id', null_ids; END IF; IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'ak_users_pkey' AND conrelid = 'public.ak_users'::regclass ) THEN ALTER TABLE public.ak_users ADD CONSTRAINT ak_users_pkey PRIMARY KEY (id); END IF; END; $$; -- 2) Backfill basic ak_users profiles from auth.users for any missing ids. INSERT INTO public.ak_users ( id, username, email, password_hash, gender, birthday, height_cm, weight_kg, bio, phone, avatar_url, region_id, school_id, grade_id, class_id, role, created_at, updated_at, auth_id, preferred_language ) SELECT au.id, COALESCE(NULLIF(trim(au.raw_user_meta_data ->> 'nickname'), ''), NULLIF(trim(au.raw_user_meta_data ->> 'username'), ''), split_part(au.email, '@', 1)), COALESCE(au.email, 'user_' || substr(au.id::text, 1, 8) || '@akmon.local'), 'auth-managed:' || au.id::text, COALESCE(NULLIF(trim(au.raw_user_meta_data ->> 'gender'), ''), 'other'), NULL, NULL, NULL, NULL, NULL, NULLIF(trim(au.raw_user_meta_data ->> 'avatar_url'), ''), NULL, NULL, NULL, NULL, COALESCE(NULLIF(trim(au.raw_user_meta_data ->> 'role'), ''), 'student'), COALESCE(au.created_at, now()), COALESCE(au.updated_at, now()), au.id, NULLIF(trim(au.raw_user_meta_data ->> 'preferred_language'), '') FROM auth.users au LEFT JOIN public.ak_users ak ON ak.id = au.id WHERE ak.id IS NULL; -- 3) Recreate chat foreign keys to reference ak_users. ALTER TABLE public.chat_conversations DROP CONSTRAINT IF EXISTS chat_conversations_owner_id_fkey; ALTER TABLE public.chat_conversations ADD CONSTRAINT chat_conversations_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES public.ak_users(id) ON DELETE SET NULL; ALTER TABLE public.chat_participants DROP CONSTRAINT IF EXISTS chat_participants_user_id_fkey; ALTER TABLE public.chat_participants ADD CONSTRAINT chat_participants_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.ak_users(id) ON DELETE CASCADE; ALTER TABLE public.chat_messages DROP CONSTRAINT IF EXISTS chat_messages_sender_id_fkey; ALTER TABLE public.chat_messages ADD CONSTRAINT chat_messages_sender_id_fkey FOREIGN KEY (sender_id) REFERENCES public.ak_users(id) ON DELETE CASCADE; ALTER TABLE public.chat_notifications DROP CONSTRAINT IF EXISTS chat_notifications_user_id_fkey; ALTER TABLE public.chat_notifications ADD CONSTRAINT chat_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.ak_users(id) ON DELETE CASCADE; ALTER TABLE public.chat_mqtt_downlinks DROP CONSTRAINT IF EXISTS chat_mqtt_downlinks_target_user_id_fkey; ALTER TABLE public.chat_mqtt_downlinks ADD CONSTRAINT chat_mqtt_downlinks_target_user_id_fkey FOREIGN KEY (target_user_id) REFERENCES public.ak_users(id) ON DELETE SET NULL; ALTER TABLE public.chat_mqtt_downlinks DROP CONSTRAINT IF EXISTS chat_mqtt_downlinks_created_by_fkey; ALTER TABLE public.chat_mqtt_downlinks ADD CONSTRAINT chat_mqtt_downlinks_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.ak_users(id) ON DELETE CASCADE; commit;