Ajoute Le Mur de la Honte
Build and deploy / deploy (push) Successful in 35s

Nouvelle page /mur : chaque Citoyen peut publier une note anonyme par
jour (dix places max sur le mur, remis à zéro chaque jour — filtrage
par date, jamais de suppression). L'anonymat n'est qu'à moitié réel :
les Archontes voient une section "Historique complet" (filtrable par
jour) avec l'auteur de chaque note, cohérent avec le thème du site où
les Archontes ont un ascendant sur les Citoyens.

Nouvelle table wall_notes + deux RPC : wall_notes_today() ne renvoie
jamais l'auteur (seul moyen de garantir l'anonymat côté serveur, la
RLS ne pouvant pas masquer une colonne pour certaines lignes) et
post_wall_note() applique les règles (un juge ne publie pas, une note
par jour, mur plafonné à 10). Pas de gel lié à la date du Tribunal :
contrairement au Char, c'est une mécanique de toute la semaine.
This commit is contained in:
Valentin ROBIN
2026-08-23 17:14:12 +02:00
parent ec6b1920af
commit ab599d55c9
7 changed files with 486 additions and 2 deletions
+112
View File
@@ -1019,3 +1019,115 @@ end;
$$;
grant execute on function public.admin_list_members() to authenticated;
-- 14. Le Mur de la Honte -------------------------------------------------------
-- Une note anonyme par Citoyen par jour, max 10 notes visibles par jour
-- (remise à zéro chaque jour — filtrage par date, pas de suppression), pour
-- donner une raison de rouvrir l'app tous les jours de la semaine, pas
-- seulement pour le classement. L'anonymat n'est qu'à moitié réel : les
-- Archontes voient l'historique complet avec l'auteur de chaque note.
create table if not exists public.wall_notes (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references public.profiles (id) on delete cascade,
text text not null,
created_at timestamptz not null default now()
);
alter table public.wall_notes enable row level security;
revoke insert, update, delete on public.wall_notes from authenticated, anon;
grant select on public.wall_notes to authenticated;
drop policy if exists "wall_notes readable by owner or judges" on public.wall_notes;
create policy "wall_notes readable by owner or judges"
on public.wall_notes 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')
);
do $$
begin
alter publication supabase_realtime add table public.wall_notes;
exception
when duplicate_object then null;
end $$;
-- Seul moyen pour un Citoyen de voir les notes des autres sans jamais
-- exposer qui les a écrites côté client : la RLS ne peut pas masquer une
-- colonne pour certaines lignes, donc cette RPC (SECURITY DEFINER) fait le
-- filtre elle-même et ne renvoie jamais user_id.
create or replace function public.wall_notes_today()
returns table (id uuid, text text, created_at timestamptz)
language sql
security definer
set search_path = public
as $$
select w.id, w.text, w.created_at
from public.wall_notes w
where (w.created_at at time zone 'Europe/Paris')::date = (now() at time zone 'Europe/Paris')::date
order by w.created_at asc;
$$;
grant execute on function public.wall_notes_today() to authenticated;
-- Seul point d'entrée pour publier une note. Pas de gel via tribunal_date
-- (contrairement au Char) : contrairement à la soirée de clôture, c'est une
-- mécanique de toute la semaine. Petite fenêtre de course possible si deux
-- personnes postent la même seconde alors qu'il reste 1 place (double
-- insertion à 11) — acceptée sciemment, comme d'autres arbitrages similaires
-- déjà faits dans le projet pour un groupe de ~12 amis.
create or replace function public.post_wall_note(p_text text)
returns public.wall_notes
language plpgsql
security definer
set search_path = public
as $$
declare
v_caller_role text;
v_today date := (now() at time zone 'Europe/Paris')::date;
v_already_posted boolean;
v_count_today int;
v_row public.wall_notes;
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 post on the wall';
end if;
if p_text is null or length(trim(p_text)) = 0 then
raise exception 'note vide';
end if;
if length(p_text) > 200 then
raise exception 'note trop longue';
end if;
select exists (
select 1 from public.wall_notes
where user_id = auth.uid()
and (created_at at time zone 'Europe/Paris')::date = v_today
) into v_already_posted;
if v_already_posted then
raise exception 'already posted today';
end if;
select count(*) into v_count_today
from public.wall_notes
where (created_at at time zone 'Europe/Paris')::date = v_today;
if v_count_today >= 10 then
raise exception 'wall full today';
end if;
insert into public.wall_notes (user_id, text)
values (auth.uid(), trim(p_text))
returning * into v_row;
return v_row;
end;
$$;
grant execute on function public.post_wall_note(text) to authenticated;