caterium-app/ops/sql/SUPABASE-REGISTRATION-USERS-V27.sql
2026-09-07 15:29:20 +03:00

246 lines
10 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Caterium v17.5.27: clear registration + email-bound employee invitations
alter table public.sun_workspace_invites add column if not exists email text;
alter table public.sun_workspace_invites add column if not exists display_name text;
create index if not exists sun_workspace_invites_pending_email_v27_idx
on public.sun_workspace_invites(workspace_id, lower(email))
where used_at is null;
-- Public Caterium signups and invite signups do not require clicking an email-confirmation link.
-- This keeps the existing Auth signup endpoint/rate limits while marking these app-originated users verified.
create or replace function public.caterium_autoconfirm_signup_v25()
returns trigger
language plpgsql
security definer
set search_path = 'auth','public'
as $$
begin
if coalesce(new.raw_user_meta_data->>'registration_source','') in ('caterium_public_signup','caterium_invite_signup') then
new.email_confirmed_at := coalesce(new.email_confirmed_at, now());
new.raw_user_meta_data := jsonb_set(coalesce(new.raw_user_meta_data,'{}'::jsonb),'{email_verified}','true'::jsonb,true);
end if;
return new;
end;
$$;
create or replace function public.caterium_autoverify_identity_v25()
returns trigger
language plpgsql
security definer
set search_path = 'auth','public'
as $$
begin
if new.provider='email' and exists (
select 1 from auth.users u
where u.id=new.user_id
and coalesce(u.raw_user_meta_data->>'registration_source','') in ('caterium_public_signup','caterium_invite_signup')
) then
new.identity_data := jsonb_set(coalesce(new.identity_data,'{}'::jsonb),'{email_verified}','true'::jsonb,true);
end if;
return new;
end;
$$;
create or replace function public.sun_create_invite_v27(
p_workspace uuid,
p_email text,
p_display_name text default '',
p_role text default 'manager'
)
returns uuid
language plpgsql
security definer
set search_path='public','auth'
as $$
declare
v_token uuid;
v_role text := lower(coalesce(p_role,'manager'));
v_email text := lower(trim(coalesce(p_email,'')));
v_name text := trim(coalesce(p_display_name,''));
v_max integer;
v_active integer;
v_pending integer;
begin
if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if;
if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для добавления пользователей'; end if;
if v_role not in ('admin','manager','kitchen','courier','viewer') then raise exception 'Некорректная роль'; end if;
if v_email='' or v_email !~* '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$' then raise exception 'Введите корректный email сотрудника'; end if;
if v_name='' then v_name:=split_part(v_email,'@',1); end if;
if exists(
select 1
from public.sun_workspace_members m
join auth.users u on u.id=m.user_id
where m.workspace_id=p_workspace and m.is_active=true and lower(coalesce(u.email,''))=v_email
) then
raise exception 'Пользователь с этим email уже добавлен в компанию';
end if;
-- Remove stale invites and replace the previous pending invite for the same email.
delete from public.sun_workspace_invites
where workspace_id=p_workspace and used_at is null
and (expires_at<=now() or lower(coalesce(email,''))=v_email);
select p.max_members into v_max
from public.sun_workspace_subscriptions s
join public.sun_plans p on p.id=s.plan_id
where s.workspace_id=p_workspace;
if v_max is not null then
select count(*)::int into v_active
from public.sun_workspace_members
where workspace_id=p_workspace and is_active=true;
select count(*)::int into v_pending
from public.sun_workspace_invites
where workspace_id=p_workspace and used_at is null and expires_at>now();
if v_active+v_pending>=v_max then
raise exception 'Достигнут лимит пользователей тарифа (%)',v_max;
end if;
end if;
insert into public.sun_workspace_invites(workspace_id,role,permissions,created_by,email,display_name)
values(p_workspace,v_role,public.sun_role_default_permissions(v_role),auth.uid(),v_email,left(v_name,120))
returning token into v_token;
return v_token;
end;
$$;
create or replace function public.sun_list_workspace_invites_v27(p_workspace uuid)
returns table(
token uuid,
email text,
display_name text,
role text,
created_at timestamptz,
expires_at timestamptz,
status text
)
language plpgsql
stable
security definer
set search_path='public'
as $$
begin
if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для просмотра приглашений'; end if;
return query
select i.token,i.email,i.display_name,i.role,i.created_at,i.expires_at,
case when i.expires_at<=now() then 'expired' else 'pending' end::text
from public.sun_workspace_invites i
where i.workspace_id=p_workspace and i.used_at is null
order by i.created_at desc;
end;
$$;
create or replace function public.sun_cancel_invite_v27(p_workspace uuid,p_token uuid)
returns boolean
language plpgsql
security definer
set search_path='public'
as $$
declare v_deleted integer;
begin
if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для отмены приглашения'; end if;
delete from public.sun_workspace_invites
where workspace_id=p_workspace and token=p_token and used_at is null;
get diagnostics v_deleted = row_count;
return v_deleted>0;
end;
$$;
-- Token is the secret. This preview intentionally reveals only the invited company/name/email/role.
create or replace function public.sun_invite_preview_v27(p_token uuid)
returns table(
workspace_id uuid,
workspace_name text,
email text,
display_name text,
role text,
expires_at timestamptz,
is_valid boolean
)
language sql
stable
security definer
set search_path='public'
as $$
select w.id,w.name,i.email,i.display_name,i.role,i.expires_at,
(i.used_at is null and i.expires_at>now()) as is_valid
from public.sun_workspace_invites i
join public.sun_workspaces w on w.id=i.workspace_id
where i.token=p_token
limit 1
$$;
-- Keep the existing RPC name for backward compatibility, but make new email-bound invites safe.
create or replace function public.sun_accept_invite(p_token uuid)
returns uuid
language plpgsql
security definer
set search_path='public','auth'
as $$
declare
v_user uuid := auth.uid();
v_invite public.sun_workspace_invites%rowtype;
v_display text;
v_user_email text;
v_max integer;
v_count integer;
begin
if v_user is null then raise exception 'Сначала войдите в Caterium'; end if;
select * into v_invite from public.sun_workspace_invites where token=p_token for update;
if not found then raise exception 'Приглашение не найдено'; end if;
if v_invite.used_at is not null then raise exception 'Приглашение уже использовано'; end if;
if v_invite.expires_at < now() then raise exception 'Срок действия приглашения истёк'; end if;
select lower(coalesce(email,'')),
coalesce(nullif(v_invite.display_name,''),nullif(raw_user_meta_data->>'name',''),split_part(coalesce(email,'Сотрудник'),'@',1))
into v_user_email,v_display
from auth.users where id=v_user;
if nullif(lower(trim(coalesce(v_invite.email,''))),'') is not null
and lower(trim(v_invite.email))<>v_user_email then
raise exception 'Это приглашение создано для другого email';
end if;
if public.sun_subscription_access_mode(v_invite.workspace_id)<>'full' then raise exception 'Подписка компании неактивна'; end if;
if not public.sun_workspace_has_feature(v_invite.workspace_id,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if;
select p.max_members into v_max
from public.sun_workspace_subscriptions s
join public.sun_plans p on p.id=s.plan_id
where s.workspace_id=v_invite.workspace_id;
select count(*)::int into v_count
from public.sun_workspace_members
where workspace_id=v_invite.workspace_id and is_active=true;
if not exists(select 1 from public.sun_workspace_members where workspace_id=v_invite.workspace_id and user_id=v_user and is_active=true) then
if v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if;
end if;
insert into public.sun_workspace_members(workspace_id,user_id,role,display_name,is_active,permissions,updated_at)
values(v_invite.workspace_id,v_user,v_invite.role,v_display,true,coalesce(v_invite.permissions,public.sun_role_default_permissions(v_invite.role)),now())
on conflict(workspace_id,user_id) do update
set role=excluded.role,
display_name=coalesce(nullif(public.sun_workspace_members.display_name,''),excluded.display_name),
is_active=true,
permissions=excluded.permissions,
updated_at=now();
update public.sun_workspace_invites set used_by=v_user,used_at=now() where token=p_token;
return v_invite.workspace_id;
end;
$$;
revoke all on function public.sun_create_invite_v27(uuid,text,text,text) from public,anon;
revoke all on function public.sun_list_workspace_invites_v27(uuid) from public,anon;
revoke all on function public.sun_cancel_invite_v27(uuid,uuid) from public,anon;
revoke all on function public.sun_invite_preview_v27(uuid) from public;
revoke all on function public.sun_accept_invite(uuid) from public,anon;
grant execute on function public.sun_create_invite_v27(uuid,text,text,text) to authenticated;
grant execute on function public.sun_list_workspace_invites_v27(uuid) to authenticated;
grant execute on function public.sun_cancel_invite_v27(uuid,uuid) to authenticated;
grant execute on function public.sun_invite_preview_v27(uuid) to anon,authenticated;
grant execute on function public.sun_accept_invite(uuid) to authenticated;