feat: add site_content, testimonials, and rules tables; featured products

This commit is contained in:
belviskhoremk
2026-08-13 08:51:41 +02:00
parent fe33d15ddc
commit a5b210f309
2 changed files with 185 additions and 1 deletions

View File

@@ -0,0 +1,98 @@
-- Phase 2D: adds site-editable content tables (site_content, testimonials,
-- rules) and featured-product columns, all following the existing
-- public-read/admin-write RLS pattern from the initial schema. Seed values
-- match what the site currently shows via hardcoded frontend constants, so
-- this migration doesn't blank any live content.
create table public.site_content (
id text primary key default 'default',
hero_headline text not null,
hero_tagline text not null,
about_bio text not null,
about_portrait_image text not null,
contact_email text not null,
contact_hours text not null
);
alter table public.site_content enable row level security;
create policy "site_content_public_read" on public.site_content
for select using (true);
create policy "site_content_admin_write" on public.site_content
for all using ((select auth.uid()) = '2b88676b-f901-459a-9147-b914190c4f81'::uuid)
with check ((select auth.uid()) = '2b88676b-f901-459a-9147-b914190c4f81'::uuid);
insert into public.site_content (id, hero_headline, hero_tagline, about_bio, about_portrait_image, contact_email, contact_hours)
values (
'default',
'Vos cheveux, votre couronne',
'Coiffures protectrices par Mira',
'Avec plus d''une décennie d''expérience derrière le fauteuil, Mira aborde chaque installation comme une œuvre d''art — douce pour le cuir chevelu, précise à la racine, et achevée pour une couronne que vous aurez envie d''afficher.',
'about/mira-portrait.jpg',
'hello@braidsbymira.com',
'Mar–Sam, 9h à 18h'
);
create table public.testimonials (
id uuid primary key default gen_random_uuid(),
quote text not null,
author text not null,
avatar_image text not null,
sort_order integer not null
);
alter table public.testimonials enable row level security;
create policy "testimonials_public_read" on public.testimonials
for select using (true);
create policy "testimonials_admin_write" on public.testimonials
for all using ((select auth.uid()) = '2b88676b-f901-459a-9147-b914190c4f81'::uuid)
with check ((select auth.uid()) = '2b88676b-f901-459a-9147-b914190c4f81'::uuid);
insert into public.testimonials (quote, author, avatar_image, sort_order) values
('Mira m''a fait sentir comme une reine sur son fauteuil. Mes tresses ont tenu 8 semaines !', 'Amara T.', 'testimonials/amara.jpg', 0),
('La tresseuse la plus douce et la plus précise que j''aie jamais consultée.', 'Keisha R.', 'testimonials/keisha.jpg', 1),
('La réservation était si simple, et le résultat parle de lui-même.', 'Nadia F.', 'testimonials/nadia.jpg', 2);
create table public.rules (
id uuid primary key default gen_random_uuid(),
title text not null,
body text not null,
icon_key text not null,
sort_order integer not null
);
alter table public.rules enable row level security;
create policy "rules_public_read" on public.rules
for select using (true);
create policy "rules_admin_write" on public.rules
for all using ((select auth.uid()) = '2b88676b-f901-459a-9147-b914190c4f81'::uuid)
with check ((select auth.uid()) = '2b88676b-f901-459a-9147-b914190c4f81'::uuid);
insert into public.rules (title, body, icon_key, sort_order) values
('Acompte', 'Un acompte de 30 $ est exigé afin de confirmer toute réservation. Celui-ci n''est pas remboursable, mais transférable à une date ultérieure sur entente, pour un maximum d''une fois. Toute annulation moins de 48 heures avant le rendez-vous entraînera la perte de l''acompte, qui ne sera ni remboursé ni transféré. En cas de non-présentation (« no-show »), l''acompte est automatiquement perdu.', 'deposit', 0),
('Préalables cheveux', 'Prière de garder vos cheveux propres, démêlés et secs, prêts à être tressés. Le blowdry peut être facturé si cette règle n''est pas respectée. Pas de bains d''huiles la veille ou le jour du rendez-vous, s''il vous plaît. Merci de toujours préciser, photo à l''appui, vos caractéristiques spécifiques (cheveux courts, locks, etc.).', 'hair-prep', 1),
('Mèches et extensions', 'Les mèches sont à déposer 24 à 48h avant la prestation si ce n''est pas moi qui les fournis. Choisissez de préférence des mèches pré-étirées ; l''étirage des mèches vous sera facturé en fonction du nombre de paquets.', 'extensions', 2),
('Retard', 'Les retards sans notification préalable ne sont pas autorisés. Un retard de 15 à 20 minutes entraîne des frais supplémentaires, et à partir de 30 minutes, le rendez-vous est automatiquement annulé.', 'lateness', 3),
('Accompagnant', 'Vous êtes tenue de vous présenter seule à votre rendez-vous. Merci de ne pas venir accompagnée sans l''avoir signalé au préalable.', 'guests', 4),
('Paiement', 'Le paiement peut être fait en espèces ou par virement Interac.', 'payment', 5);
alter table public.products add column featured boolean not null default false;
alter table public.products add column featured_order integer;
-- Grant SELECT to all public roles; write operations are restricted to admin via RLS
grant select on public.site_content to anon, authenticated;
grant select on public.testimonials to anon, authenticated;
grant select on public.rules to anon, authenticated;
-- Grant write permissions only to authenticated (admin) role; RLS policies further
-- restrict these to the specific admin UUID
grant update on public.site_content to authenticated;
grant insert, update, delete on public.testimonials to authenticated;
grant insert, update, delete on public.rules to authenticated;
-- Grant all permissions to service_role (which bypasses RLS for admin tasks)
grant all on public.site_content to service_role;
grant all on public.testimonials to service_role;
grant all on public.rules to service_role;

