Improve MineGeo exploration and mapping features
This commit is contained in:
172
supabase/migrations/20261005063646_panorama_contributions.sql
Normal file
172
supabase/migrations/20261005063646_panorama_contributions.sql
Normal file
@@ -0,0 +1,172 @@
|
||||
-- Contributions reuse the private upload pipeline. Only root maps can be published.
|
||||
alter table public.datasets
|
||||
add column parent_dataset_id uuid references public.datasets(id),
|
||||
add column allow_contributions boolean not null default false,
|
||||
add column contribution_status text check (contribution_status in ('pending','submitted','accepted','rejected')),
|
||||
add column contribution_count integer not null default 0 check (contribution_count between 0 and 24),
|
||||
add column update_state text check (update_state in ('draft','review','rejected'));
|
||||
create index datasets_contribution_parent on public.datasets(parent_dataset_id,owner_id) where parent_dataset_id is not null;
|
||||
|
||||
create function public.contribution_capacity(p_dataset uuid,p_owner uuid) returns jsonb
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare d public.datasets; m jsonb; reserved integer; used integer; pool integer;
|
||||
begin
|
||||
select * into d from public.datasets where id=p_dataset for update;
|
||||
if not found or d.parent_dataset_id is not null or d.state not in ('draft','rejected','published') then raise exception 'Map does not accept contributions at this time'; end if;
|
||||
if d.owner_id<>p_owner and not d.allow_contributions then raise exception 'The owner has disabled player contributions'; end if;
|
||||
select manifest into m from public.dataset_inputs where dataset_id=p_dataset;
|
||||
pool:=jsonb_array_length(coalesce(m->'availableViewpoints',m->'viewpoints'));
|
||||
select coalesce(sum(contribution_count) filter(where contribution_status in ('pending','submitted') and state<>'failed'),0),
|
||||
coalesce(sum(contribution_count) filter(where owner_id=p_owner and (contribution_status='accepted' or (contribution_status in ('pending','submitted') and state<>'failed'))),0)
|
||||
into reserved,used from public.datasets where parent_dataset_id=p_dataset;
|
||||
return jsonb_build_object('id',d.id,'name',d.name,'bounds',d.bounds,'remaining',greatest(0,24-pool-reserved),
|
||||
'playerRemaining',greatest(0,case when d.owner_id=p_owner then 24 else 12 end-used),'isOwner',d.owner_id=p_owner);
|
||||
end; $$;
|
||||
|
||||
create function public.create_upload(p_owner uuid,p_manifest jsonb) returns uuid
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare target uuid; capacity jsonb; server uuid; result uuid; count integer; b jsonb;
|
||||
begin
|
||||
target:=(p_manifest->>'contributionTo')::uuid;
|
||||
count:=jsonb_array_length(p_manifest->'viewpoints');
|
||||
if count<1 or count>24 or (target is null and count<5) then raise exception 'Maps require 5–24 panoramas'; end if;
|
||||
if exists(select 1 from jsonb_array_elements(p_manifest->'viewpoints') p where p ?| array['assetRoot','assetId','contributorId','source']) or p_manifest ? 'availableViewpoints' then raise exception 'Upload contains server-only media provenance'; end if;
|
||||
if target is not null then
|
||||
capacity:=public.contribution_capacity(target,p_owner); b:=capacity->'bounds';
|
||||
if count>(capacity->>'remaining')::integer then raise exception 'Map limit is 24 panoramas, including pending uploads'; end if;
|
||||
if count>(capacity->>'playerRemaining')::integer then raise exception 'Each player may contribute at most 12 panoramas per map'; end if;
|
||||
if exists(select 1 from jsonb_array_elements(p_manifest->'viewpoints') p where
|
||||
(p->>'x')::numeric<(b->>'minX')::numeric or (p->>'x')::numeric>(b->>'maxX')::numeric or
|
||||
(p->>'z')::numeric<(b->>'minZ')::numeric or (p->>'z')::numeric>(b->>'maxZ')::numeric) then raise exception 'Contributed panoramas must be inside the original map bounds'; end if;
|
||||
select server_id into server from public.datasets where id=target;
|
||||
else
|
||||
select id into server from public.catalog_servers where name=p_manifest->>'serverName' limit 1;
|
||||
if server is null then insert into public.catalog_servers(name,address) values(p_manifest->>'serverName',p_manifest->>'serverAddress') returning id into server; end if;
|
||||
end if;
|
||||
insert into public.datasets(owner_id,server_id,name,description,source,bounds,parent_dataset_id,contribution_status,contribution_count)
|
||||
values(p_owner,server,p_manifest->>'name',coalesce(p_manifest->>'description',''),p_manifest->>'source',p_manifest->'bounds',target,
|
||||
case when target is not null then 'pending' end,case when target is not null then count else 0 end) returning id into result;
|
||||
insert into public.dataset_inputs(dataset_id,manifest) values(result,p_manifest);
|
||||
return result;
|
||||
end; $$;
|
||||
|
||||
create or replace function public.complete_job(p_job uuid,p_worker text,p_count integer,p_cover text) returns void
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare j public.upload_jobs; d public.datasets;
|
||||
begin
|
||||
select * into j from public.upload_jobs where id=p_job and lease_owner=p_worker and state='running' and lease_until>now() for update;
|
||||
if not found then raise exception 'Worker lease lost'; end if;
|
||||
select * into d from public.datasets where id=j.dataset_id for update;
|
||||
if p_count<(case when d.parent_dataset_id is null then 5 else 1 end) or p_count>24 then raise exception 'Invalid panorama count'; end if;
|
||||
update public.datasets set state='draft',panorama_count=p_count,map_path=j.dataset_id::text||'/map.webp',cover_path=j.dataset_id::text||'/'||p_cover||'/preview.webp',updated_at=now() where id=j.dataset_id and state='processing';
|
||||
if not found then raise exception 'Dataset processing cancelled'; end if;
|
||||
update public.dataset_inputs set validated=true where dataset_id=j.dataset_id;
|
||||
update public.upload_jobs set state='complete',progress=100,lease_until=null,error=null,updated_at=now() where id=p_job;
|
||||
end; $$;
|
||||
|
||||
-- Lock before selecting locations, so acceptance cannot race with owner edits.
|
||||
create function public.save_draft(p_dataset uuid,p_owner uuid,p_name text,p_description text,p_ids jsonb) returns void
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare d public.datasets; m jsonb; available jsonb; selected jsonb; count integer; first jsonb;
|
||||
begin
|
||||
select * into d from public.datasets where id=p_dataset and owner_id=p_owner for update;
|
||||
if not found then raise exception 'Dataset not found'; end if;
|
||||
if d.state not in ('draft','rejected','published') or d.update_state='review' or d.contribution_status in ('submitted','accepted','rejected') then raise exception 'Draft cannot be edited while awaiting approval'; end if;
|
||||
if length(p_name)<3 or length(p_name)>80 or length(p_description)>2000 then raise exception 'Invalid map details'; end if;
|
||||
select manifest into m from public.dataset_inputs where dataset_id=p_dataset and validated;
|
||||
if m is null then raise exception 'Dataset not validated'; end if;
|
||||
available:=coalesce(m->'availableViewpoints',m->'viewpoints');
|
||||
select coalesce(jsonb_agg(p order by n),'[]') into selected from jsonb_array_elements(available) with ordinality as pts(p,n) where p_ids ? (p->>'id');
|
||||
count:=jsonb_array_length(selected);
|
||||
if count<>jsonb_array_length(p_ids) or count<(case when d.parent_dataset_id is null then 5 else 1 end) or count>24 then raise exception 'Choose unique captured locations within the panorama limit'; end if;
|
||||
m:=m||jsonb_build_object('name',p_name,'description',p_description,'availableViewpoints',available,'viewpoints',selected);
|
||||
update public.dataset_inputs set manifest=m where dataset_id=p_dataset;
|
||||
first:=selected->0;
|
||||
update public.datasets set name=case when state='published' then name else p_name end,
|
||||
description=case when state='published' then description else p_description end,
|
||||
panorama_count=case when state='published' then panorama_count else count end,
|
||||
cover_path=case when state='published' then cover_path else coalesce(first->>'assetRoot',id::text)||'/'||coalesce(first->>'assetId',first->>'id')||'/preview.webp' end,
|
||||
state=case when state='published' then state else 'draft' end,
|
||||
update_state=case when state='published' then 'draft' else null end,rejection_reason=null,updated_at=now() where id=p_dataset;
|
||||
end; $$;
|
||||
|
||||
create function public.submit_draft(p_dataset uuid,p_owner uuid) returns void
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare d public.datasets; count integer;
|
||||
begin
|
||||
select * into d from public.datasets where id=p_dataset and owner_id=p_owner for update;
|
||||
if not found then raise exception 'Dataset not found'; end if;
|
||||
if d.state not in ('draft','rejected','published') or (d.state='published' and coalesce(d.update_state,'') not in ('draft','rejected')) or d.contribution_status in ('submitted','accepted','rejected') then raise exception 'Draft is not ready for submission'; end if;
|
||||
select jsonb_array_length(manifest->'viewpoints') into count from public.dataset_inputs where dataset_id=p_dataset and validated;
|
||||
if count is null or count<(case when d.parent_dataset_id is null then 5 else 1 end) or count>24 then raise exception 'Invalid panorama count'; end if;
|
||||
update public.datasets set state=case when state='published' then state else 'review' end,
|
||||
update_state=case when state='published' then 'review' end,
|
||||
contribution_status=case when parent_dataset_id is not null then 'submitted' end,updated_at=now() where id=p_dataset;
|
||||
end; $$;
|
||||
|
||||
create function public.set_contributions(p_dataset uuid,p_owner uuid,p_allowed boolean) returns void
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
begin
|
||||
perform 1 from public.datasets where id=p_dataset and owner_id=p_owner and parent_dataset_id is null for update;
|
||||
if not found then raise exception 'Map owner permission required'; end if;
|
||||
update public.datasets set allow_contributions=p_allowed,updated_at=now() where id=p_dataset;
|
||||
end; $$;
|
||||
|
||||
create function public.review_contribution(p_dataset uuid,p_contribution uuid,p_owner uuid,p_decision text,p_reason text default null) returns void
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare d public.datasets; c public.datasets; m jsonb; cm jsonb; added jsonb; pool jsonb; chosen jsonb;
|
||||
begin
|
||||
select * into d from public.datasets where id=p_dataset and owner_id=p_owner and parent_dataset_id is null for update;
|
||||
if not found then raise exception 'Map owner permission required'; end if;
|
||||
if d.state not in ('draft','rejected','published') or d.update_state='review' then raise exception 'Wait until the current map review finishes'; end if;
|
||||
select * into c from public.datasets where id=p_contribution and parent_dataset_id=p_dataset for update;
|
||||
if not found or c.contribution_status<>'submitted' or c.state<>'review' then raise exception 'Contribution is not awaiting owner approval'; end if;
|
||||
if p_decision='reject' then
|
||||
if coalesce(length(trim(p_reason)),0)=0 then raise exception 'Provide feedback for the contributor'; end if;
|
||||
update public.datasets set state='rejected',contribution_status='rejected',rejection_reason=p_reason,updated_at=now() where id=c.id;
|
||||
return;
|
||||
end if;
|
||||
if p_decision<>'accept' then raise exception 'Invalid contribution decision'; end if;
|
||||
select manifest into cm from public.dataset_inputs where dataset_id=c.id and validated;
|
||||
select manifest into m from public.dataset_inputs where dataset_id=d.id and validated;
|
||||
if cm is null or m is null then raise exception 'Dataset not validated'; end if;
|
||||
select jsonb_agg(p||jsonb_build_object('id','c'||replace(c.id::text,'-','')||'_'||n,'assetRoot',c.id,'assetId',p->>'id','contributorId',c.owner_id,'source',c.source) order by n)
|
||||
into added from jsonb_array_elements(cm->'viewpoints') with ordinality as pts(p,n);
|
||||
pool:=coalesce(m->'availableViewpoints',m->'viewpoints')||added;
|
||||
if jsonb_array_length(pool)>24 then raise exception 'Map limit is 24 panoramas'; end if;
|
||||
if c.owner_id<>d.owner_id and (select coalesce(sum(contribution_count),0) from public.datasets where parent_dataset_id=d.id and owner_id=c.owner_id and contribution_status='accepted')+jsonb_array_length(added)>12 then raise exception 'Each player may contribute at most 12 panoramas per map'; end if;
|
||||
chosen:=(m->'viewpoints')||added;
|
||||
update public.dataset_inputs set manifest=m||jsonb_build_object('availableViewpoints',pool,'viewpoints',chosen) where dataset_id=d.id;
|
||||
update public.datasets set state=case when state='published' then state else 'draft' end,
|
||||
update_state=case when state='published' then 'draft' end,
|
||||
panorama_count=case when state='published' then panorama_count else jsonb_array_length(chosen) end,rejection_reason=null,updated_at=now() where id=d.id;
|
||||
update public.datasets set contribution_status='accepted',contribution_count=jsonb_array_length(added),updated_at=now() where id=c.id;
|
||||
end; $$;
|
||||
|
||||
create or replace function public.publish_dataset(p_dataset uuid,p_reviewer uuid,p_decision text,p_reason text default null) returns void
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare d public.datasets; m jsonb; rev uuid; first jsonb; count integer;
|
||||
begin
|
||||
if not exists(select 1 from public.profiles where id=p_reviewer and role='moderator') then raise exception 'Moderator permission required'; end if;
|
||||
select * into d from public.datasets where id=p_dataset and parent_dataset_id is null for update;
|
||||
if not found then raise exception 'Map not found'; end if;
|
||||
if p_decision='approve' then
|
||||
if not(d.state='review' or (d.state='published' and coalesce(d.update_state,'')='review')) then raise exception 'Map is not ready for publication'; end if;
|
||||
select manifest into m from public.dataset_inputs where dataset_id=p_dataset and validated;
|
||||
count:=jsonb_array_length(m->'viewpoints'); first:=m->'viewpoints'->0;
|
||||
if m is null or count<5 or count>24 then raise exception 'Map requires 5–24 validated panoramas'; end if;
|
||||
insert into public.revisions(dataset_id,manifest,asset_root) values(p_dataset,m,p_dataset::text) returning id into rev;
|
||||
update public.datasets set state='published',revision_id=rev,update_state=null,name=m->>'name',description=m->>'description',panorama_count=count,
|
||||
cover_path=coalesce(first->>'assetRoot',id::text)||'/'||coalesce(first->>'assetId',first->>'id')||'/preview.webp',rejection_reason=null,updated_at=now() where id=p_dataset;
|
||||
elsif p_decision='reject' and (d.state='review' or (d.state='published' and d.update_state='review')) then
|
||||
if coalesce(length(trim(p_reason)),0)=0 then raise exception 'Provide a rejection reason'; end if;
|
||||
update public.datasets set state=case when state='published' then state else 'rejected' end,update_state=case when state='published' then 'rejected' end,rejection_reason=p_reason,updated_at=now() where id=p_dataset;
|
||||
elsif p_decision='unpublish' and d.state='published' then
|
||||
update public.datasets set state='unpublished',update_state=null,updated_at=now() where id=p_dataset;
|
||||
else raise exception 'Invalid review transition'; end if;
|
||||
insert into public.review_decisions(dataset_id,reviewer_id,decision,reason) values(p_dataset,p_reviewer,p_decision,p_reason);
|
||||
end; $$;
|
||||
|
||||
revoke execute on function public.contribution_capacity(uuid,uuid),public.create_upload(uuid,jsonb),public.save_draft(uuid,uuid,text,text,jsonb),public.submit_draft(uuid,uuid),public.set_contributions(uuid,uuid,boolean),public.review_contribution(uuid,uuid,uuid,text,text) from public,anon,authenticated;
|
||||
grant execute on function public.contribution_capacity(uuid,uuid),public.create_upload(uuid,jsonb),public.save_draft(uuid,uuid,text,text,jsonb),public.submit_draft(uuid,uuid),public.set_contributions(uuid,uuid,boolean),public.review_contribution(uuid,uuid,uuid,text,text) to service_role;
|
||||
|
||||
Reference in New Issue
Block a user