Add player bug reports and profile identity features
This commit is contained in:
90
supabase/migrations/20261008071054_bug_reports.sql
Normal file
90
supabase/migrations/20261008071054_bug_reports.sql
Normal file
@@ -0,0 +1,90 @@
|
||||
alter table public.profiles add column name_glow boolean not null default false;
|
||||
create function public.protect_name_glow() returns trigger language plpgsql security invoker set search_path='' as $$
|
||||
begin
|
||||
if ((tg_op='INSERT' and new.name_glow) or (tg_op='UPDATE' and new.name_glow is distinct from old.name_glow))
|
||||
and current_user not in ('postgres','service_role') then raise exception 'Reward permission required'; end if;
|
||||
return new;
|
||||
end; $$;
|
||||
create trigger profiles_protect_glow before insert or update on public.profiles for each row execute function public.protect_name_glow();
|
||||
revoke all on function public.protect_name_glow() from public,anon,authenticated;
|
||||
|
||||
create table public.bug_reports (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
client_id uuid not null unique,
|
||||
owner_id uuid references public.profiles(id) on delete set null,
|
||||
access_hash text not null check(access_hash ~ '^[a-f0-9]{64}$'),
|
||||
display_name text not null,
|
||||
title text not null check(length(title) between 5 and 120),
|
||||
description text not null check(length(description) between 15 and 6000),
|
||||
page_path text not null default '/',
|
||||
state text not null default 'draft' check(state in ('draft','pending','open')),
|
||||
screenshot_expected boolean not null default false,
|
||||
screenshot_path text,
|
||||
linear_asset_url text,
|
||||
linear_identifier text,
|
||||
issue_synced boolean not null default false,
|
||||
glow_requested boolean not null default false,
|
||||
lease_owner text,
|
||||
lease_until timestamptz,
|
||||
retry_at timestamptz,
|
||||
created_at timestamptz not null default clock_timestamp(),
|
||||
updated_at timestamptz not null default clock_timestamp()
|
||||
);
|
||||
create index bug_reports_owner on public.bug_reports(owner_id,created_at desc);
|
||||
create index bug_reports_guest on public.bug_reports(access_hash,created_at desc) where owner_id is null;
|
||||
create table public.bug_report_messages (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
client_id uuid,
|
||||
report_id uuid not null references public.bug_reports(id) on delete cascade,
|
||||
kind text not null check(kind in ('user','staff','system')),
|
||||
body text not null check(length(body) between 1 and 6000),
|
||||
author_name text not null,
|
||||
delivered boolean not null default false,
|
||||
external_id uuid unique,
|
||||
created_at timestamptz not null default clock_timestamp(),
|
||||
unique(report_id,client_id)
|
||||
);
|
||||
create index bug_messages_report on public.bug_report_messages(report_id,created_at,id);
|
||||
alter table public.bug_reports enable row level security;
|
||||
alter table public.bug_report_messages enable row level security;
|
||||
revoke all on public.bug_reports,public.bug_report_messages from public,anon,authenticated;
|
||||
grant all on public.bug_reports,public.bug_report_messages to service_role;
|
||||
|
||||
create function public.claim_bug_sync(p_report uuid,p_worker text) returns boolean language plpgsql security invoker set search_path='' as $$
|
||||
begin
|
||||
update public.bug_reports set lease_owner=p_worker,lease_until=clock_timestamp()+interval '2 minutes'
|
||||
where id=p_report and state<>'draft' and (lease_until is null or lease_until<clock_timestamp())
|
||||
and (retry_at is null or retry_at<clock_timestamp());
|
||||
return found;
|
||||
end; $$;
|
||||
create function public.receive_bug_reply(p_report uuid,p_external uuid,p_author text,p_body text,p_glow boolean default null)
|
||||
returns boolean language plpgsql security invoker set search_path='' as $$
|
||||
declare report public.bug_reports;
|
||||
begin
|
||||
select * into report from public.bug_reports where id=p_report and state<>'draft' for update;
|
||||
if not found then return false; end if;
|
||||
if exists(select 1 from public.bug_report_messages where external_id=p_external) then return false; end if;
|
||||
if p_glow is null and (p_body is null or length(p_body) not between 1 and 6000) then raise exception 'Invalid support reply'; end if;
|
||||
if p_glow is not null then
|
||||
update public.bug_reports set glow_requested=p_glow,updated_at=clock_timestamp() where id=p_report;
|
||||
if report.owner_id is not null then update public.profiles set name_glow=p_glow where id=report.owner_id; end if;
|
||||
end if;
|
||||
insert into public.bug_report_messages(id,report_id,kind,body,author_name,delivered,external_id)
|
||||
values(gen_random_uuid(),p_report,case when p_glow is null then 'staff' else 'system' end,
|
||||
case when p_glow is null then p_body when p_glow then 'Username glow granted.' else 'Username glow removed.' end,
|
||||
left(coalesce(p_author,'Support'),80),true,p_external);
|
||||
return true;
|
||||
end; $$;
|
||||
create function public.claim_bug_report(p_report uuid,p_hash text,p_owner uuid) returns void language plpgsql security invoker set search_path='' as $$
|
||||
declare report public.bug_reports;
|
||||
begin
|
||||
if p_owner is null then raise exception 'Sign in to link this report'; end if;
|
||||
select * into report from public.bug_reports where id=p_report and owner_id is null and access_hash=p_hash for update;
|
||||
if not found then raise exception 'Report not found'; end if;
|
||||
update public.bug_reports set owner_id=p_owner,updated_at=clock_timestamp() where id=p_report;
|
||||
if report.glow_requested then update public.profiles set name_glow=true where id=p_owner; end if;
|
||||
end; $$;
|
||||
revoke all on function public.claim_bug_sync(uuid,text),public.receive_bug_reply(uuid,uuid,text,text,boolean),public.claim_bug_report(uuid,text,uuid) from public,anon,authenticated;
|
||||
grant execute on function public.claim_bug_sync(uuid,text),public.receive_bug_reply(uuid,uuid,text,text,boolean),public.claim_bug_report(uuid,text,uuid) to service_role;
|
||||
insert into storage.buckets(id,name,public,file_size_limit)
|
||||
values('minegeo-support','minegeo-support',false,5242880) on conflict(id) do nothing;
|
||||
67
supabase/migrations/20261008075322_profile_identity.sql
Normal file
67
supabase/migrations/20261008075322_profile_identity.sql
Normal file
@@ -0,0 +1,67 @@
|
||||
alter table public.profiles
|
||||
add column glow_color text not null default '#ffe6a1' check(glow_color ~ '^#[a-fA-F0-9]{6}$'),
|
||||
add column bug_finder boolean not null default false,
|
||||
add column custom_prefix text check(custom_prefix is null or (length(custom_prefix) between 1 and 24 and custom_prefix !~ '[[:cntrl:]]')),
|
||||
add column custom_prefix_icon text not null default 'star' check(custom_prefix_icon in ('star','crown','heart','flame','gem','trophy','sparkles'));
|
||||
alter table public.bug_reports add column bug_confirmed boolean not null default false;
|
||||
update public.profiles set bug_finder=true where name_glow;
|
||||
update public.bug_reports set bug_confirmed=true where glow_requested;
|
||||
|
||||
create function public.protect_profile_identity() returns trigger language plpgsql security invoker set search_path='' as $$
|
||||
begin
|
||||
if current_user not in ('postgres','service_role') and (
|
||||
(tg_op='INSERT' and (new.role<>'contributor' or new.bug_finder or new.custom_prefix is not null or new.custom_prefix_icon<>'star'))
|
||||
or (tg_op='UPDATE' and (new.role is distinct from old.role or new.bug_finder is distinct from old.bug_finder
|
||||
or new.custom_prefix is distinct from old.custom_prefix or new.custom_prefix_icon is distinct from old.custom_prefix_icon))
|
||||
) then raise exception 'Moderator permission required'; end if;
|
||||
if tg_op='UPDATE' and new.glow_color is distinct from old.glow_color and not new.name_glow then
|
||||
raise exception 'Earn a username glow before choosing its color';
|
||||
end if;
|
||||
return new;
|
||||
end; $$;
|
||||
create trigger profiles_protect_identity before insert or update on public.profiles for each row execute function public.protect_profile_identity();
|
||||
revoke all on function public.protect_profile_identity() from public,anon,authenticated;
|
||||
|
||||
create function public.set_user_prefix(p_reviewer uuid,p_user uuid,p_prefix text,p_icon text) returns void
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
begin
|
||||
if not exists(select 1 from public.profiles where id=p_reviewer and role='moderator') then raise exception 'Moderator permission required'; end if;
|
||||
if p_icon not in ('star','crown','heart','flame','gem','trophy','sparkles') or p_icon is null then raise exception 'Invalid prefix icon'; end if;
|
||||
if p_prefix is not null and (length(btrim(p_prefix)) not between 1 and 24 or p_prefix ~ '[[:cntrl:]]') then raise exception 'Invalid custom prefix'; end if;
|
||||
update public.profiles set custom_prefix=case when p_prefix is null then null else btrim(p_prefix) end,custom_prefix_icon=p_icon where id=p_user;
|
||||
if not found then raise exception 'Player not found'; end if;
|
||||
end; $$;
|
||||
revoke all on function public.set_user_prefix(uuid,uuid,text,text) from public,anon,authenticated;
|
||||
grant execute on function public.set_user_prefix(uuid,uuid,text,text) to service_role;
|
||||
|
||||
create or replace function public.receive_bug_reply(p_report uuid,p_external uuid,p_author text,p_body text,p_glow boolean default null)
|
||||
returns boolean language plpgsql security invoker set search_path='' as $$
|
||||
declare report public.bug_reports;
|
||||
begin
|
||||
select * into report from public.bug_reports where id=p_report and state<>'draft' for update;
|
||||
if not found then return false; end if;
|
||||
if exists(select 1 from public.bug_report_messages where external_id=p_external) then return false; end if;
|
||||
if p_glow is null and (p_body is null or length(p_body) not between 1 and 6000) then raise exception 'Invalid support reply'; end if;
|
||||
if p_glow is not null then
|
||||
update public.bug_reports set glow_requested=p_glow,bug_confirmed=bug_confirmed or p_glow,updated_at=clock_timestamp() where id=p_report;
|
||||
if report.owner_id is not null then update public.profiles set name_glow=p_glow,bug_finder=bug_finder or p_glow where id=report.owner_id; end if;
|
||||
end if;
|
||||
insert into public.bug_report_messages(id,report_id,kind,body,author_name,delivered,external_id)
|
||||
values(gen_random_uuid(),p_report,case when p_glow is null then 'staff' else 'system' end,
|
||||
case when p_glow is null then p_body when p_glow then 'Username glow granted.' else 'Username glow removed.' end,
|
||||
left(coalesce(p_author,'Support'),80),true,p_external);
|
||||
return true;
|
||||
end; $$;
|
||||
create or replace function public.claim_bug_report(p_report uuid,p_hash text,p_owner uuid) returns void
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare report public.bug_reports;
|
||||
begin
|
||||
if p_owner is null then raise exception 'Sign in to link this report'; end if;
|
||||
select * into report from public.bug_reports where id=p_report and owner_id is null and access_hash=p_hash for update;
|
||||
if not found then raise exception 'Report not found'; end if;
|
||||
update public.bug_reports set owner_id=p_owner,updated_at=clock_timestamp() where id=p_report;
|
||||
if report.bug_confirmed then update public.profiles set bug_finder=true,name_glow=name_glow or report.glow_requested where id=p_owner; end if;
|
||||
end; $$;
|
||||
|
||||
alter table public.multiplayer_players add column account_id uuid references public.profiles(id) on delete set null;
|
||||
create index multiplayer_players_account on public.multiplayer_players(account_id) where account_id is not null;
|
||||
Reference in New Issue
Block a user