113 lines
3.0 KiB
Python
113 lines
3.0 KiB
Python
"""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
|
|
"""
|
|
)
|