Ajoute les suggestions de questions des Citoyens et les emails sur Admin
Build and deploy / deploy (push) Successful in 35s
Build and deploy / deploy (push) Successful in 35s
Chaque Citoyen peut proposer une question pour Le Char depuis /profile (upsert via submit_chariot_question, figée dès la date du Tribunal comme les scores d'Icare). Les Archontes modèrent ces suggestions directement sur /char/questions (ajouter à la banque ou rejeter). /admin affiche maintenant l'email de chaque membre via une nouvelle RPC admin_list_members(), seule façon d'exposer auth.users.email sans passer par la service_role key côté client. Corrige au passage deux bugs découverts pendant les tests : la policy interne de admin_list_members() référençait id/role sans les qualifier, ambigus avec les colonnes du RETURNS TABLE (erreur Postgres 42702) ; et l'auteur d'une suggestion s'affichait toujours comme "un Citoyen" car le embed profiles(...) avait été mal retypé en tableau alors qu'il est retourné en objet à l'exécution.
This commit is contained in:
@@ -861,3 +861,127 @@ exception
|
||||
when others then
|
||||
raise notice 'pg_cron indisponible : active l''extension via le Dashboard Supabase (Database → Extensions → pg_cron), puis ré-exécute ce script pour planifier le reroll automatique du Gardien.';
|
||||
end $$;
|
||||
|
||||
-- 13. Suggestions de questions par les Citoyens + emails sur Admin
|
||||
-- -----------------------------------------------------------------
|
||||
|
||||
-- Une proposition par Citoyen (une ligne par user_id, comme icarus_scores).
|
||||
-- Verrouillée en écriture comme points_log/icarus_scores : seule la RPC
|
||||
-- submit_chariot_question() (SECURITY DEFINER) peut insert/update ; les
|
||||
-- juges gardent un accès direct en lecture (modération) et en suppression
|
||||
-- (rejet), sans passer par une RPC pour ce cas simple.
|
||||
create table if not exists public.chariot_submissions (
|
||||
user_id uuid primary key references public.profiles (id) on delete cascade,
|
||||
text text not null,
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
alter table public.chariot_submissions enable row level security;
|
||||
revoke insert, update on public.chariot_submissions from authenticated, anon;
|
||||
grant select, delete on public.chariot_submissions to authenticated;
|
||||
|
||||
drop policy if exists "chariot_submissions readable by owner or judges" on public.chariot_submissions;
|
||||
create policy "chariot_submissions readable by owner or judges"
|
||||
on public.chariot_submissions for select to authenticated
|
||||
using (
|
||||
user_id = auth.uid()
|
||||
or exists (select 1 from public.profiles p where p.id = auth.uid() and p.role = 'judge')
|
||||
);
|
||||
|
||||
drop policy if exists "chariot_submissions delete by judges" on public.chariot_submissions;
|
||||
create policy "chariot_submissions delete by judges" on public.chariot_submissions for delete to authenticated
|
||||
using (exists (select 1 from public.profiles p where p.id = auth.uid() and p.role = 'judge'));
|
||||
|
||||
do $$
|
||||
begin
|
||||
alter publication supabase_realtime add table public.chariot_submissions;
|
||||
exception
|
||||
when duplicate_object then null;
|
||||
end $$;
|
||||
|
||||
-- Seul point d'entrée pour proposer/modifier sa question. Réutilise
|
||||
-- settings.tribunal_date comme déclencheur de gel — même philosophie que
|
||||
-- submit_icarus_score() : une fois l'Agora commencée, no-op silencieux
|
||||
-- (retourne la ligne existante sans la modifier) plutôt qu'une erreur.
|
||||
create or replace function public.submit_chariot_question(p_text text)
|
||||
returns public.chariot_submissions
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_caller_role text;
|
||||
v_tribunal_date timestamptz;
|
||||
v_row public.chariot_submissions;
|
||||
begin
|
||||
if auth.uid() is null then
|
||||
raise exception 'authentication required';
|
||||
end if;
|
||||
|
||||
select role into v_caller_role from public.profiles where id = auth.uid();
|
||||
if v_caller_role = 'judge' then
|
||||
raise exception 'only citizens can submit a chariot question';
|
||||
end if;
|
||||
|
||||
if p_text is null or length(trim(p_text)) = 0 then
|
||||
raise exception 'question vide';
|
||||
end if;
|
||||
if length(p_text) > 300 then
|
||||
raise exception 'question trop longue';
|
||||
end if;
|
||||
|
||||
select tribunal_date into v_tribunal_date from public.settings where id = true;
|
||||
|
||||
if v_tribunal_date is not null and now() >= v_tribunal_date then
|
||||
select * into v_row from public.chariot_submissions where user_id = auth.uid();
|
||||
return v_row;
|
||||
end if;
|
||||
|
||||
insert into public.chariot_submissions (user_id, text, updated_at)
|
||||
values (auth.uid(), trim(p_text), now())
|
||||
on conflict (user_id) do update
|
||||
set text = excluded.text,
|
||||
updated_at = now();
|
||||
|
||||
select * into v_row from public.chariot_submissions where user_id = auth.uid();
|
||||
return v_row;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.submit_chariot_question(text) to authenticated;
|
||||
|
||||
-- Émails des membres pour /admin : jamais stockés dans profiles (voir §4 du
|
||||
-- doc — le pseudo est le seul nom montré aux autres), donc invisibles côté
|
||||
-- client sans passer par auth.users. Plutôt que d'exposer la service_role
|
||||
-- key côté client, une RPC SECURITY DEFINER fait la jointure, gardée par la
|
||||
-- même vérification de rôle que les autres RPC juge-only du projet.
|
||||
create or replace function public.admin_list_members()
|
||||
returns table (
|
||||
id uuid,
|
||||
pseudo text,
|
||||
avatar_url text,
|
||||
role text,
|
||||
pseudo_locked boolean,
|
||||
email text
|
||||
)
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
begin
|
||||
-- Colonnes de sortie qualifiées par un alias partout : returns table(...)
|
||||
-- déclare id/role comme variables PL/pgSQL, qui masqueraient sinon les
|
||||
-- colonnes de même nom dans profiles (erreur 42702 "ambiguous").
|
||||
if not exists (select 1 from public.profiles pr where pr.id = auth.uid() and pr.role = 'judge') then
|
||||
raise exception 'only judges can list members';
|
||||
end if;
|
||||
|
||||
return query
|
||||
select p.id, p.pseudo, p.avatar_url, p.role, p.pseudo_locked, u.email::text
|
||||
from public.profiles p
|
||||
join auth.users u on u.id = p.id
|
||||
order by p.pseudo asc;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.admin_list_members() to authenticated;
|
||||
|
||||
Reference in New Issue
Block a user