564 lines
26 KiB
PL/PgSQL
564 lines
26 KiB
PL/PgSQL
-- =====================================================================
|
|
-- GameDev Tracker — Mise à jour complète (v2 + v3 + v4)
|
|
-- À coller dans : Supabase > SQL Editor > New query > Run
|
|
-- Sans danger si une partie a déjà été appliquée (tout est idempotent).
|
|
-- =====================================================================
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- v2 — Pôles, assignation multiple, notifications, suppression jalons
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.tasks add column if not exists pole member_role;
|
|
alter table public.bugs add column if not exists pole member_role;
|
|
|
|
alter table public.tasks
|
|
add column if not exists assignee_ids uuid[] not null default '{}';
|
|
|
|
-- Reprend l'assigné unique existant dans le tableau (si la colonne existe encore).
|
|
do $$ begin
|
|
if exists (select 1 from information_schema.columns
|
|
where table_schema='public' and table_name='tasks' and column_name='assignee_id') then
|
|
update public.tasks
|
|
set assignee_ids = array[assignee_id]
|
|
where assignee_id is not null and (assignee_ids is null or assignee_ids = '{}');
|
|
alter table public.tasks drop column assignee_id;
|
|
end if;
|
|
end $$;
|
|
|
|
alter table public.tasks drop column if exists milestone_id;
|
|
drop table if exists public.milestones cascade;
|
|
drop type if exists milestone_status;
|
|
|
|
create table if not exists public.notifications (
|
|
id uuid primary key default gen_random_uuid(),
|
|
user_id uuid not null references public.profiles(id) on delete cascade,
|
|
project_id uuid references public.projects(id) on delete cascade,
|
|
kind text not null,
|
|
title text not null,
|
|
body text,
|
|
entity_id uuid,
|
|
pole member_role,
|
|
read boolean not null default false,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
create index if not exists idx_notifications_user on public.notifications(user_id);
|
|
alter table public.notifications enable row level security;
|
|
|
|
drop policy if exists notifications_select on public.notifications;
|
|
drop policy if exists notifications_insert on public.notifications;
|
|
drop policy if exists notifications_update on public.notifications;
|
|
drop policy if exists notifications_delete on public.notifications;
|
|
create policy notifications_select on public.notifications
|
|
for select to authenticated using (user_id = auth.uid());
|
|
create policy notifications_insert on public.notifications
|
|
for insert to authenticated with check (true);
|
|
create policy notifications_update on public.notifications
|
|
for update to authenticated using (user_id = auth.uid()) with check (user_id = auth.uid());
|
|
create policy notifications_delete on public.notifications
|
|
for delete to authenticated using (user_id = auth.uid());
|
|
|
|
do $$ begin
|
|
alter publication supabase_realtime add table public.notifications;
|
|
exception when duplicate_object then null; end $$;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- v3 — Feuille de temps : entrées libres (réunion, etc.)
|
|
-- ---------------------------------------------------------------------
|
|
-- Crée la table si elle n'a jamais existé (ancienne base sans suivi du temps).
|
|
create table if not exists public.time_logs (
|
|
id uuid primary key default gen_random_uuid(),
|
|
project_id uuid references public.projects(id) on delete cascade,
|
|
task_id uuid references public.tasks(id) on delete cascade,
|
|
label text,
|
|
user_id uuid references public.profiles(id) on delete set null,
|
|
minutes integer not null check (minutes > 0),
|
|
note text,
|
|
logged_at date not null default current_date,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
-- Si la table existait déjà (ancienne version), on l'aligne sur le nouveau format.
|
|
alter table public.time_logs alter column task_id drop not null;
|
|
alter table public.time_logs add column if not exists label text;
|
|
alter table public.time_logs
|
|
add column if not exists project_id uuid references public.projects(id) on delete cascade;
|
|
|
|
update public.time_logs tl
|
|
set project_id = t.project_id
|
|
from public.tasks t
|
|
where tl.task_id = t.id and tl.project_id is null;
|
|
|
|
create index if not exists idx_time_logs_task on public.time_logs(task_id);
|
|
create index if not exists idx_time_logs_project on public.time_logs(project_id);
|
|
create index if not exists idx_time_logs_user on public.time_logs(user_id);
|
|
|
|
alter table public.time_logs enable row level security;
|
|
drop policy if exists time_logs_all on public.time_logs;
|
|
create policy time_logs_all on public.time_logs
|
|
for all to authenticated using (true) with check (true);
|
|
|
|
do $$ begin
|
|
alter publication supabase_realtime add table public.time_logs;
|
|
exception when duplicate_object then null; end $$;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- v4 — Plusieurs pôles (rôles) par membre
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.profiles
|
|
add column if not exists roles member_role[] not null default '{}';
|
|
|
|
update public.profiles
|
|
set roles = array[role]
|
|
where roles = '{}';
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- v5 — Accueil (onboarding) des nouveaux membres
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.profiles
|
|
add column if not exists onboarded boolean not null default false;
|
|
|
|
-- Les membres déjà présents ne sont pas ré-embarqués : on les marque « fait ».
|
|
update public.profiles set onboarded = true where onboarded = false;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- v6 — Photo de profil (avatar image) : colonne + bucket de stockage
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.profiles add column if not exists avatar_url text;
|
|
|
|
-- Bucket public "avatars" pour héberger les photos de profil.
|
|
insert into storage.buckets (id, name, public)
|
|
values ('avatars', 'avatars', true)
|
|
on conflict (id) do nothing;
|
|
|
|
-- Lecture publique ; écriture réservée aux membres connectés.
|
|
drop policy if exists avatars_read on storage.objects;
|
|
drop policy if exists avatars_insert on storage.objects;
|
|
drop policy if exists avatars_update on storage.objects;
|
|
drop policy if exists avatars_delete on storage.objects;
|
|
|
|
create policy avatars_read on storage.objects
|
|
for select using (bucket_id = 'avatars');
|
|
create policy avatars_insert on storage.objects
|
|
for insert to authenticated with check (bucket_id = 'avatars');
|
|
create policy avatars_update on storage.objects
|
|
for update to authenticated using (bucket_id = 'avatars');
|
|
create policy avatars_delete on storage.objects
|
|
for delete to authenticated using (bucket_id = 'avatars');
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- v7 — Plusieurs pôles par tâche
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.tasks
|
|
add column if not exists poles member_role[] not null default '{}';
|
|
|
|
-- Reprend le pôle unique existant dans le tableau (si présent).
|
|
update public.tasks
|
|
set poles = array[pole]
|
|
where pole is not null and poles = '{}';
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- v8 — Checklists (cases à cocher) sur les tâches
|
|
-- ---------------------------------------------------------------------
|
|
-- Liste d'éléments [{ "id": "...", "text": "...", "done": false }, ...]
|
|
alter table public.tasks
|
|
add column if not exists checklist jsonb not null default '[]'::jsonb;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- v9 — Assignation MULTIPLE des bugs (parité avec les tâches)
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.bugs
|
|
add column if not exists assignee_ids uuid[] not null default '{}';
|
|
|
|
-- Reprend l'assigné unique existant dans le tableau (si présent).
|
|
update public.bugs
|
|
set assignee_ids = array[assignee_id]
|
|
where assignee_id is not null and assignee_ids = '{}';
|
|
|
|
|
|
-- =====================================================================
|
|
-- GameDev Tracker — Migration v10 « Refonte »
|
|
-- À coller dans : Supabase > SQL Editor > New query > Run
|
|
-- Idempotent : sans danger si déjà appliquée en tout ou partie.
|
|
--
|
|
-- Ce que fait cette migration :
|
|
-- 0. Colonnes « tableau » manquantes (dont tasks.tags) et non-NULL
|
|
-- 1. Commentaires sur les tâches et les bugs
|
|
-- 2. Pièces jointes (captures d'écran) sur les tâches et les bugs
|
|
-- 3. Parité tâches/bugs : plusieurs pôles par bug
|
|
-- 4. Suppression des colonnes « de compatibilité » devenues des pièges
|
|
-- (profiles.role, tasks.pole, bugs.assignee_id)
|
|
-- 5. Réordonnancement du Kanban en UNE requête transactionnelle
|
|
-- 6. Suppression d'un projet réservée à son créateur ou à un admin
|
|
-- 7. Temps réel sur les projets
|
|
-- =====================================================================
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 0) Colonnes « tableau » garanties — À EXÉCUTER EN PREMIER
|
|
--
|
|
-- `tasks.tags` n'existait que dans schema.sql (installations neuves) et
|
|
-- n'a jamais été ajoutée par les migrations : sur une base mise à jour
|
|
-- pas à pas, la colonne manquait et l'application plantait en tentant
|
|
-- d'afficher les étiquettes d'une tâche.
|
|
--
|
|
-- Tant qu'on y est, on ré-affirme l'invariant sur TOUTES les colonnes
|
|
-- tableau : jamais NULL, toujours un tableau vide par défaut.
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.tasks add column if not exists tags text[] not null default '{}';
|
|
alter table public.tasks add column if not exists poles member_role[] not null default '{}';
|
|
alter table public.tasks add column if not exists assignee_ids uuid[] not null default '{}';
|
|
alter table public.tasks add column if not exists checklist jsonb not null default '[]'::jsonb;
|
|
alter table public.bugs add column if not exists assignee_ids uuid[] not null default '{}';
|
|
alter table public.profiles add column if not exists roles member_role[] not null default '{}';
|
|
|
|
-- Remet à vide les valeurs NULL héritées, puis verrouille la contrainte.
|
|
do $$
|
|
declare
|
|
r record;
|
|
begin
|
|
for r in
|
|
select * from (values
|
|
('tasks', 'tags', '''{}''::text[]'),
|
|
('tasks', 'poles', '''{}''::member_role[]'),
|
|
('tasks', 'assignee_ids', '''{}''::uuid[]'),
|
|
('tasks', 'checklist', '''[]''::jsonb'),
|
|
('bugs', 'poles', '''{}''::member_role[]'),
|
|
('bugs', 'assignee_ids', '''{}''::uuid[]'),
|
|
('profiles', 'roles', '''{}''::member_role[]')
|
|
) as t(tbl, col, empty)
|
|
loop
|
|
if exists (
|
|
select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = r.tbl and column_name = r.col
|
|
) then
|
|
execute format('update public.%I set %I = %s where %I is null', r.tbl, r.col, r.empty, r.col);
|
|
execute format('alter table public.%I alter column %I set default %s', r.tbl, r.col, r.empty);
|
|
execute format('alter table public.%I alter column %I set not null', r.tbl, r.col);
|
|
end if;
|
|
end loop;
|
|
end $$;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 1) Parité bugs / tâches : plusieurs pôles par bug
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.bugs
|
|
add column if not exists poles member_role[] not null default '{}';
|
|
|
|
do $$ begin
|
|
if exists (select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'bugs' and column_name = 'pole') then
|
|
update public.bugs set poles = array[pole] where pole is not null and poles = '{}';
|
|
end if;
|
|
end $$;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 2) Commentaires (fil de discussion sur une tâche ou un bug)
|
|
-- ---------------------------------------------------------------------
|
|
create table if not exists public.comments (
|
|
id uuid primary key default gen_random_uuid(),
|
|
project_id uuid not null references public.projects(id) on delete cascade,
|
|
entity_kind text not null check (entity_kind in ('task', 'bug')),
|
|
entity_id uuid not null,
|
|
author_id uuid references public.profiles(id) on delete set null,
|
|
body text not null check (length(btrim(body)) > 0),
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists idx_comments_entity on public.comments(entity_kind, entity_id);
|
|
create index if not exists idx_comments_project on public.comments(project_id);
|
|
|
|
alter table public.comments enable row level security;
|
|
|
|
drop policy if exists comments_select on public.comments;
|
|
drop policy if exists comments_insert on public.comments;
|
|
drop policy if exists comments_update on public.comments;
|
|
drop policy if exists comments_delete on public.comments;
|
|
|
|
-- Toute l'équipe lit la discussion ; on n'écrit qu'en son propre nom ;
|
|
-- on ne modifie/supprime que ses propres messages (ou n'importe lequel si admin).
|
|
create policy comments_select on public.comments
|
|
for select to authenticated using (true);
|
|
create policy comments_insert on public.comments
|
|
for insert to authenticated with check (author_id = auth.uid());
|
|
create policy comments_update on public.comments
|
|
for update to authenticated
|
|
using (author_id = auth.uid()) with check (author_id = auth.uid());
|
|
create policy comments_delete on public.comments
|
|
for delete to authenticated
|
|
using (
|
|
author_id = auth.uid()
|
|
or exists (select 1 from public.profiles p where p.id = auth.uid() and p.is_admin)
|
|
);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 3) Pièces jointes (captures d'écran, logs, fichiers)
|
|
-- ---------------------------------------------------------------------
|
|
create table if not exists public.attachments (
|
|
id uuid primary key default gen_random_uuid(),
|
|
project_id uuid not null references public.projects(id) on delete cascade,
|
|
entity_kind text not null check (entity_kind in ('task', 'bug')),
|
|
entity_id uuid not null,
|
|
uploader_id uuid references public.profiles(id) on delete set null,
|
|
name text not null,
|
|
path text not null, -- chemin dans le bucket "attachments"
|
|
size bigint not null default 0,
|
|
mime text,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists idx_attachments_entity on public.attachments(entity_kind, entity_id);
|
|
create index if not exists idx_attachments_project on public.attachments(project_id);
|
|
|
|
alter table public.attachments enable row level security;
|
|
|
|
drop policy if exists attachments_select on public.attachments;
|
|
drop policy if exists attachments_insert on public.attachments;
|
|
drop policy if exists attachments_delete on public.attachments;
|
|
|
|
create policy attachments_select on public.attachments
|
|
for select to authenticated using (true);
|
|
create policy attachments_insert on public.attachments
|
|
for insert to authenticated with check (uploader_id = auth.uid());
|
|
create policy attachments_delete on public.attachments
|
|
for delete to authenticated
|
|
using (
|
|
uploader_id = auth.uid()
|
|
or exists (select 1 from public.profiles p where p.id = auth.uid() and p.is_admin)
|
|
);
|
|
|
|
-- Bucket de stockage des fichiers joints.
|
|
insert into storage.buckets (id, name, public)
|
|
values ('attachments', 'attachments', true)
|
|
on conflict (id) do nothing;
|
|
|
|
drop policy if exists attachments_read on storage.objects;
|
|
drop policy if exists attachments_write on storage.objects;
|
|
drop policy if exists attachments_remove on storage.objects;
|
|
|
|
create policy attachments_read on storage.objects
|
|
for select using (bucket_id = 'attachments');
|
|
create policy attachments_write on storage.objects
|
|
for insert to authenticated with check (bucket_id = 'attachments');
|
|
create policy attachments_remove on storage.objects
|
|
for delete to authenticated using (bucket_id = 'attachments');
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 4) Notifications : sur QUOI pointe la notification
|
|
-- (une notif de commentaire ouvre la tâche ou le bug commenté)
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.notifications
|
|
add column if not exists entity_kind text not null default 'task';
|
|
|
|
update public.notifications
|
|
set entity_kind = kind
|
|
where kind in ('task', 'bug') and entity_kind is distinct from kind;
|
|
|
|
create index if not exists idx_notifications_user_unread
|
|
on public.notifications(user_id, read, created_at desc);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 5) Réordonnancement du Kanban en UNE requête
|
|
-- Avant : une requête UPDATE par carte de la colonne (jusqu'à 30
|
|
-- allers-retours non transactionnels — un échec corrompait l'ordre).
|
|
-- ---------------------------------------------------------------------
|
|
create or replace function public.reorder_tasks(p_status task_status, p_ids uuid[])
|
|
returns void
|
|
language plpgsql
|
|
security invoker -- la RLS de l'appelant s'applique normalement
|
|
set search_path = public
|
|
as $$
|
|
begin
|
|
update public.tasks t
|
|
set status = p_status,
|
|
position = ordered.idx
|
|
from (
|
|
select id, (ord - 1)::double precision as idx
|
|
from unnest(p_ids) with ordinality as u(id, ord)
|
|
) ordered
|
|
where t.id = ordered.id;
|
|
end $$;
|
|
|
|
grant execute on function public.reorder_tasks(task_status, uuid[]) to authenticated;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 6) Suppression d'un projet : créateur ou administrateur seulement
|
|
-- (avant : n'importe quel membre pouvait effacer un projet entier
|
|
-- et tout son contenu en cascade)
|
|
-- ---------------------------------------------------------------------
|
|
drop policy if exists projects_all on public.projects;
|
|
drop policy if exists projects_select on public.projects;
|
|
drop policy if exists projects_insert on public.projects;
|
|
drop policy if exists projects_update on public.projects;
|
|
drop policy if exists projects_delete on public.projects;
|
|
|
|
create policy projects_select on public.projects
|
|
for select to authenticated using (true);
|
|
create policy projects_insert on public.projects
|
|
for insert to authenticated with check (true);
|
|
create policy projects_update on public.projects
|
|
for update to authenticated using (true) with check (true);
|
|
create policy projects_delete on public.projects
|
|
for delete to authenticated
|
|
using (
|
|
created_by = auth.uid()
|
|
or exists (select 1 from public.profiles p where p.id = auth.uid() and p.is_admin)
|
|
);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 7) Temps réel : projets, commentaires, pièces jointes
|
|
-- ---------------------------------------------------------------------
|
|
do $$ begin
|
|
alter publication supabase_realtime add table public.projects;
|
|
exception when duplicate_object then null; end $$;
|
|
do $$ begin
|
|
alter publication supabase_realtime add table public.comments;
|
|
exception when duplicate_object then null; end $$;
|
|
do $$ begin
|
|
alter publication supabase_realtime add table public.attachments;
|
|
exception when duplicate_object then null; end $$;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 8) Création de profil à l'inscription : couleur d'avatar variée
|
|
-- (avant, tout le monde héritait du même violet #7c5cff)
|
|
-- ---------------------------------------------------------------------
|
|
create or replace function public.handle_new_user()
|
|
returns trigger language plpgsql security definer set search_path = public as $$
|
|
declare
|
|
palette text[] := array['#7c5cff','#00d2b8','#ff5c8a','#ffa53c','#3ca8ff','#9bdb4d','#e15cff','#ff6b6b'];
|
|
r member_role;
|
|
begin
|
|
r := coalesce((new.raw_user_meta_data->>'role')::member_role, 'other');
|
|
insert into public.profiles (id, full_name, roles, avatar_color)
|
|
values (
|
|
new.id,
|
|
coalesce(new.raw_user_meta_data->>'full_name', split_part(new.email, '@', 1)),
|
|
array[r],
|
|
palette[1 + (abs(hashtext(new.id::text)) % array_length(palette, 1))]
|
|
)
|
|
on conflict (id) do nothing;
|
|
return new;
|
|
end $$;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 9) Suppression des colonnes « de compatibilité »
|
|
-- Elles doublonnaient les tableaux et devaient être re-synchronisées
|
|
-- à la main dans six endroits du code. On sauvegarde d'abord, puis
|
|
-- on supprime. À exécuter EN DERNIER (la fonction du point 8 ne les
|
|
-- utilise plus).
|
|
-- ---------------------------------------------------------------------
|
|
|
|
-- a) profiles.role → profiles.roles[1]
|
|
do $$ begin
|
|
if exists (select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'profiles' and column_name = 'role') then
|
|
update public.profiles set roles = array[role] where roles = '{}' and role is not null;
|
|
alter table public.profiles drop column role;
|
|
end if;
|
|
end $$;
|
|
|
|
-- b) tasks.pole → tasks.poles[1]
|
|
do $$ begin
|
|
if exists (select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'tasks' and column_name = 'pole') then
|
|
update public.tasks set poles = array[pole] where pole is not null and poles = '{}';
|
|
alter table public.tasks drop column pole;
|
|
end if;
|
|
end $$;
|
|
|
|
-- c) bugs.assignee_id → bugs.assignee_ids[1] · bugs.pole → bugs.poles[1]
|
|
do $$ begin
|
|
if exists (select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'bugs' and column_name = 'assignee_id') then
|
|
update public.bugs set assignee_ids = array[assignee_id]
|
|
where assignee_id is not null and assignee_ids = '{}';
|
|
alter table public.bugs drop column assignee_id;
|
|
end if;
|
|
if exists (select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'bugs' and column_name = 'pole') then
|
|
alter table public.bugs drop column pole;
|
|
end if;
|
|
end $$;
|
|
|
|
-- d) bugs.priority : plus exposée dans l'interface (la sévérité suffit).
|
|
-- La colonne reste en base, avec une valeur par défaut, pour ne rien
|
|
-- perdre — mais plus personne ne la remplit.
|
|
do $$ begin
|
|
if exists (select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'bugs' and column_name = 'priority') then
|
|
alter table public.bugs alter column priority set default 'medium';
|
|
end if;
|
|
end $$;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 10) Index manquants sur les colonnes les plus filtrées
|
|
-- ---------------------------------------------------------------------
|
|
create index if not exists idx_tasks_project_status on public.tasks(project_id, status, position);
|
|
create index if not exists idx_bugs_project_status on public.bugs(project_id, status);
|
|
create index if not exists idx_time_logs_date on public.time_logs(project_id, logged_at);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 11) Temps passé sur un BUG (parité avec les tâches)
|
|
-- Une entrée de temps porte sur une tâche, OU sur un bug, OU sur
|
|
-- rien (activité libre : réunion, veille…). Jamais les deux.
|
|
-- ---------------------------------------------------------------------
|
|
alter table public.time_logs
|
|
add column if not exists bug_id uuid references public.bugs(id) on delete cascade;
|
|
|
|
create index if not exists idx_time_logs_bug on public.time_logs(bug_id);
|
|
|
|
do $$ begin
|
|
alter table public.time_logs
|
|
add constraint time_logs_one_target
|
|
check (not (task_id is not null and bug_id is not null));
|
|
exception when duplicate_object then null; end $$;
|
|
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Dernière ligne : forcer l'API à relire le schéma.
|
|
-- Sans ça, PostgREST peut continuer à servir l'ancienne structure et
|
|
-- renvoyer « Could not find the 'xxx' column ... in the schema cache »
|
|
-- pendant plusieurs minutes après la migration.
|
|
-- ---------------------------------------------------------------------
|
|
notify pgrst, 'reload schema';
|
|
|
|
|
|
-- =====================================================================
|
|
-- v11 — Documentation (fiches créées ou importées)
|
|
-- =====================================================================
|
|
|
|
create table if not exists public.documents (
|
|
id uuid primary key default gen_random_uuid(),
|
|
project_id uuid not null references public.projects(id) on delete cascade,
|
|
title text not null,
|
|
-- Contenu en Markdown : c'est le format pivot. Un .docx importé est
|
|
-- converti en Markdown à l'import, ce qui garde les fiches lisibles,
|
|
-- cherchables et modifiables dans l'application.
|
|
content text not null default '',
|
|
tags text[] not null default '{}',
|
|
-- Fichier d'origine, quand la fiche vient d'un import (informatif).
|
|
source_name text,
|
|
created_by uuid references public.profiles(id) on delete set null,
|
|
updated_by uuid references public.profiles(id) on delete set null,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists idx_documents_project on public.documents(project_id, updated_at desc);
|
|
|
|
drop trigger if exists trg_documents_touch on public.documents;
|
|
create trigger trg_documents_touch before update on public.documents
|
|
for each row execute function public.touch_updated_at();
|
|
|
|
alter table public.documents enable row level security;
|
|
|
|
drop policy if exists documents_all on public.documents;
|
|
|
|
-- Équipe de confiance : tout membre connecté lit et écrit la documentation.
|
|
create policy documents_all on public.documents
|
|
for all to authenticated using (true) with check (true);
|
|
|
|
do $$ begin
|
|
alter publication supabase_realtime add table public.documents;
|
|
exception when duplicate_object then null; end $$;
|
|
|
|
notify pgrst, 'reload schema';
|