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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user