"""allow register ledger without chat and add user init trigger Revision ID: 202604030003 Revises: 202604030002 Create Date: 2026-04-03 23:20:00 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa revision: str = "202604030003" down_revision: Union[str, Sequence[str], None] = "202604030002" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.alter_column( "points_ledger", "biz_type", existing_type=sa.String(length=16), nullable=True ) op.alter_column("points_ledger", "biz_id", existing_type=sa.UUID(), nullable=True) op.drop_constraint("ck_points_ledger_biz_type", "points_ledger", type_="check") op.create_check_constraint( "ck_points_ledger_biz_type", "points_ledger", "biz_type is null or biz_type = 'chat'", ) op.create_check_constraint( "ck_points_ledger_biz_binding", "points_ledger", "((change_type = 'register' and biz_type is null and biz_id is null) or " "(change_type in ('consume', 'grant', 'adjust') and biz_type = 'chat' and biz_id is not null))", ) op.execute( """ create or replace function public.initialize_profile_and_points_on_signup() returns trigger language plpgsql security definer set search_path = public as $$ declare v_username text; v_ledger_id uuid; v_event_id text; begin v_username := 'user_' || substring(md5(new.id::text || clock_timestamp()::text || random()::text) from 1 for 6); v_ledger_id := md5(new.id::text || 'ledger' || clock_timestamp()::text || random()::text)::uuid; v_event_id := 'register:' || new.id::text; insert into public.profiles (id, username, avatar_url, bio, settings) values ( new.id, v_username, null, null, jsonb_build_object( 'version', 1, 'preferences', jsonb_build_object( 'interface_language', 'zh-CN', 'ai_language', 'zh-CN', 'timezone', 'Asia/Shanghai', 'country', 'CN' ), 'privacy', jsonb_build_object('profile_visibility', 'public'), 'notification', jsonb_build_object('push_enabled', true) ) ) on conflict (id) do nothing; insert into public.user_points ( user_id, balance, frozen_balance, lifetime_earned, lifetime_spent, version ) values (new.id, 100, 0, 100, 0, 0) on conflict (user_id) do nothing; insert into public.points_ledger ( id, user_id, direction, amount, balance_after, change_type, biz_type, biz_id, event_id, operator_id, metadata ) values ( v_ledger_id, new.id, 1, 100, 100, 'register', null, null, v_event_id, null, jsonb_build_object( 'schema_version', 1, 'reason_code', 'REGISTER_WELCOME', 'operator_type', 'system', 'run_id', v_event_id, 'request_id', null, 'ext', jsonb_build_object('source', 'auth_signup') ) ) on conflict (user_id, event_id) do nothing; return new; end; $$; """ ) op.execute( "drop trigger if exists trg_initialize_profile_and_points_on_signup on auth.users" ) op.execute( """ create trigger trg_initialize_profile_and_points_on_signup after insert on auth.users for each row execute function public.initialize_profile_and_points_on_signup(); """ ) def downgrade() -> None: op.execute( "drop trigger if exists trg_initialize_profile_and_points_on_signup on auth.users" ) op.execute( "drop function if exists public.initialize_profile_and_points_on_signup()" ) op.drop_constraint("ck_points_ledger_biz_binding", "points_ledger", type_="check") op.drop_constraint("ck_points_ledger_biz_type", "points_ledger", type_="check") op.create_check_constraint( "ck_points_ledger_biz_type", "points_ledger", "biz_type = 'chat'", ) op.execute( "delete from points_ledger where change_type = 'register' and biz_id is null" ) op.alter_column( "points_ledger", "biz_type", existing_type=sa.String(length=16), nullable=False ) op.alter_column("points_ledger", "biz_id", existing_type=sa.UUID(), nullable=False)