Files
social-app/backend/alembic/versions/20260224_bind_profiles_to_auth_users.py
T

113 lines
3.0 KiB
Python
Raw Normal View History

2026-02-24 18:18:42 +08:00
"""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
"""
)