208 lines
8.5 KiB
PL/PgSQL
208 lines
8.5 KiB
PL/PgSQL
begin;
|
|
|
|
create table if not exists public.feature_pricing (
|
|
id uuid primary key default gen_random_uuid(),
|
|
feature_key text not null check (feature_key in ('chat.standard', 'chat.premium', 'rectification', 'report.full', 'report.export', 'profile.extra')),
|
|
model_tier text not null check (model_tier in ('standard', 'premium', 'internal')),
|
|
credit_cost integer not null check (credit_cost > 0),
|
|
version integer not null check (version > 0),
|
|
status text not null default 'draft' check (status in ('draft', 'published', 'retired')),
|
|
enabled boolean not null default false,
|
|
effective_from timestamptz,
|
|
effective_to timestamptz,
|
|
created_by uuid references auth.users(id) on delete set null,
|
|
updated_by uuid references auth.users(id) on delete set null,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
unique (feature_key, model_tier, version),
|
|
check (effective_to is null or effective_from is null or effective_to > effective_from)
|
|
);
|
|
|
|
create unique index if not exists feature_pricing_one_draft_idx
|
|
on public.feature_pricing(feature_key, model_tier) where status = 'draft';
|
|
create index if not exists feature_pricing_active_idx
|
|
on public.feature_pricing(feature_key, model_tier, effective_from)
|
|
where status = 'published' and enabled;
|
|
|
|
create or replace function public.resolve_feature_pricing(
|
|
p_feature_key text,
|
|
p_model_id text
|
|
)
|
|
returns table(feature_key text, model_tier text, credit_cost integer, version integer)
|
|
language plpgsql security definer set search_path = ''
|
|
as $$
|
|
declare
|
|
v_model_tier text;
|
|
begin
|
|
if p_feature_key is null or p_feature_key not in ('chat.standard', 'chat.premium', 'rectification', 'report.full', 'report.export', 'profile.extra') then
|
|
raise exception 'feature_pricing_invalid_feature' using errcode = '22023';
|
|
end if;
|
|
if p_model_id is null or btrim(p_model_id) = '' then
|
|
raise exception 'feature_pricing_model_required' using errcode = '22023';
|
|
end if;
|
|
|
|
select v.model_tier into v_model_tier
|
|
from public.model_configs c
|
|
join public.model_config_versions v on v.config_id = c.id
|
|
where c.model_id = btrim(p_model_id)
|
|
and v.status = 'published'
|
|
and v.enabled
|
|
order by v.version desc
|
|
limit 1;
|
|
|
|
if v_model_tier is null then
|
|
raise exception 'feature_pricing_model_unavailable' using errcode = '22023';
|
|
end if;
|
|
|
|
return query
|
|
select p.feature_key, p.model_tier, p.credit_cost, p.version
|
|
from public.feature_pricing p
|
|
where p.feature_key = p_feature_key
|
|
and p.model_tier = v_model_tier
|
|
and p.status = 'published'
|
|
and p.enabled
|
|
and (p.effective_from is null or p.effective_from <= clock_timestamp())
|
|
and (p.effective_to is null or p.effective_to > clock_timestamp())
|
|
order by p.version desc
|
|
limit 1;
|
|
|
|
if not found then
|
|
raise exception 'feature_pricing_missing' using errcode = '22023';
|
|
end if;
|
|
end; $$;
|
|
|
|
create or replace function public.admin_save_feature_pricing_draft(
|
|
p_actor_user_id uuid,
|
|
p_pricing_id uuid,
|
|
p_feature_key text,
|
|
p_model_tier text,
|
|
p_credit_cost integer,
|
|
p_reason text,
|
|
p_request_id text
|
|
)
|
|
returns uuid
|
|
language plpgsql security definer set search_path = ''
|
|
as $$
|
|
declare
|
|
v_id uuid;
|
|
v_version integer;
|
|
begin
|
|
if not public.admin_has_permission(p_actor_user_id, 'billing.products.write') then
|
|
raise exception 'admin_permission_denied' using errcode = '42501';
|
|
end if;
|
|
if char_length(btrim(coalesce(p_reason, ''))) not between 1 and 500 then
|
|
raise exception 'admin_reason_required' using errcode = '22023';
|
|
end if;
|
|
if p_feature_key not in ('chat.standard', 'chat.premium', 'rectification', 'report.full', 'report.export', 'profile.extra')
|
|
or p_model_tier not in ('standard', 'premium', 'internal')
|
|
or p_credit_cost is null or p_credit_cost <= 0 then
|
|
raise exception 'feature_pricing_invalid' using errcode = '22023';
|
|
end if;
|
|
|
|
if p_pricing_id is null then
|
|
select coalesce(max(version), 0) + 1 into v_version
|
|
from public.feature_pricing
|
|
where feature_key = p_feature_key and model_tier = p_model_tier;
|
|
insert into public.feature_pricing(feature_key, model_tier, credit_cost, version, status, enabled, updated_by)
|
|
values (p_feature_key, p_model_tier, p_credit_cost, v_version, 'draft', false, p_actor_user_id)
|
|
returning id into v_id;
|
|
else
|
|
update public.feature_pricing
|
|
set credit_cost = p_credit_cost, updated_by = p_actor_user_id, updated_at = clock_timestamp()
|
|
where id = p_pricing_id and status = 'draft'
|
|
returning id into v_id;
|
|
if v_id is null then
|
|
raise exception 'feature_pricing_draft_not_found' using errcode = '22023';
|
|
end if;
|
|
end if;
|
|
|
|
insert into audit.admin_audit_logs(
|
|
actor_user_id, actor_email, actor_role, action, target_type, target_id,
|
|
after_value, request_id, permission_used, reason
|
|
)
|
|
select p_actor_user_id, lower(btrim(u.email)), 'admin', 'billing.feature_pricing.draft.save',
|
|
'feature_pricing', v_id,
|
|
jsonb_build_object('featureKey', p_feature_key, 'modelTier', p_model_tier, 'creditCost', p_credit_cost),
|
|
btrim(p_request_id), 'billing.products.write', btrim(p_reason)
|
|
from identity.users u where u.id = p_actor_user_id
|
|
on conflict do nothing;
|
|
return v_id;
|
|
end; $$;
|
|
|
|
create or replace function public.admin_publish_feature_pricing(
|
|
p_actor_user_id uuid,
|
|
p_pricing_id uuid,
|
|
p_reason text,
|
|
p_request_id text
|
|
)
|
|
returns uuid
|
|
language plpgsql security definer set search_path = ''
|
|
as $$
|
|
declare
|
|
v_draft public.feature_pricing%rowtype;
|
|
v_old public.feature_pricing%rowtype;
|
|
begin
|
|
if not public.admin_has_permission(p_actor_user_id, 'billing.products.publish') then
|
|
raise exception 'admin_permission_denied' using errcode = '42501';
|
|
end if;
|
|
if char_length(btrim(coalesce(p_reason, ''))) not between 1 and 500 then
|
|
raise exception 'admin_reason_required' using errcode = '22023';
|
|
end if;
|
|
|
|
select * into v_draft from public.feature_pricing where id = p_pricing_id and status = 'draft' for update;
|
|
if not found then raise exception 'feature_pricing_draft_not_found' using errcode = '22023'; end if;
|
|
|
|
select * into v_old
|
|
from public.feature_pricing
|
|
where feature_key = v_draft.feature_key and model_tier = v_draft.model_tier
|
|
and status = 'published'
|
|
order by version desc limit 1 for update;
|
|
|
|
if v_old.id is not null then
|
|
update public.feature_pricing
|
|
set status = 'retired', enabled = false, effective_to = clock_timestamp(), updated_by = p_actor_user_id, updated_at = clock_timestamp()
|
|
where id = v_old.id;
|
|
end if;
|
|
update public.feature_pricing
|
|
set status = 'published', enabled = true, effective_from = clock_timestamp(), effective_to = null,
|
|
updated_by = p_actor_user_id, updated_at = clock_timestamp()
|
|
where id = v_draft.id;
|
|
|
|
insert into audit.admin_audit_logs(
|
|
actor_user_id, actor_email, actor_role, action, target_type, target_id,
|
|
before_value, after_value, request_id, permission_used, reason
|
|
)
|
|
select p_actor_user_id, lower(btrim(u.email)), 'admin', 'billing.feature_pricing.publish',
|
|
'feature_pricing', v_draft.id,
|
|
case when v_old.id is null then '{}'::jsonb else jsonb_build_object('id', v_old.id, 'version', v_old.version, 'status', v_old.status) end,
|
|
jsonb_build_object('featureKey', v_draft.feature_key, 'modelTier', v_draft.model_tier, 'version', v_draft.version, 'creditCost', v_draft.credit_cost),
|
|
btrim(p_request_id), 'billing.products.publish', btrim(p_reason)
|
|
from identity.users u where u.id = p_actor_user_id
|
|
on conflict do nothing;
|
|
return v_draft.id;
|
|
end; $$;
|
|
|
|
alter table public.feature_pricing enable row level security;
|
|
revoke all on table public.feature_pricing from public, anon, authenticated;
|
|
grant all on table public.feature_pricing to service_role;
|
|
|
|
drop policy if exists feature_pricing_public_select on public.feature_pricing;
|
|
|
|
revoke all on function public.resolve_feature_pricing(text, text),
|
|
public.admin_save_feature_pricing_draft(uuid, uuid, text, text, integer, text, text),
|
|
public.admin_publish_feature_pricing(uuid, uuid, text, text)
|
|
from public, anon, authenticated;
|
|
grant execute on function public.resolve_feature_pricing(text, text) to service_role;
|
|
grant execute on function public.admin_save_feature_pricing_draft(uuid, uuid, text, text, integer, text, text),
|
|
public.admin_publish_feature_pricing(uuid, uuid, text, text) to service_role;
|
|
|
|
do $$ begin
|
|
if exists(select 1 from pg_roles where rolname = 'admin_runtime') then
|
|
grant select on table public.feature_pricing to admin_runtime;
|
|
grant execute on function public.admin_save_feature_pricing_draft(uuid, uuid, text, text, integer, text, text),
|
|
public.admin_publish_feature_pricing(uuid, uuid, text, text) to admin_runtime;
|
|
end if;
|
|
end $$;
|
|
|
|
commit;
|