137 lines
11 KiB
PL/PgSQL
137 lines
11 KiB
PL/PgSQL
-- Reconstructed from the production client and retained SQL, 2026-09-17.
|
|
-- Empty-project recovery only; original v17 foundation was not in Git history.
|
|
create table public.sun_v17_orders (
|
|
workspace_id uuid not null references public.sun_workspaces(id) on delete cascade,
|
|
order_id text not null, data jsonb not null, version bigint not null default 1,
|
|
created_at timestamptz not null default now(), updated_at timestamptz not null default now(),
|
|
updated_by uuid references auth.users(id) on delete set null, primary key(workspace_id,order_id)
|
|
);
|
|
create table public.sun_v17_catalog_items (
|
|
workspace_id uuid not null references public.sun_workspaces(id) on delete cascade,
|
|
item_id text not null, data jsonb not null, version bigint not null default 1,
|
|
created_at timestamptz not null default now(), updated_at timestamptz not null default now(),
|
|
updated_by uuid references auth.users(id) on delete set null, primary key(workspace_id,item_id)
|
|
);
|
|
create table public.sun_v17_clients (
|
|
workspace_id uuid not null references public.sun_workspaces(id) on delete cascade,
|
|
client_key text not null,name text,phone text,latest_address text,data jsonb not null default '{}',
|
|
version bigint not null default 1,created_at timestamptz not null default now(),updated_at timestamptz not null default now(),
|
|
primary key(workspace_id,client_key)
|
|
);
|
|
create table public.sun_v17_settings (
|
|
workspace_id uuid not null references public.sun_workspaces(id) on delete cascade,
|
|
key text not null,value jsonb,version bigint not null default 1,updated_at timestamptz not null default now(),
|
|
primary key(workspace_id,key)
|
|
);
|
|
create table public.sun_v17_workspace_meta (
|
|
workspace_id uuid primary key references public.sun_workspaces(id) on delete cascade,
|
|
schema_version integer not null default 17,last_backup_on date,legacy_revision bigint not null default 0,
|
|
migrated_at timestamptz not null default now(),updated_at timestamptz not null default now()
|
|
);
|
|
create table public.sun_v17_change_events (
|
|
id bigint generated by default as identity primary key,
|
|
workspace_id uuid not null references public.sun_workspaces(id) on delete cascade,
|
|
entity text not null,entity_key text,operation text not null,version bigint,client_id text,
|
|
created_by uuid references auth.users(id) on delete set null,created_at timestamptz not null default now()
|
|
);
|
|
create index sun_v17_changes_workspace_id_idx on public.sun_v17_change_events(workspace_id,id);
|
|
create table public.sun_v17_backups (
|
|
id uuid primary key default gen_random_uuid(),workspace_id uuid not null references public.sun_workspaces(id) on delete cascade,
|
|
kind text not null,label text not null default '',snapshot jsonb not null,
|
|
created_by uuid references auth.users(id) on delete set null,created_at timestamptz not null default now()
|
|
);
|
|
create index sun_v17_backups_workspace_created_idx on public.sun_v17_backups(workspace_id,created_at desc);
|
|
create table public.sun_v17_error_events (
|
|
id uuid primary key default gen_random_uuid(),workspace_id uuid references public.sun_workspaces(id) on delete cascade,
|
|
user_id uuid references auth.users(id) on delete set null,client_id text,app_version text,level text,message text,
|
|
stack text,context jsonb not null default '{}',created_at timestamptz not null default now()
|
|
);
|
|
create index sun_v17_errors_workspace_created_idx on public.sun_v17_error_events(workspace_id,created_at desc);
|
|
create table public.caterium_company_owner_invites (
|
|
token uuid primary key default gen_random_uuid(),workspace_id uuid not null references public.sun_workspaces(id) on delete cascade,
|
|
email text not null,created_at timestamptz not null default now(),expires_at timestamptz not null default now()+interval '7 days',
|
|
used_at timestamptz,used_by uuid references auth.users(id) on delete set null
|
|
);
|
|
|
|
do $$ declare t text; begin
|
|
foreach t in array array['sun_v17_orders','sun_v17_catalog_items','sun_v17_clients','sun_v17_settings','sun_v17_workspace_meta','sun_v17_change_events','sun_v17_backups','sun_v17_error_events','caterium_company_owner_invites'] loop
|
|
execute format('alter table public.%I enable row level security',t);
|
|
execute format('revoke all on public.%I from anon,authenticated',t);
|
|
end loop;
|
|
end $$;
|
|
-- Browser data access uses the permission-checked RPCs. Realtime only reveals an event.
|
|
grant select on public.sun_v17_change_events to authenticated;
|
|
create policy sun_v17_change_read on public.sun_v17_change_events for select to authenticated using(public.sun_member_role(workspace_id) is not null);
|
|
alter publication supabase_realtime add table public.sun_v17_change_events;
|
|
|
|
create function public.sun_my_workspaces()
|
|
returns table(id uuid,name text,role text,display_name text,is_active boolean,permissions jsonb)
|
|
language sql stable security definer set search_path=public as $$
|
|
select w.id,w.name,m.role,m.display_name,m.is_active,public.sun_role_default_permissions(m.role)||coalesce(m.permissions,'{}')
|
|
from public.sun_workspace_members m join public.sun_workspaces w on w.id=m.workspace_id
|
|
where m.user_id=auth.uid() and m.is_active order by w.created_at,w.id
|
|
$$;
|
|
|
|
create function public.sun_v17_mirror_legacy(p_workspace uuid,p_payload jsonb,p_client_id text default null)
|
|
returns void language plpgsql security definer set search_path=public as $$
|
|
declare canonical jsonb; r record; v_data jsonb; v_ids text[]; v_key text; v_rev bigint;
|
|
begin
|
|
if public.sun_member_role(p_workspace) is null and not public.sun_is_platform_admin() then raise exception 'Access denied'; end if;
|
|
perform 1 from public.sun_workspaces where id=p_workspace for update;
|
|
select payload,revision into canonical,v_rev from public.sun_app_state where workspace_id=p_workspace;
|
|
if canonical is null then return; end if;
|
|
-- Always mirror the validated server row. The supplied legacy argument is never trusted.
|
|
for r in select * from (values ('sunOrders','sun_v17_orders','order_id','order'),('sunBoxes','sun_v17_catalog_items','item_id','catalog')) as x(storage_key,table_name,id_column,entity) loop
|
|
v_data:=coalesce(canonical#>array['storage',r.storage_key,'v'],'[]'::jsonb);
|
|
if jsonb_typeof(v_data)<>'array' then raise exception 'Invalid entity array: %',r.storage_key; end if;
|
|
if exists(select 1 from jsonb_array_elements(v_data) e where nullif(e->>'id','') is null) then raise exception 'Entity ID is required'; end if;
|
|
if (select count(*) from jsonb_array_elements(v_data))<>(select count(distinct e->>'id') from jsonb_array_elements(v_data) e) then raise exception 'Duplicate entity ID'; end if;
|
|
select coalesce(array_agg(e->>'id'),'{}') into v_ids from jsonb_array_elements(v_data) e;
|
|
execute format('with gone as (delete from public.%I where workspace_id=$1 and not (%I=any($2)) returning %I,version) insert into public.sun_v17_change_events(workspace_id,entity,entity_key,operation,version,client_id,created_by) select $1,$3,%I,''delete'',version+1,$4,auth.uid() from gone',r.table_name,r.id_column,r.id_column,r.id_column) using p_workspace,v_ids,r.entity,p_client_id;
|
|
execute format('with saved as (insert into public.%I as dest(workspace_id,%I,data,updated_by) select $1,e->>''id'',e,auth.uid() from jsonb_array_elements($2) e on conflict(workspace_id,%I) do update set data=excluded.data,version=dest.version+1,updated_at=now(),updated_by=auth.uid() where dest.data is distinct from excluded.data returning %I,version) insert into public.sun_v17_change_events(workspace_id,entity,entity_key,operation,version,client_id,created_by) select $1,$3,%I,''upsert'',version,$4,auth.uid() from saved',r.table_name,r.id_column,r.id_column,r.id_column,r.id_column) using p_workspace,v_data,r.entity,p_client_id;
|
|
end loop;
|
|
delete from public.sun_v17_settings where workspace_id=p_workspace and not (canonical->'storage' ? key);
|
|
insert into public.sun_v17_settings as dest(workspace_id,key,value)
|
|
select p_workspace,key,value from jsonb_each(coalesce(canonical->'storage','{}')) where key not in ('sunOrders','sunBoxes')
|
|
on conflict(workspace_id,key) do update set value=excluded.value,version=dest.version+1,updated_at=now() where dest.value is distinct from excluded.value;
|
|
insert into public.sun_v17_workspace_meta(workspace_id,legacy_revision) values(p_workspace,v_rev)
|
|
on conflict(workspace_id) do update set legacy_revision=excluded.legacy_revision,updated_at=now();
|
|
end $$;
|
|
|
|
create function public.sun_save_app_state_v17(p_workspace uuid,p_payload jsonb,p_client_id text,p_expected_revision bigint default null)
|
|
returns table(workspace_id uuid,payload jsonb,revision bigint,updated_at timestamptz,client_id text)
|
|
language plpgsql security definer set search_path=public as $$
|
|
declare current_revision bigint;
|
|
begin
|
|
if public.sun_member_role(p_workspace) is null then raise exception 'Access denied'; end if;
|
|
perform 1 from public.sun_workspaces w where w.id=p_workspace for update;
|
|
select s.revision into current_revision from public.sun_app_state s where s.workspace_id=p_workspace;
|
|
if p_expected_revision is not null and coalesce(current_revision,0)<>p_expected_revision then
|
|
raise exception using errcode='40001',message=format('SUN_CONFLICT expected=%s actual=%s',p_expected_revision,coalesce(current_revision,0));
|
|
end if;
|
|
perform public.sun_save_app_state(p_workspace,p_payload,p_client_id);
|
|
perform public.sun_v17_mirror_legacy(p_workspace,p_payload,p_client_id);
|
|
return query select * from public.sun_fetch_app_state(p_workspace);
|
|
end $$;
|
|
|
|
create function public.sun_v17_build_snapshot(p_workspace uuid)
|
|
returns jsonb language sql stable security definer set search_path=public as $$
|
|
select jsonb_build_object('version',17,'legacy',payload,'revision',revision,'created_at',now()) from public.sun_app_state where workspace_id=p_workspace
|
|
$$;
|
|
create function public.sun_v17_prune_backups(p_workspace uuid,p_keep integer default 30)
|
|
returns integer language plpgsql security definer set search_path=public as $$
|
|
declare n integer;
|
|
begin
|
|
if public.sun_member_role(p_workspace) is null and not public.sun_is_platform_admin() then raise exception 'Access denied'; end if;
|
|
delete from public.sun_v17_backups where workspace_id=p_workspace and kind='daily' and id in
|
|
(select id from public.sun_v17_backups where workspace_id=p_workspace and kind='daily' order by created_at desc offset greatest(30,coalesce(p_keep,30)));
|
|
get diagnostics n=row_count; return n;
|
|
end $$;
|
|
|
|
create function public.sun_workspace_access_mode_internal_v28(p_workspace uuid) returns text language sql stable security definer set search_path=public as $$select public.sun_subscription_access_mode(p_workspace)$$;
|
|
create function public.sun_workspace_feature_internal_v28(p_workspace uuid,p_feature text) returns boolean language sql stable security definer set search_path=public as $$select public.sun_workspace_has_feature(p_workspace,p_feature)$$;
|
|
|
|
revoke all on function public.sun_v17_build_snapshot(uuid),public.sun_workspace_access_mode_internal_v28(uuid),public.sun_workspace_feature_internal_v28(uuid,text) from public,anon,authenticated;
|
|
revoke all on function public.sun_my_workspaces(),public.sun_save_app_state_v17(uuid,jsonb,text,bigint),public.sun_v17_mirror_legacy(uuid,jsonb,text),public.sun_v17_prune_backups(uuid,integer) from public,anon;
|
|
grant execute on function public.sun_my_workspaces(),public.sun_save_app_state_v17(uuid,jsonb,text,bigint),public.sun_v17_mirror_legacy(uuid,jsonb,text),public.sun_v17_prune_backups(uuid,integer) to authenticated;
|