Files
gestion_projet/supabase/schema.sql
T
Mathew b054e36df0 Panel admin, planning, tags, suivi du temps, thèmes clair/sombre
- Admin: création/suppression de comptes + rôles via Edge Function sécurisée
- Config Supabase intégrée au build (.env), écran Setup supprimé
- Page Planning (vue mois/semaine)
- Tags "jobs" sur les tâches + filtres (membre / tag)
- Suivi du temps par tâche, total par jalon
- Toasts + confirmations in-app (fini alert/confirm natifs)
- Thème Notion clair + mode sombre (accent monochrome, bascule persistée)
- .env suivi par git (public uniquement) pour synchro multi-machines

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:51:40 +02:00

248 lines
10 KiB
PL/PgSQL

-- =====================================================================
-- GameDev Tracker — Schéma de base de données (Supabase / PostgreSQL)
-- À exécuter UNE SEULE FOIS dans : Supabase > SQL Editor > New query
-- =====================================================================
-- ---------- Types énumérés -------------------------------------------
do $$ begin
create type member_role as enum
('lead','programmer','artist','game_designer','sound','writer','qa','other');
exception when duplicate_object then null; end $$;
do $$ begin
create type task_status as enum ('todo','in_progress','review','done');
exception when duplicate_object then null; end $$;
do $$ begin
create type priority_level as enum ('low','medium','high','urgent');
exception when duplicate_object then null; end $$;
do $$ begin
create type milestone_status as enum ('planned','in_progress','done');
exception when duplicate_object then null; end $$;
do $$ begin
create type bug_severity as enum ('low','medium','high','critical');
exception when duplicate_object then null; end $$;
do $$ begin
create type bug_status as enum ('open','in_progress','resolved','closed');
exception when duplicate_object then null; end $$;
-- ---------- Profils (étend auth.users) -------------------------------
create table if not exists public.profiles (
id uuid primary key references auth.users(id) on delete cascade,
full_name text not null default 'Membre',
role member_role not null default 'other',
avatar_color text not null default '#7c5cff',
created_at timestamptz not null default now()
);
-- ---------- Projets --------------------------------------------------
create table if not exists public.projects (
id uuid primary key default gen_random_uuid(),
name text not null,
description text,
created_by uuid references public.profiles(id) on delete set null,
created_at timestamptz not null default now()
);
-- ---------- Jalons (milestones) --------------------------------------
create table if not exists public.milestones (
id uuid primary key default gen_random_uuid(),
project_id uuid not null references public.projects(id) on delete cascade,
title text not null,
description text,
due_date date,
status milestone_status not null default 'planned',
created_at timestamptz not null default now()
);
-- ---------- Tâches ---------------------------------------------------
create table if not exists public.tasks (
id uuid primary key default gen_random_uuid(),
project_id uuid not null references public.projects(id) on delete cascade,
title text not null,
description text,
status task_status not null default 'todo',
priority priority_level not null default 'medium',
assignee_id uuid references public.profiles(id) on delete set null,
milestone_id uuid references public.milestones(id) on delete set null,
due_date date,
position double precision not null default 0,
created_by uuid references public.profiles(id) on delete set null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- ---------- Bugs -----------------------------------------------------
create table if not exists public.bugs (
id uuid primary key default gen_random_uuid(),
project_id uuid not null references public.projects(id) on delete cascade,
title text not null,
description text,
severity bug_severity not null default 'medium',
status bug_status not null default 'open',
priority priority_level not null default 'medium',
assignee_id uuid references public.profiles(id) on delete set null,
reporter_id 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_tasks_project on public.tasks(project_id);
create index if not exists idx_bugs_project on public.bugs(project_id);
create index if not exists idx_milestones_project on public.milestones(project_id);
-- ---------- Maj automatique de updated_at ----------------------------
create or replace function public.touch_updated_at()
returns trigger language plpgsql as $$
begin new.updated_at = now(); return new; end $$;
drop trigger if exists trg_tasks_touch on public.tasks;
create trigger trg_tasks_touch before update on public.tasks
for each row execute function public.touch_updated_at();
drop trigger if exists trg_bugs_touch on public.bugs;
create trigger trg_bugs_touch before update on public.bugs
for each row execute function public.touch_updated_at();
-- ---------- Création auto du profil à l'inscription ------------------
create or replace function public.handle_new_user()
returns trigger language plpgsql security definer set search_path = public as $$
begin
insert into public.profiles (id, full_name, role)
values (
new.id,
coalesce(new.raw_user_meta_data->>'full_name', split_part(new.email,'@',1)),
coalesce((new.raw_user_meta_data->>'role')::member_role, 'other')
)
on conflict (id) do nothing;
return new;
end $$;
drop trigger if exists on_auth_user_created on auth.users;
create trigger on_auth_user_created
after insert on auth.users
for each row execute function public.handle_new_user();
-- ---------- Sécurité (RLS) -------------------------------------------
-- Équipe privée et de confiance : tout membre connecté a accès complet.
alter table public.profiles enable row level security;
alter table public.projects enable row level security;
alter table public.milestones enable row level security;
alter table public.tasks enable row level security;
alter table public.bugs enable row level security;
do $$
declare t text;
begin
foreach t in array array['profiles','projects','milestones','tasks','bugs'] loop
execute format('drop policy if exists %I_all on public.%I', t, t);
execute format(
'create policy %I_all on public.%I for all to authenticated using (true) with check (true)',
t, t);
end loop;
end $$;
-- ---------- Realtime (synchro live entre membres) --------------------
do $$ begin
alter publication supabase_realtime add table public.tasks;
exception when duplicate_object then null; end $$;
do $$ begin
alter publication supabase_realtime add table public.bugs;
exception when duplicate_object then null; end $$;
do $$ begin
alter publication supabase_realtime add table public.milestones;
exception when duplicate_object then null; end $$;
do $$ begin
alter publication supabase_realtime add table public.profiles;
exception when duplicate_object then null; end $$;
-- =====================================================================
-- ADMINISTRATION : comptes & rôles gérés par un panel admin in-app
-- =====================================================================
-- ---------- Champ "administrateur" sur les profils -------------------
alter table public.profiles
add column if not exists is_admin boolean not null default false;
-- ---------- Sécurité fine sur les profils ----------------------------
-- On remplace la policy permissive "profiles_all" par des règles précises :
-- * tout le monde (connecté) PEUT LIRE les profils (affichage de l'équipe)
-- * chacun ne peut MODIFIER que SON propre profil
-- * la création/suppression de comptes passe uniquement par l'Edge
-- Function "admin-users" (clé service_role, côté serveur).
drop policy if exists profiles_all on public.profiles;
drop policy if exists profiles_select on public.profiles;
drop policy if exists profiles_update_self on public.profiles;
create policy profiles_select on public.profiles
for select to authenticated using (true);
create policy profiles_update_self on public.profiles
for update to authenticated
using (id = auth.uid()) with check (id = auth.uid());
-- ---------- Garde-fou : on ne se promeut pas admin soi-même ----------
-- Empêche un membre connecté (rôle "authenticated") de changer le champ
-- is_admin. Les opérations légitimes passent par l'Edge Function
-- (rôle "service_role") ou par le SQL Editor (rôle "postgres"), tous deux
-- autorisés ici. Un admin déjà en place peut aussi modifier le statut.
create or replace function public.guard_is_admin()
returns trigger language plpgsql security definer set search_path = public as $$
begin
if new.is_admin is distinct from old.is_admin
and current_user = 'authenticated'
and not exists (
select 1 from public.profiles p where p.id = auth.uid() and p.is_admin
) then
raise exception 'Seul un administrateur peut modifier le statut admin.';
end if;
return new;
end $$;
drop trigger if exists trg_profiles_guard_admin on public.profiles;
create trigger trg_profiles_guard_admin before update on public.profiles
for each row execute function public.guard_is_admin();
-- ---------- Amorçage du PREMIER admin (à faire UNE fois) -------------
-- Après ta première inscription dans l'appli, exécute cette ligne en
-- remplaçant l'email par le tien pour devenir administrateur :
--
-- update public.profiles set is_admin = true
-- where id = (select id from auth.users where email = 'TON_EMAIL@ exemple.com');
--
-- Ensuite, tu pourras créer/gérer tous les autres comptes depuis l'appli.
-- =====================================================================
-- TAGS (« jobs ») & SUIVI DU TEMPS
-- =====================================================================
-- ---------- Tags sur les tâches (ex: Programmation, Art, Audio) ------
alter table public.tasks
add column if not exists tags text[] not null default '{}';
-- ---------- Journal du temps passé (par tâche, par membre) -----------
create table if not exists public.time_logs (
id uuid primary key default gen_random_uuid(),
task_id uuid not null references public.tasks(id) on delete cascade,
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()
);
create index if not exists idx_time_logs_task on public.time_logs(task_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 $$;