feat: add site_content, testimonials, and rules tables; featured products
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user