View File

@@ -1,5 +1,5 @@
begin;
select plan(53);
select plan(64);
-- Seed reference data as service_role (bypasses RLS)
set local role service_role;
@@ -391,5 +391,91 @@ select is_empty(
'booked_slots mirror row is removed after the duration-test booking is cancelled, regardless of duration'
);
-- site_content: public read, admin-only write. Singleton row seeded by the
-- migration, so no insert test is needed -- only read and update.
set local role anon;
select isnt_empty(
$$ select * from public.site_content where id = 'default' $$,
'anon can read site_content'
);
select throws_ok(
$$ update public.site_content set hero_headline = 'hack' where id = 'default' $$,
'42501',
null,
'anon cannot update site_content'
);
set local role authenticated;
set local request.jwt.claim.sub = '2b88676b-f901-459a-9147-b914190c4f81';
select lives_ok(
$$ update public.site_content set hero_headline = 'Updated Headline' where id = 'default' $$,
'admin can update site_content'
);
-- testimonials: public read, admin-only write.
set local role anon;
select isnt_empty(
$$ select * from public.testimonials $$,
'anon can read testimonials'
);
select throws_ok(
$$ insert into public.testimonials (quote, author, avatar_image, sort_order)
values ('hack', 'Hacker', 'x.jpg', 99) $$,
'42501',
null,
'anon cannot insert into testimonials'
);
set local role authenticated;
set local request.jwt.claim.sub = '2b88676b-f901-459a-9147-b914190c4f81';
select lives_ok(
$$ insert into public.testimonials (quote, author, avatar_image, sort_order)
values ('Great service!', 'Test Client', 'test.jpg', 99) $$,
'admin can insert into testimonials'
);
-- rules: public read, admin-only write.
set local role anon;
select isnt_empty(
$$ select * from public.rules $$,
'anon can read rules'
);
select throws_ok(
$$ insert into public.rules (title, body, icon_key, sort_order)
values ('hack', 'hack', 'deposit', 99) $$,
'42501',
null,
'anon cannot insert into rules'
);
set local role authenticated;
set local request.jwt.claim.sub = '2b88676b-f901-459a-9147-b914190c4f81';
select lives_ok(
$$ insert into public.rules (title, body, icon_key, sort_order)
values ('Test Rule', 'Test body.', 'payment', 99) $$,
'admin can insert into rules'
);
-- products.featured / featured_order: no new RLS policy (existing
-- products_admin_write already covers all columns), but confirm anon still
-- can't set them and admin can.
set local role anon;
select throws_ok(
$$ update public.products set featured = true where id = 'edge-control' $$,
'42501',
null,
'anon cannot set products.featured'
);
set local role authenticated;
set local request.jwt.claim.sub = '2b88676b-f901-459a-9147-b914190c4f81';
select lives_ok(
$$ update public.products set featured = true, featured_order = 0 where id = 'edge-control' $$,
'admin can set products.featured and featured_order'
);
select * from finish();
rollback;