Files
eryao/backend/alembic/versions/20260403_0003_points_register_init_trigger.py
T

164 lines
4.8 KiB
Python
Raw Normal View History

2026-04-03 16:56:47 +08:00
"""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)