"""remove redundant reason_code from points ledger metadata Revision ID: 202604030004 Revises: 202604030003 Create Date: 2026-04-03 23:35:00 """ from typing import Sequence, Union from alembic import op revision: str = "202604030004" down_revision: Union[str, Sequence[str], None] = "202604030003" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.drop_constraint( "ck_points_ledger_metadata_common", "points_ledger", type_="check" ) op.drop_constraint( "ck_points_ledger_metadata_register_shape", "points_ledger", type_="check" ) op.drop_constraint( "ck_points_ledger_metadata_consume_shape", "points_ledger", type_="check" ) op.drop_constraint( "ck_points_ledger_metadata_grant_shape", "points_ledger", type_="check" ) op.drop_constraint( "ck_points_ledger_metadata_adjust_shape", "points_ledger", type_="check" ) op.create_check_constraint( "ck_points_ledger_metadata_common", "points_ledger", "metadata->>'schema_version' = '1' and " "metadata->>'operator_type' in ('user', 'system', 'admin') and " "coalesce(metadata->>'run_id', '') <> '' and " "(not (metadata ? 'ext') or jsonb_typeof(metadata->'ext') = 'object')", ) op.create_check_constraint( "ck_points_ledger_metadata_register_shape", "points_ledger", "(change_type <> 'register' or not (metadata ? 'charge'))", ) op.create_check_constraint( "ck_points_ledger_metadata_consume_shape", "points_ledger", "(change_type <> 'consume' or (" "(metadata ? 'charge') and jsonb_typeof(metadata->'charge') = 'object' and " "(metadata->'charge' ? 'message_id') and (metadata->'charge' ? 'message_seq') and " "(metadata->'charge' ? 'model_code') and (metadata->'charge' ? 'input_tokens') and " "(metadata->'charge' ? 'output_tokens') and (metadata->'charge' ? 'cost')))", ) op.create_check_constraint( "ck_points_ledger_metadata_adjust_shape", "points_ledger", "(change_type <> 'adjust' or (" "(metadata ? 'ext') and (metadata->'ext' ? 'ticket_id') and " "coalesce(metadata #>> '{ext,ticket_id}', '') <> ''))", ) op.execute( "update points_ledger set metadata = metadata - 'reason_code' where metadata ? 'reason_code'" ) 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, 60, 0, 60, 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, 60, 60, 'register', null, null, v_event_id, null, jsonb_build_object( 'schema_version', 1, '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; $$; """ ) def downgrade() -> None: op.drop_constraint( "ck_points_ledger_metadata_adjust_shape", "points_ledger", type_="check" ) op.drop_constraint( "ck_points_ledger_metadata_consume_shape", "points_ledger", type_="check" ) op.drop_constraint( "ck_points_ledger_metadata_register_shape", "points_ledger", type_="check" ) op.drop_constraint( "ck_points_ledger_metadata_common", "points_ledger", type_="check" ) op.execute( """ update points_ledger set metadata = jsonb_set( metadata, '{reason_code}', to_jsonb( case change_type when 'register' then 'REGISTER_WELCOME' when 'consume' then 'CHAT_CONSUME' when 'grant' then 'CHAT_GRANT' when 'adjust' then 'CHAT_ADJUST' else 'CHAT_ADJUST' end ), true ) where not (metadata ? 'reason_code'); """ ) op.create_check_constraint( "ck_points_ledger_metadata_common", "points_ledger", "metadata->>'schema_version' = '1' and " "metadata->>'reason_code' in ('REGISTER_WELCOME', 'CHAT_CONSUME', 'CHAT_GRANT', 'CHAT_ADJUST') and " "metadata->>'operator_type' in ('user', 'system', 'admin') and " "coalesce(metadata->>'run_id', '') <> '' and " "(not (metadata ? 'ext') or jsonb_typeof(metadata->'ext') = 'object')", ) op.create_check_constraint( "ck_points_ledger_metadata_register_shape", "points_ledger", "(change_type <> 'register' or (metadata->>'reason_code' = 'REGISTER_WELCOME' and not (metadata ? 'charge')))", ) op.create_check_constraint( "ck_points_ledger_metadata_consume_shape", "points_ledger", "(change_type <> 'consume' or (metadata->>'reason_code' = 'CHAT_CONSUME' and " "(metadata ? 'charge') and jsonb_typeof(metadata->'charge') = 'object' and " "(metadata->'charge' ? 'message_id') and (metadata->'charge' ? 'message_seq') and " "(metadata->'charge' ? 'model_code') and (metadata->'charge' ? 'input_tokens') and " "(metadata->'charge' ? 'output_tokens') and (metadata->'charge' ? 'cost')))", ) op.create_check_constraint( "ck_points_ledger_metadata_grant_shape", "points_ledger", "(change_type <> 'grant' or metadata->>'reason_code' = 'CHAT_GRANT')", ) op.create_check_constraint( "ck_points_ledger_metadata_adjust_shape", "points_ledger", "(change_type <> 'adjust' or (metadata->>'reason_code' = 'CHAT_ADJUST' and " "(metadata ? 'ext') and (metadata->'ext' ? 'ticket_id') and " "coalesce(metadata #>> '{ext,ticket_id}', '') <> ''))", ) 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, 60, 0, 60, 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, 60, 60, '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; $$; """ )