diff --git a/ops/sql/SUPABASE-V17.6.8-DEVELOPER-CONSOLE-UX.sql b/ops/sql/SUPABASE-V17.6.8-DEVELOPER-CONSOLE-UX.sql new file mode 100644 index 0000000..3e46834 --- /dev/null +++ b/ops/sql/SUPABASE-V17.6.8-DEVELOPER-CONSOLE-UX.sql @@ -0,0 +1,236 @@ +-- Caterium v17.6.8 Developer Console UX +-- Persistent company numbers, safe company deletion and grouped production error journal. + +create sequence if not exists public.sun_workspace_company_number_seq; + +alter table public.sun_workspaces + add column if not exists company_number bigint; + +with numbered as ( + select id,row_number() over(order by created_at,id)::bigint as n + from public.sun_workspaces + where company_number is null +), existing as ( + select coalesce(max(company_number),0)::bigint as max_n from public.sun_workspaces +) +update public.sun_workspaces w +set company_number = numbered.n + existing.max_n +from numbered,existing +where w.id=numbered.id and w.company_number is null; + +select setval( + 'public.sun_workspace_company_number_seq', + greatest(1,coalesce((select max(company_number) from public.sun_workspaces),0)), + coalesce((select max(company_number) from public.sun_workspaces),0)>0 +); + +alter table public.sun_workspaces + alter column company_number set default nextval('public.sun_workspace_company_number_seq'); + +update public.sun_workspaces +set company_number=nextval('public.sun_workspace_company_number_seq') +where company_number is null; + +alter table public.sun_workspaces + alter column company_number set not null; + +create unique index if not exists sun_workspaces_company_number_uq + on public.sun_workspaces(company_number); + +create or replace function public.sun_dev_list_companies_v1768() +returns table( + workspace_id uuid, + workspace_name text, + company_number bigint, + created_at timestamptz, + plan_id text, + plan_name text, + status text, + access_mode text, + trial_ends_at timestamptz, + current_period_end timestamptz, + grace_until timestamptz, + member_count bigint, + max_members integer, + owner_email text, + pending_owner_email text +) +language plpgsql +stable +security definer +set search_path='public','auth' +as $$ +begin + perform public.sun_require_platform_admin_aal2(); + return query + select + w.id, + w.name, + w.company_number, + w.created_at, + s.plan_id, + p.name, + s.status, + public.sun_subscription_access_mode(w.id), + s.trial_ends_at, + s.current_period_end, + s.grace_until, + (select count(*) from public.sun_workspace_members m where m.workspace_id=w.id and m.is_active), + p.max_members, + (select u.email::text + from public.sun_workspace_members m + join auth.users u on u.id=m.user_id + where m.workspace_id=w.id and m.role='admin' and m.is_active + order by m.created_at asc + limit 1), + (select i.email + from public.caterium_company_owner_invites i + where i.workspace_id=w.id and i.used_at is null and i.expires_at>now() + order by i.created_at desc + limit 1) + from public.sun_workspaces w + left join public.sun_workspace_subscriptions s on s.workspace_id=w.id + left join public.sun_plans p on p.id=s.plan_id + order by w.company_number; +end; +$$; + +create or replace function public.sun_dev_delete_company_v1768( + p_workspace uuid, + p_confirm_name text +) returns jsonb +language plpgsql +security definer +set search_path='public','auth' +as $$ +declare + v_name text; + v_number bigint; + v_members bigint; + v_orders bigint; + v_clients bigint; + v_catalog bigint; +begin + perform public.sun_require_platform_admin_aal2(); + + select w.name,w.company_number + into v_name,v_number + from public.sun_workspaces w + where w.id=p_workspace + for update; + + if v_name is null then raise exception 'Workspace not found'; end if; + if trim(coalesce(p_confirm_name,'')) <> v_name then raise exception 'Company name confirmation mismatch'; end if; + + select count(*) into v_members from public.sun_workspace_members where workspace_id=p_workspace; + select count(*) into v_orders from public.sun_v17_orders where workspace_id=p_workspace; + select count(*) into v_clients from public.sun_v17_clients where workspace_id=p_workspace; + select count(*) into v_catalog from public.sun_v17_catalog_items where workspace_id=p_workspace; + + perform public.sun_platform_log_event( + 'company.delete', + p_workspace, + null, + jsonb_build_object( + 'company',v_name, + 'company_number',v_number, + 'members',v_members, + 'orders',v_orders, + 'clients',v_clients, + 'catalog_items',v_catalog, + 'auth_accounts_preserved',true + ) + ); + + delete from public.sun_workspaces where id=p_workspace; + + return jsonb_build_object( + 'deleted',true, + 'workspace_id',p_workspace, + 'workspace_name',v_name, + 'company_number',v_number, + 'memberships_removed',v_members, + 'auth_accounts_preserved',true + ); +end; +$$; + +create or replace function public.sun_dev_error_groups_v1768( + p_hours integer default 168, + p_limit integer default 100 +) returns table( + workspace_name text, + app_version text, + level text, + message text, + occurrences bigint, + first_seen timestamptz, + last_seen timestamptz, + sample_stack text, + source_url text +) +language plpgsql +stable +security definer +set search_path='public' +as $$ +declare + v_hours integer:=greatest(1,least(coalesce(p_hours,168),720)); + v_limit integer:=greatest(1,least(coalesce(p_limit,100),200)); +begin + perform public.sun_require_platform_admin_aal2(); + return query + with scoped as ( + select + e.*, + w.name as company_name, + nullif(e.context->>'url','') as event_url + from public.sun_v17_error_events e + left join public.sun_workspaces w on w.id=e.workspace_id + where e.created_at >= now()-make_interval(hours=>v_hours) + and coalesce(e.context->>'url','') !~* '^file:' + ), grouped as ( + select + company_name, + coalesce(app_version,'') as app_version, + coalesce(level,'error') as level, + coalesce(message,'Unknown error') as message, + count(*)::bigint as occurrences, + min(created_at) as first_seen, + max(created_at) as last_seen + from scoped + group by company_name,coalesce(app_version,''),coalesce(level,'error'),coalesce(message,'Unknown error') + ) + select + g.company_name, + nullif(g.app_version,''), + g.level, + g.message, + g.occurrences, + g.first_seen, + g.last_seen, + s.stack, + s.event_url + from grouped g + left join lateral ( + select x.stack,x.event_url + from scoped x + where x.company_name is not distinct from g.company_name + and coalesce(x.app_version,'')=g.app_version + and coalesce(x.level,'error')=g.level + and coalesce(x.message,'Unknown error')=g.message + order by x.created_at desc + limit 1 + ) s on true + order by g.last_seen desc + limit v_limit; +end; +$$; + +revoke all on function public.sun_dev_list_companies_v1768() from public,anon; +revoke all on function public.sun_dev_delete_company_v1768(uuid,text) from public,anon; +revoke all on function public.sun_dev_error_groups_v1768(integer,integer) from public,anon; + +grant execute on function public.sun_dev_list_companies_v1768() to authenticated; +grant execute on function public.sun_dev_delete_company_v1768(uuid,text) to authenticated; +grant execute on function public.sun_dev_error_groups_v1768(integer,integer) to authenticated;