"""bind_profiles_to_auth_users Revision ID: 20260224_bind_profiles_auth Revises: 85d25a191d06 Create Date: 2026-02-24 17:55:00.000000 """ from typing import Sequence, Union from alembic import op # revision identifiers, used by Alembic. revision: str = "20260224_bind_profiles_auth" down_revision: Union[str, Sequence[str], None] = "85d25a191d06" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # Remove orphan profiles that are not backed by auth.users. # This guarantees FK creation will not fail on historical inconsistent data. op.execute( """ DELETE FROM public.profiles p WHERE NOT EXISTS ( SELECT 1 FROM auth.users u WHERE u.id = p.id ) """ ) # Backfill profile rows for existing auth users. op.execute( """ INSERT INTO public.profiles (id, username, display_name) SELECT u.id, 'user_' || substr(replace(u.id::text, '-', ''), 1, 25), COALESCE( NULLIF(u.raw_user_meta_data->>'display_name', ''), NULLIF(u.raw_user_meta_data->>'full_name', '') ) FROM auth.users u LEFT JOIN public.profiles p ON p.id = u.id WHERE p.id IS NULL """ ) # Enforce one-to-one binding between profiles.id and auth.users.id. op.execute( """ ALTER TABLE public.profiles ADD CONSTRAINT fk_profiles_id_auth_users FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE """ ) # Auto-create profile rows when new auth users are registered. op.execute( """ CREATE OR REPLACE FUNCTION public.create_profile_for_new_user() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ BEGIN INSERT INTO public.profiles (id, username, display_name) VALUES ( NEW.id, 'user_' || substr(replace(NEW.id::text, '-', ''), 1, 25), COALESCE( NULLIF(NEW.raw_user_meta_data->>'display_name', ''), NULLIF(NEW.raw_user_meta_data->>'full_name', '') ) ) ON CONFLICT (id) DO NOTHING; RETURN NEW; END; $$ """ ) op.execute( """ DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users """ ) op.execute( """ CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.create_profile_for_new_user() """ ) def downgrade() -> None: op.execute("DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users") op.execute("DROP FUNCTION IF EXISTS public.create_profile_for_new_user()") op.execute( """ ALTER TABLE public.profiles DROP CONSTRAINT IF EXISTS fk_profiles_id_auth_users """ )