113 lines
9.4 KiB
PL/PgSQL
113 lines
9.4 KiB
PL/PgSQL
-- Missing production RPC contracts reconstructed for the empty Caterium project.
|
||
create table public.caterium_trial_promos (
|
||
id uuid primary key default gen_random_uuid(),code text not null unique,
|
||
client_email text,trial_days integer not null default 14 check(trial_days between 1 and 365),
|
||
plan_id text not null default 'full' references public.sun_plans(id),
|
||
max_uses integer not null default 1 check(max_uses between 1 and 10000),use_count integer not null default 0,
|
||
is_active boolean not null default true,valid_until timestamptz,note text,
|
||
created_by uuid references auth.users(id) on delete set null,created_at timestamptz not null default now(),updated_at timestamptz not null default now()
|
||
);
|
||
create table public.caterium_trial_redemptions (
|
||
id uuid primary key default gen_random_uuid(),promo_id uuid not null references public.caterium_trial_promos(id),
|
||
workspace_id uuid not null unique references public.sun_workspaces(id) on delete cascade,
|
||
user_id uuid not null unique references auth.users(id) on delete cascade,email text,trial_ends_at timestamptz,
|
||
created_at timestamptz not null default now()
|
||
);
|
||
alter table public.caterium_trial_promos enable row level security;
|
||
alter table public.caterium_trial_redemptions enable row level security;
|
||
revoke all on public.caterium_trial_promos,public.caterium_trial_redemptions from anon,authenticated;
|
||
create function public.caterium_normalize_trial_code(p_code text) returns text language sql immutable set search_path=public as $$select upper(regexp_replace(trim(coalesce(p_code,'')),'[[:space:]]','','g'))$$;
|
||
create function public.caterium_trial_promo_preview(p_code text,p_email text default null)
|
||
returns jsonb language plpgsql stable security definer set search_path=public as $$
|
||
declare p public.caterium_trial_promos%rowtype;
|
||
begin
|
||
select * into p from public.caterium_trial_promos where code=public.caterium_normalize_trial_code(p_code);
|
||
if not found or not p.is_active or (p.valid_until is not null and p.valid_until<=now()) or p.use_count>=p.max_uses
|
||
or (p.client_email is not null and p.client_email<>lower(trim(coalesce(p_email,'')))) then
|
||
return jsonb_build_object('valid',false,'reason','Промокод недействителен для этого email или срок его действия истёк');
|
||
end if;
|
||
return jsonb_build_object('valid',true,'trial_days',p.trial_days,'plan',p.plan_id);
|
||
end $$;
|
||
create function public.sun_dev_create_trial_promo(p_code text default null,p_email text default null,p_trial_days integer default 14,p_valid_days integer default 7,p_max_uses integer default 1,p_plan text default 'full',p_note text default null)
|
||
returns jsonb language plpgsql security definer set search_path=public as $$
|
||
declare p public.caterium_trial_promos%rowtype; c text;
|
||
begin
|
||
perform public.sun_require_platform_admin_aal2();
|
||
c:=coalesce(nullif(public.caterium_normalize_trial_code(p_code),''),'CTM-'||upper(replace(gen_random_uuid()::text,'-',''))::varchar(16));
|
||
if c !~ '^[A-Z0-9-]{3,32}$' then raise exception 'Некорректный промокод'; end if;
|
||
if p_valid_days not between 1 and 365 then raise exception 'Некорректный срок'; end if;
|
||
insert into public.caterium_trial_promos(code,client_email,trial_days,valid_until,max_uses,plan_id,note,created_by)
|
||
values(c,nullif(lower(trim(p_email)),''),p_trial_days,now()+make_interval(days=>p_valid_days),p_max_uses,p_plan,p_note,auth.uid()) returning * into p;
|
||
perform public.sun_platform_log_event('trial_promo.create',null,null,jsonb_build_object('promo_id',p.id));
|
||
return jsonb_build_object('promo_id',p.id,'code',p.code,'trial_days',p.trial_days,'valid_until',p.valid_until);
|
||
end $$;
|
||
create function public.sun_dev_list_trial_promos(p_limit integer default 300)
|
||
returns table(promo_id uuid,code text,client_email text,trial_days integer,valid_until timestamptz,max_uses integer,use_count integer,is_active boolean,last_redeemed_at timestamptz,last_workspace_name text,last_redeemed_email text)
|
||
language plpgsql stable security definer set search_path=public as $$
|
||
begin
|
||
perform public.sun_require_platform_admin_aal2();
|
||
return query select p.id,p.code,p.client_email,p.trial_days,p.valid_until,p.max_uses,p.use_count,p.is_active,r.created_at,w.name,r.email
|
||
from public.caterium_trial_promos p left join lateral (select x.* from public.caterium_trial_redemptions x where x.promo_id=p.id order by x.created_at desc limit 1) r on true
|
||
left join public.sun_workspaces w on w.id=r.workspace_id order by p.created_at desc limit greatest(1,least(coalesce(p_limit,300),1000));
|
||
end $$;
|
||
create function public.sun_dev_set_trial_promo_active(p_promo uuid,p_active boolean)
|
||
returns void language plpgsql security definer set search_path=public as $$
|
||
begin
|
||
perform public.sun_require_platform_admin_aal2();
|
||
update public.caterium_trial_promos set is_active=p_active,updated_at=now() where id=p_promo;
|
||
if not found then raise exception 'Промокод не найден'; end if;
|
||
perform public.sun_platform_log_event('trial_promo.set_active',null,null,jsonb_build_object('promo_id',p_promo,'active',p_active));
|
||
end $$;
|
||
|
||
create function public.caterium_platform_create_company(p_name text,p_owner_email text default null,p_plan text default 'full',p_days integer default 30,p_mode text default 'empty')
|
||
returns jsonb language plpgsql security definer set search_path=public,auth as $$
|
||
declare v_ws uuid; v_owner uuid; v_email text:=nullif(lower(trim(p_owner_email)),''); v_token uuid;
|
||
begin
|
||
perform public.sun_require_platform_admin_aal2();
|
||
if p_days not between 1 and 3650 then raise exception 'Invalid subscription duration'; end if;
|
||
select id into v_owner from auth.users where lower(email)=v_email and email_confirmed_at is not null;
|
||
insert into public.sun_workspaces(name,created_by) values(coalesce(nullif(trim(p_name),''),'Новая компания'),coalesce(v_owner,auth.uid())) returning id into v_ws;
|
||
if v_owner is not null then
|
||
insert into public.sun_workspace_members(workspace_id,user_id,role,is_active,permissions,display_name)
|
||
values(v_ws,v_owner,'admin',true,public.sun_role_default_permissions('admin'),split_part(v_email,'@',1));
|
||
elsif v_email is not null then
|
||
insert into public.caterium_company_owner_invites(workspace_id,email) values(v_ws,v_email) returning token into v_token;
|
||
end if;
|
||
insert into public.sun_app_state(workspace_id,client_id) values(v_ws,'platform-bootstrap');
|
||
insert into public.sun_workspace_subscriptions(workspace_id,plan_id,status,current_period_start,current_period_end,grace_until,source)
|
||
values(v_ws,p_plan,'active',now(),now()+make_interval(days=>p_days),now()+make_interval(days=>p_days+7),'platform');
|
||
return jsonb_build_object('workspace_id',v_ws,'owner_user_id',v_owner,'owner_email',v_email,'owner_invite_token',v_token);
|
||
end $$;
|
||
|
||
-- Recover the sun_dev_* names used by the client, preserving the retained
|
||
-- server implementation and requiring AAL2 at every platform boundary.
|
||
do $recovery$
|
||
declare entry record; f record; call_args text; command text;
|
||
begin
|
||
for entry in select * from (values
|
||
('sun_dev_dashboard','sun_platform_dashboard'),('sun_dev_list_activity','sun_platform_list_activity'),
|
||
('sun_dev_list_companies','sun_platform_list_companies_v22'),('sun_dev_list_users','sun_platform_list_users_v22'),
|
||
('sun_dev_list_errors','sun_platform_list_errors_v22'),('sun_dev_support_snapshot','sun_platform_support_snapshot'),
|
||
('sun_dev_workspace_diagnostics','sun_platform_workspace_diagnostics'),('sun_dev_list_workspace_features','sun_platform_list_workspace_features'),
|
||
('sun_dev_log_event','sun_platform_log_event'),('sun_dev_set_plan_feature','sun_platform_set_plan_feature'),
|
||
('sun_dev_set_plan_max_members','sun_platform_set_plan_max_members'),('sun_dev_seed_workspace_catalog','sun_platform_seed_workspace_catalog'),
|
||
('sun_dev_reset_feature_override','sun_platform_reset_feature_override'),('sun_dev_create_company','sun_platform_create_company_v22'),
|
||
('sun_dev_set_subscription','sun_platform_set_subscription'),('sun_dev_set_feature_override','sun_platform_set_feature_override')
|
||
) names(alias_name,source_name) loop
|
||
select p.*,pg_get_function_arguments(p.oid) as args,pg_get_function_result(p.oid) as result into strict f
|
||
from pg_proc p join pg_namespace n on n.oid=p.pronamespace where n.nspname='public' and p.proname=entry.source_name;
|
||
select coalesce(string_agg('$'||i,',' order by i),'') into call_args from generate_series(1,f.pronargs) i;
|
||
command:=case when f.proretset then 'return query select * from' when f.prorettype='void'::regtype then 'perform' else 'return' end;
|
||
execute format('create function public.%I(%s) returns %s language plpgsql security definer set search_path=public,auth as $body$ begin perform public.sun_require_platform_admin_aal2(); %s public.%I(%s); end $body$',entry.alias_name,f.args,f.result,command,entry.source_name,call_args);
|
||
end loop;
|
||
end $recovery$;
|
||
|
||
revoke all on function public.caterium_normalize_trial_code(text),public.caterium_trial_promo_preview(text,text),public.caterium_platform_create_company(text,text,text,integer,text) from public,anon,authenticated;
|
||
grant execute on function public.caterium_trial_promo_preview(text,text) to anon,authenticated;
|
||
do $$declare f record;begin
|
||
for f in select p.oid::regprocedure as signature from pg_proc p join pg_namespace n on n.oid=p.pronamespace where n.nspname='public' and p.proname like 'sun_dev_%' loop
|
||
execute format('revoke all on function %s from public,anon',f.signature);
|
||
execute format('grant execute on function %s to authenticated',f.signature);
|
||
end loop;
|
||
end $$;
|