Add tournament and multiplayer community chat moderation
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 12m17s
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 12m17s
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
-- Event-scoped moderation and chat are private to the trusted server.
|
||||
create table public.tournament_moderators (
|
||||
tournament_id uuid not null references public.tournaments(id) on delete cascade,
|
||||
account_id uuid not null references public.profiles(id) on delete cascade,
|
||||
primary key(tournament_id,account_id)
|
||||
);
|
||||
create table public.tournament_bans (
|
||||
tournament_id uuid not null references public.tournaments(id) on delete cascade,
|
||||
account_id uuid not null references public.profiles(id) on delete cascade,
|
||||
primary key(tournament_id,account_id)
|
||||
);
|
||||
create table public.tournament_tags (
|
||||
tournament_id uuid not null references public.tournaments(id) on delete cascade,
|
||||
account_id uuid not null references public.profiles(id) on delete cascade,
|
||||
tag text not null check(length(tag) between 1 and 32 and tag=btrim(tag) and tag !~ '[[:cntrl:]]' and tag !~ U&'[\0080-\009F\202A-\202E\2066-\2069]'),
|
||||
primary key(tournament_id,account_id)
|
||||
);
|
||||
alter table public.tournaments add column chat_slow_seconds integer not null default 0 check(chat_slow_seconds between 0 and 300);
|
||||
create table public.tournament_messages (
|
||||
id bigint generated always as identity primary key,
|
||||
tournament_id uuid not null references public.tournaments(id) on delete cascade,
|
||||
account_id uuid not null references public.profiles(id) on delete cascade,
|
||||
client_id uuid not null, body text not null check(length(btrim(body)) between 1 and 500
|
||||
and body !~ '[[:cntrl:]]' and body !~ U&'[\0080-\009F\202A-\202E\2066-\2069]'),
|
||||
created_at timestamptz not null default clock_timestamp(),
|
||||
unique(tournament_id,account_id,client_id)
|
||||
);
|
||||
create index tournament_messages_history on public.tournament_messages(tournament_id,id desc);
|
||||
create index tournament_messages_rate on public.tournament_messages(tournament_id,account_id,created_at desc);
|
||||
alter table public.tournament_moderators enable row level security;
|
||||
alter table public.tournament_bans enable row level security;
|
||||
alter table public.tournament_tags enable row level security;
|
||||
alter table public.tournament_messages enable row level security;
|
||||
revoke all on public.tournament_moderators,public.tournament_bans,public.tournament_tags,public.tournament_messages from public,anon,authenticated;
|
||||
revoke all on sequence public.tournament_messages_id_seq from public,anon,authenticated;
|
||||
grant all on public.tournament_moderators,public.tournament_bans,public.tournament_tags,public.tournament_messages to service_role;
|
||||
grant usage,select on sequence public.tournament_messages_id_seq to service_role;
|
||||
|
||||
create function public.guard_tournament_ban() returns trigger language plpgsql security invoker set search_path='' as $$
|
||||
begin
|
||||
if exists(select 1 from public.tournament_bans where tournament_id=new.tournament_id and account_id=new.account_id) then
|
||||
raise exception 'You are banned from this tournament';
|
||||
end if;
|
||||
return new;
|
||||
end; $$;
|
||||
create trigger guard_tournament_ban before insert on public.tournament_members for each row execute function public.guard_tournament_ban();
|
||||
create function public.clear_tournament_member_roles() returns trigger language plpgsql security invoker set search_path='' as $$
|
||||
begin
|
||||
delete from public.tournament_moderators where tournament_id=old.tournament_id and account_id=old.account_id;
|
||||
delete from public.tournament_tags where tournament_id=old.tournament_id and account_id=old.account_id;
|
||||
return old;
|
||||
end; $$;
|
||||
create trigger clear_tournament_member_roles after delete on public.tournament_members for each row execute function public.clear_tournament_member_roles();
|
||||
|
||||
create function public.tournament_community(p_tournament uuid,p_account uuid,p_action text,p_payload jsonb default '{}') returns jsonb
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare event public.tournaments; target uuid; moderator boolean; owner boolean; seconds integer; label text;
|
||||
begin
|
||||
select * into event from public.tournaments where id=p_tournament for update;
|
||||
if not found then raise exception 'Tournament not found'; end if;
|
||||
if not exists(select 1 from public.profiles where id=p_account and account_status='active') then raise exception 'Active account required'; end if;
|
||||
if not exists(select 1 from public.tournament_members where tournament_id=p_tournament and account_id=p_account) then raise exception 'Tournament membership required'; end if;
|
||||
owner := p_account=event.owner_id;
|
||||
moderator := owner or exists(select 1 from public.tournament_moderators where tournament_id=p_tournament and account_id=p_account);
|
||||
if p_action='view' then null;
|
||||
elsif not moderator then raise exception 'Tournament moderator permission required';
|
||||
elsif event.status in('finished','cancelled') then raise exception 'Tournament has ended';
|
||||
elsif p_action='slow' then
|
||||
seconds:=(p_payload->>'seconds')::integer;
|
||||
if seconds is null or seconds not between 0 and 300 then raise exception 'Invalid slow mode'; end if;
|
||||
update public.tournaments set chat_slow_seconds=seconds where id=p_tournament;
|
||||
elsif p_action in('admin','unadmin','kick','ban','unban','tag') then
|
||||
target:=(p_payload->>'accountId')::uuid;
|
||||
if target is null or target=event.owner_id or target=p_account then raise exception 'Cannot moderate the owner or yourself'; end if;
|
||||
if p_action in('admin','unadmin') and not owner then raise exception 'Only the owner can manage moderators'; end if;
|
||||
if not owner and exists(select 1 from public.tournament_moderators where tournament_id=p_tournament and account_id=target) then raise exception 'Only the owner can moderate a moderator'; end if;
|
||||
if p_action='admin' then
|
||||
if not exists(select 1 from public.tournament_members where tournament_id=p_tournament and account_id=target) then raise exception 'Player must join first'; end if;
|
||||
insert into public.tournament_moderators values(p_tournament,target) on conflict do nothing;
|
||||
elsif p_action='unadmin' then delete from public.tournament_moderators where tournament_id=p_tournament and account_id=target;
|
||||
elsif p_action='unban' then delete from public.tournament_bans where tournament_id=p_tournament and account_id=target;
|
||||
elsif p_action='tag' then
|
||||
if not exists(select 1 from public.tournament_members where tournament_id=p_tournament and account_id=target) then raise exception 'Player must join first'; end if;
|
||||
label:=btrim(p_payload->>'tag');
|
||||
if label is null or length(label)>32 or label ~ '[[:cntrl:]]' or label ~ U&'[\0080-\009F\202A-\202E\2066-\2069]' then raise exception 'Invalid tag'; end if;
|
||||
if label='' then delete from public.tournament_tags where tournament_id=p_tournament and account_id=target;
|
||||
else insert into public.tournament_tags values(p_tournament,target,label) on conflict(tournament_id,account_id) do update set tag=excluded.tag; end if;
|
||||
else
|
||||
if not exists(select 1 from public.tournament_members where tournament_id=p_tournament and account_id=target) then raise exception 'Player must join first'; end if;
|
||||
if p_action='ban' then insert into public.tournament_bans values(p_tournament,target) on conflict do nothing; end if;
|
||||
delete from public.tournament_moderators where tournament_id=p_tournament and account_id=target;
|
||||
update public.multiplayer_players set has_left=true where account_id=target and room_id in(select room_id from public.tournament_rounds where tournament_id=p_tournament);
|
||||
delete from public.tournament_members where tournament_id=p_tournament and account_id=target;
|
||||
end if;
|
||||
else raise exception 'Invalid tournament action'; end if;
|
||||
return jsonb_build_object('owner',owner,'moderator',moderator,'slowSeconds',event.chat_slow_seconds,
|
||||
'moderators',coalesce((select jsonb_agg(account_id) from public.tournament_moderators where tournament_id=p_tournament),'[]'::jsonb),
|
||||
'tags',coalesce((select jsonb_object_agg(account_id::text,tag) from public.tournament_tags where tournament_id=p_tournament),'{}'::jsonb),
|
||||
'bans',case when moderator then coalesce((select jsonb_agg(jsonb_build_object('accountId',b.account_id,'name',p.display_name)) from public.tournament_bans b join public.profiles p on p.id=b.account_id where b.tournament_id=p_tournament),'[]'::jsonb) else '[]'::jsonb end);
|
||||
end; $$;
|
||||
|
||||
create function public.tournament_chat(p_tournament uuid,p_account uuid,p_text text default null,p_client_id uuid default null) returns jsonb
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare event public.tournaments; player public.profiles; delay_seconds integer;
|
||||
begin
|
||||
select * into event from public.tournaments where id=p_tournament for update;
|
||||
if not found then raise exception 'Tournament not found'; end if;
|
||||
select * into player from public.profiles where id=p_account and account_status='active';
|
||||
if not found or not exists(select 1 from public.tournament_members where tournament_id=p_tournament and account_id=p_account) then raise exception 'Tournament membership required'; end if;
|
||||
if event.status='cancelled' then raise exception 'Chat is unavailable'; end if;
|
||||
if p_text is not null or p_client_id is not null then
|
||||
if event.status='finished' then raise exception 'Tournament has ended'; end if;
|
||||
if p_text is null or p_client_id is null or length(btrim(p_text)) not between 1 and 500 or p_text ~ '[[:cntrl:]]' or p_text ~ U&'[\0080-\009F\202A-\202E\2066-\2069]' then raise exception 'Invalid chat message'; end if;
|
||||
if not exists(select 1 from public.tournament_messages where tournament_id=p_tournament and account_id=p_account and client_id=p_client_id) then
|
||||
delay_seconds:=case when p_account=event.owner_id or exists(select 1 from public.tournament_moderators where tournament_id=p_tournament and account_id=p_account) then 1 else greatest(1,event.chat_slow_seconds) end;
|
||||
if exists(select 1 from public.tournament_messages where tournament_id=p_tournament and account_id=p_account and created_at>clock_timestamp()-make_interval(secs=>delay_seconds)) or
|
||||
(select count(*) from public.tournament_messages where tournament_id=p_tournament and account_id=p_account and created_at>clock_timestamp()-interval '1 minute')>=30 then raise exception 'Chat slow mode: wait before sending another message'; end if;
|
||||
insert into public.tournament_messages(tournament_id,account_id,client_id,body) values(p_tournament,p_account,p_client_id,btrim(p_text));
|
||||
end if;
|
||||
end if;
|
||||
return jsonb_build_object('messages',coalesce((select jsonb_agg(jsonb_build_object('id',m.id::text,'playerId',m.account_id,'name',p.display_name,'text',m.body,'createdAt',m.created_at,'tag',tag.tag) order by m.id)
|
||||
from (select * from public.tournament_messages where tournament_id=p_tournament order by id desc limit 100) m join public.profiles p on p.id=m.account_id left join public.tournament_tags tag on tag.tournament_id=m.tournament_id and tag.account_id=m.account_id),'[]'::jsonb));
|
||||
end; $$;
|
||||
revoke all on function public.guard_tournament_ban(),public.clear_tournament_member_roles(),public.tournament_community(uuid,uuid,text,jsonb),public.tournament_chat(uuid,uuid,text,uuid) from public,anon,authenticated;
|
||||
grant execute on function public.guard_tournament_ban(),public.clear_tournament_member_roles(),public.tournament_community(uuid,uuid,text,jsonb),public.tournament_chat(uuid,uuid,text,uuid) to service_role;
|
||||
notify pgrst,'reload schema';
|
||||
106
supabase/migrations/20261027061048_party_community_controls.sql
Normal file
106
supabase/migrations/20261027061048_party_community_controls.sql
Normal file
@@ -0,0 +1,106 @@
|
||||
create table public.party_moderators (
|
||||
room_id uuid not null references public.multiplayer_rooms(id) on delete cascade,
|
||||
player_id uuid not null references public.multiplayer_players(id) on delete cascade,
|
||||
primary key(room_id,player_id)
|
||||
);
|
||||
create table public.party_bans (
|
||||
id uuid primary key default gen_random_uuid(), room_id uuid not null references public.multiplayer_rooms(id) on delete cascade,
|
||||
access_hash text not null, account_id uuid references public.profiles(id) on delete cascade, display_name text not null,
|
||||
unique(room_id,access_hash)
|
||||
);
|
||||
create index party_bans_account on public.party_bans(room_id,account_id) where account_id is not null;
|
||||
create table public.party_tags (
|
||||
room_id uuid not null references public.multiplayer_rooms(id) on delete cascade,
|
||||
player_id uuid not null references public.multiplayer_players(id) on delete cascade,
|
||||
tag text not null check(length(tag) between 1 and 32 and tag=btrim(tag) and tag !~ '[[:cntrl:]]' and tag !~ U&'[\0080-\009F\202A-\202E\2066-\2069]'),
|
||||
primary key(room_id,player_id)
|
||||
);
|
||||
alter table public.multiplayer_rooms add column chat_slow_seconds integer not null default 0 check(chat_slow_seconds between 0 and 300);
|
||||
alter table public.party_moderators enable row level security;
|
||||
alter table public.party_bans enable row level security;
|
||||
alter table public.party_tags enable row level security;
|
||||
revoke all on public.party_moderators,public.party_bans,public.party_tags from public,anon,authenticated;
|
||||
grant all on public.party_moderators,public.party_bans,public.party_tags to service_role;
|
||||
|
||||
create or replace function public.join_multiplayer_room(p_room uuid,p_access_hash text,p_owner uuid,p_name text)
|
||||
returns jsonb language plpgsql security invoker set search_path='' as $$
|
||||
declare account_name text; result jsonb;
|
||||
begin
|
||||
perform 1 from public.multiplayer_rooms where id=p_room for update;
|
||||
if exists(select 1 from public.party_bans where room_id=p_room and (access_hash=p_access_hash or (p_owner is not null and account_id=p_owner))) then raise exception 'You are banned from this party'; end if;
|
||||
if p_owner is not null then
|
||||
select display_name into account_name from public.profiles where id=p_owner;
|
||||
if not found then raise exception 'Account not found'; end if;
|
||||
else account_name:=p_name; end if;
|
||||
result:=public.multiplayer_action(p_room,p_access_hash,'join',jsonb_build_object('name',account_name));
|
||||
if p_owner is not null then update public.multiplayer_players set account_id=p_owner where room_id=p_room and access_hash=p_access_hash and account_id is null and not has_left; end if;
|
||||
return result;
|
||||
end; $$;
|
||||
|
||||
create function public.party_community(p_room uuid,p_access_hash text,p_action text,p_payload jsonb default '{}') returns jsonb
|
||||
language plpgsql security invoker set search_path='' as $$
|
||||
declare room public.multiplayer_rooms; actor public.multiplayer_players; target public.multiplayer_players; mod boolean; owner boolean; target_id uuid; seconds integer; label text;
|
||||
begin
|
||||
select * into room from public.multiplayer_rooms where id=p_room for update;
|
||||
if not found or room.tournament_id is not null then raise exception 'Party not found'; end if;
|
||||
select * into actor from public.multiplayer_players where room_id=p_room and access_hash=p_access_hash and not has_left;
|
||||
if not found then raise exception 'Room membership permission required'; end if;
|
||||
owner:=actor.id=room.host_player_id;
|
||||
mod:=owner or exists(select 1 from public.party_moderators where room_id=p_room and player_id=actor.id);
|
||||
if p_action='view' then null;
|
||||
elsif not mod then raise exception 'Party moderator permission required';
|
||||
elsif room.status in('finished','closed') then raise exception 'Party has ended';
|
||||
elsif p_action='slow' then
|
||||
seconds:=(p_payload->>'seconds')::integer;
|
||||
if seconds is null or seconds not between 0 and 300 then raise exception 'Invalid slow mode'; end if;
|
||||
update public.multiplayer_rooms set chat_slow_seconds=seconds where id=p_room returning * into room;
|
||||
elsif p_action='unban' then
|
||||
target_id:=(p_payload->>'banId')::uuid;
|
||||
delete from public.party_bans where room_id=p_room and id=target_id;
|
||||
elsif p_action in('admin','unadmin','kick','ban','tag') then
|
||||
target_id:=(p_payload->>'playerId')::uuid;
|
||||
select * into target from public.multiplayer_players where room_id=p_room and id=target_id and not has_left;
|
||||
if not found or target.id=actor.id or target.id=room.host_player_id then raise exception 'Cannot moderate the owner or yourself'; end if;
|
||||
if p_action in('admin','unadmin') and not owner then raise exception 'Only the owner can manage moderators'; end if;
|
||||
if not owner and exists(select 1 from public.party_moderators where room_id=p_room and player_id=target.id) then raise exception 'Only the owner can moderate a moderator'; end if;
|
||||
if p_action='admin' then insert into public.party_moderators values(p_room,target.id) on conflict do nothing;
|
||||
elsif p_action='unadmin' then delete from public.party_moderators where room_id=p_room and player_id=target.id;
|
||||
elsif p_action='tag' then
|
||||
label:=btrim(p_payload->>'tag');
|
||||
if label is null or length(label)>32 or label ~ '[[:cntrl:]]' or label ~ U&'[\0080-\009F\202A-\202E\2066-\2069]' then raise exception 'Invalid tag'; end if;
|
||||
if label='' then delete from public.party_tags where room_id=p_room and player_id=target.id;
|
||||
else insert into public.party_tags values(p_room,target.id,label) on conflict(room_id,player_id) do update set tag=excluded.tag; end if;
|
||||
else
|
||||
if p_action='ban' then insert into public.party_bans(room_id,access_hash,account_id,display_name) values(p_room,target.access_hash,target.account_id,target.display_name) on conflict(room_id,access_hash) do nothing; end if;
|
||||
delete from public.party_moderators where room_id=p_room and player_id=target.id;
|
||||
if room.status='lobby' then delete from public.multiplayer_players where id=target.id;
|
||||
else update public.multiplayer_players set has_left=true where id=target.id; end if;
|
||||
end if;
|
||||
else raise exception 'Invalid party action'; end if;
|
||||
return jsonb_build_object('owner',owner,'moderator',mod,'slowSeconds',room.chat_slow_seconds,
|
||||
'moderators',coalesce((select jsonb_agg(player_id) from public.party_moderators where room_id=p_room),'[]'::jsonb),
|
||||
'tags',coalesce((select jsonb_object_agg(player_id::text,tag) from public.party_tags where room_id=p_room),'{}'::jsonb),
|
||||
'bans',case when mod then coalesce((select jsonb_agg(jsonb_build_object('id',id,'name',display_name)) from public.party_bans where room_id=p_room),'[]'::jsonb) else '[]'::jsonb end);
|
||||
end; $$;
|
||||
|
||||
-- The existing function remains the only writer; serialize slow-mode checks under the same room lock.
|
||||
alter function public.multiplayer_chat(uuid,text,text,uuid) rename to multiplayer_chat_unchecked;
|
||||
create function public.multiplayer_chat(p_room uuid,p_access_hash text,p_text text default null,p_client_id uuid default null)
|
||||
returns jsonb language plpgsql security invoker set search_path='' as $$
|
||||
declare room public.multiplayer_rooms; player public.multiplayer_players; result jsonb;
|
||||
begin
|
||||
select * into room from public.multiplayer_rooms where id=p_room for update;
|
||||
select * into player from public.multiplayer_players where room_id=p_room and access_hash=p_access_hash and not has_left;
|
||||
if player.id is not null and p_text is not null and room.chat_slow_seconds>0 and player.id<>room.host_player_id
|
||||
and not exists(select 1 from public.party_moderators where room_id=p_room and player_id=player.id)
|
||||
and not exists(select 1 from public.multiplayer_messages where room_id=p_room and player_id=player.id and client_id=p_client_id)
|
||||
and exists(select 1 from public.multiplayer_messages where room_id=p_room and player_id=player.id and created_at>clock_timestamp()-make_interval(secs=>room.chat_slow_seconds))
|
||||
then raise exception 'Chat slow mode: wait before sending another message'; end if;
|
||||
result:=public.multiplayer_chat_unchecked(p_room,p_access_hash,p_text,p_client_id);
|
||||
return jsonb_set(result,'{messages}',coalesce((select jsonb_agg(msg || jsonb_build_object('tag',tag.tag) order by ord)
|
||||
from jsonb_array_elements(result->'messages') with ordinality x(msg,ord)
|
||||
left join public.party_tags tag on tag.room_id=p_room and tag.player_id=(msg->>'playerId')::uuid),'[]'::jsonb));
|
||||
end; $$;
|
||||
revoke all on function public.party_community(uuid,text,text,jsonb),public.multiplayer_chat(uuid,text,text,uuid),public.multiplayer_chat_unchecked(uuid,text,text,uuid) from public,anon,authenticated;
|
||||
grant execute on function public.party_community(uuid,text,text,jsonb),public.multiplayer_chat(uuid,text,text,uuid),public.multiplayer_chat_unchecked(uuid,text,text,uuid) to service_role;
|
||||
notify pgrst,'reload schema';
|
||||
@@ -0,0 +1,7 @@
|
||||
create index party_bans_profile_fk on public.party_bans(account_id);
|
||||
create index party_moderators_player_fk on public.party_moderators(player_id);
|
||||
create index party_tags_player_fk on public.party_tags(player_id);
|
||||
create index tournament_bans_profile_fk on public.tournament_bans(account_id);
|
||||
create index tournament_messages_profile_fk on public.tournament_messages(account_id);
|
||||
create index tournament_moderators_profile_fk on public.tournament_moderators(account_id);
|
||||
create index tournament_tags_profile_fk on public.tournament_tags(account_id);
|
||||
@@ -0,0 +1,55 @@
|
||||
-- Wait for in-flight room actions and block new ones until backfill and function
|
||||
-- replacement are committed, so accepted messages cannot fall between them.
|
||||
lock table public.multiplayer_rooms in exclusive mode;
|
||||
|
||||
-- Player rows and their messages disappear when a player leaves a lobby.
|
||||
-- Keep the cooldown on the room's existing access identity instead.
|
||||
create table public.party_chat_cooldowns (
|
||||
room_id uuid not null references public.multiplayer_rooms(id) on delete cascade,
|
||||
access_hash text not null,
|
||||
last_message_at timestamptz not null,
|
||||
primary key (room_id, access_hash)
|
||||
);
|
||||
alter table public.party_chat_cooldowns enable row level security;
|
||||
revoke all on public.party_chat_cooldowns from public, anon, authenticated;
|
||||
grant all on public.party_chat_cooldowns to service_role;
|
||||
|
||||
-- Preserve the cooldown of players who are already in a room at deployment.
|
||||
insert into public.party_chat_cooldowns (room_id, access_hash, last_message_at)
|
||||
select m.room_id, p.access_hash, max(m.created_at)
|
||||
from public.multiplayer_messages m
|
||||
join public.multiplayer_players p on p.room_id=m.room_id and p.id=m.player_id
|
||||
join public.multiplayer_rooms r on r.id=m.room_id
|
||||
where r.tournament_id is null
|
||||
group by m.room_id, p.access_hash;
|
||||
|
||||
create or replace function public.multiplayer_chat(p_room uuid,p_access_hash text,p_text text default null,p_client_id uuid default null)
|
||||
returns jsonb language plpgsql security invoker set search_path='' as $$
|
||||
declare room public.multiplayer_rooms; player public.multiplayer_players; result jsonb; duplicate_message boolean;
|
||||
begin
|
||||
-- The room lock also serializes joining, leaving, and concurrent chat sends.
|
||||
select * into room from public.multiplayer_rooms where id=p_room for update;
|
||||
select * into player from public.multiplayer_players where room_id=p_room and access_hash=p_access_hash and not has_left;
|
||||
duplicate_message := player.id is not null and p_client_id is not null and exists(
|
||||
select 1 from public.multiplayer_messages where room_id=p_room and player_id=player.id and client_id=p_client_id
|
||||
);
|
||||
if player.id is not null and p_text is not null and room.chat_slow_seconds>0 and player.id<>room.host_player_id
|
||||
and not exists(select 1 from public.party_moderators where room_id=p_room and player_id=player.id)
|
||||
and not duplicate_message
|
||||
and exists(select 1 from public.party_chat_cooldowns where room_id=p_room and access_hash=p_access_hash
|
||||
and last_message_at>clock_timestamp()-make_interval(secs=>room.chat_slow_seconds))
|
||||
then raise exception 'Chat slow mode: wait before sending another message'; end if;
|
||||
result:=public.multiplayer_chat_unchecked(p_room,p_access_hash,p_text,p_client_id);
|
||||
-- Only accepted new messages advance the cooldown; reads and retries do not.
|
||||
if player.id is not null and p_text is not null and p_client_id is not null and not duplicate_message then
|
||||
insert into public.party_chat_cooldowns(room_id,access_hash,last_message_at)
|
||||
values(p_room,p_access_hash,clock_timestamp())
|
||||
on conflict(room_id,access_hash) do update set last_message_at=excluded.last_message_at;
|
||||
end if;
|
||||
return jsonb_set(result,'{messages}',coalesce((select jsonb_agg(msg || jsonb_build_object('tag',tag.tag) order by ord)
|
||||
from jsonb_array_elements(result->'messages') with ordinality x(msg,ord)
|
||||
left join public.party_tags tag on tag.room_id=p_room and tag.player_id=(msg->>'playerId')::uuid),'[]'::jsonb));
|
||||
end; $$;
|
||||
revoke all on function public.multiplayer_chat(uuid,text,text,uuid) from public,anon,authenticated;
|
||||
grant execute on function public.multiplayer_chat(uuid,text,text,uuid) to service_role;
|
||||
notify pgrst,'reload schema';
|
||||
Reference in New Issue
Block a user