Handoff - Plateforme MJY Holding : migration Supabase
Ce document résume toutes les décisions prises jusqu'ici pour permettre une reprise du travail sans perte de contexte. Il fait suite au brief officiel BRIEF_PLATEFORME_MJY.pdf (Marco, DG MJY Holding, 6 juillet 2026) et à plusieurs sessions de conception du schéma.
📌 Ce handoff est la source de vérité du Sprint 05 - Refonte de l'authentification par utilisateur.
1. Contexte produit (résumé du brief officiel)
Application de gestion de tâches multi-projets pour le groupe MJY Holding (Miyan Noodles, ENGWE France, KOBIKE Marketplace...). Actuellement un index.html autonome en React 18 CDN/Babel, stockage localStorage, déployé sur Netlify. Le brief recommande explicitement l'option B : migration Supabase, pour la persistance et le collaboratif temps réel via Realtime.
Fonctionnalités à préserver impérativement (brief §3) :
- 3 rôles :
super_manager(PIN 9999, non rattaché à une personne),manager(PIN individuel + fallback partagé 1052),employee(fallback 4011). Ces valeurs internes ne doivent pas être renommées - l'UI affiche "Manager"/"Collaborateur" mais le storage garde ces clés. - Tâches : statuts
todo/in_progress/pending_validation/done, prioritésbloquant/haute/normale, champscreated_by,manager,owner(trois FK distinctes, pas une seule). - Workflow de validation : une tâche créée par un collaborateur pour lui-même passe par
pending_validation, le manager assigné approuve/rejette. - Journal de notes par tâche (
task_notes), visible manager + collaborateur. - Visibilité métier : manager voit tout ce où il est
created_byOUmanagerOUowner; collaborateur voit tout ce qui lui est assigné (owner), tous projets confondus ; super_manager voit tout. - Import IA (texte libre + PDF) → projets/phases/tâches/membres structurés, via Netlify Function appelant
claude-haiku-4-5-20251001. - Session PIN doit rester locale au navigateur, jamais synchronisée.
2. Décisions d'architecture validées avec l'utilisateur
| Sujet | Décision | Statut |
|---|---|---|
| Type des ID | uuid partout (gen_random_uuid()) |
Validé |
| Membre ↔ Projet | Relation N-N via table pivot project_members (pas de project_id direct sur member) |
Validé |
| Rôle et PIN | Uniques par membre, identiques sur tous ses projets (pas par-projet) | Validé |
| Auth | auth.users natif Supabase, PIN = mot de passe stocké dans encrypted_password (bcrypt géré nativement par Supabase, jamais de hashing manuel) |
Validé |
| Super admin | Compte réel dans auth.users avec un email de service (admin@mjyholding.fr ou équivalent), pas de compte "flottant" sans email |
Validé |
| PIN par défaut à la création | employee → 4011, manager → 1052, super_manager → 9999, appliqués comme mot de passe initial via Admin API (auth.admin.createUser) |
Validé |
| Fallback PIN | Interprété comme "mot de passe par défaut identique tant que non modifié individuellement" (pas un vrai fallback dynamique post-changement) | Validé |
| Changement de PIN | Le membre change lui-même via supabase.auth.updateUser({ password }), une fois connecté |
Validé |
| JWT | Le JWT Supabase standard ne fait aucun hashing - sert uniquement de jeton de session signé après authentification. Le hashing du PIN se fait en amont, côté auth.users, par bcrypt |
Point de compréhension clarifié |
| Realtime | À activer sur task et task_notes dès la conception du schéma (brief §5-6) |
Validé |
| RLS | Policies fines par rôle (voir §5), basées sur created_by/manager/owner. Le brief tolère une version "lecture ouverte / écriture authentifiée" en tout premier temps si besoin d'aller vite - c'est un choix de séquençage à trancher avec Marco/Julien, pas un choix technique figé |
Validé (avec marge de manœuvre assumée) |
3. IMPORTANT - dérive constatée en base à corriger
Un travail a été effectué directement en base en dehors de ce plan, avec une architecture incompatible :
- PIN géré via
pgcrypto/crypt()dans une colonnemembers.pin_hash, avec triggershash_member_pin()et fonctionverify_member_pin()enSECURITY DEFINER. - Un système de JWT personnalisé lisant des claims
user_role/emailviacurrent_setting('request.jwt.claims'), avec des fonctionsjwt_user_role(),jwt_user_email(),is_project_manager(),is_project_member(). - Des policies RLS utilisant les rôles
admin/employee-adminne fait partie d'aucun rôle du brief officiel (qui définitsuper_manager/manager/employee). - Des tables au pluriel (
projects,phases,members,tasks) avecmembers.project_identext- c'est-à-dire un membre rattaché à un seul projet, exactement le défaut de conception identifié et corrigé en tout début de cette collaboration (un membre doit pouvoir appartenir à plusieurs projets, d'où la table pivot). - Un compte super-admin sur
admin@engwe-france.fravec un username admin.
Décision explicite de l'utilisateur : abandonner cette dérive intégralement. On repart sur auth.users + bcrypt natif + rôles super_manager/manager/employee + table pivot project_members, comme validé au §2.
⚠️ État du code au 08/07/2026 : cette dérive est actuellement en place dans le code applicatif -
netlify/functions/auth.js(JWTsignJWTcustom, rôlesadmin/employee,verify_member_pin),src/pages/Auth.jsx(/api/auth,super@manager),src/services/supabaseClient.js(setSessionToken),src/utils/taskHelpers.js(SUPER_ADMIN_EMAILS,INIT_MEMBERSmono-projet). Le Sprint 05 la remplace.
Action requise en premier lieu : exécuter 01_cleanup.sql (voir sql/01_cleanup.sql) qui supprime à la fois les objets du plan validé ET tous les objets de la dérive listés ci-dessus, avant toute recréation.
4. Schéma cible (état final attendu après migration)
public.member -- id = auth.users.id (1-to-1), name, role, created_at
public.project -- id, name, emoji, color, created_at
public.project_members -- project_id, member_id (pivot N-N pur)
public.phase -- id, project_id, name, position, date_start, date_end
public.task -- id, project_id, phase_id, title, description,
-- status, priority, created_by, manager, owner,
-- created_at, updated_at
public.task_notes -- id, task_id, author, body, created_at
Contraintes clés :
member.role:check (role in ('super_manager', 'manager', 'employee'))task.status:check (status in ('todo', 'in_progress', 'pending_validation', 'done'))task.priority:check (priority in ('bloquant', 'haute', 'normale'))task.phase_idnullable (tâches "sans phase" existent, voir logique d'import IA)task.created_by,task.manager,task.ownersont trois FK distinctes versmember- Trigger
touch_updated_at()surtaskpourupdated_atautomatique
5. RLS à appliquer (basée sur les vrais rôles du brief)
alter table public.task enable row level security;
create policy "super_manager_sees_all"
on public.task for select
using (exists (select 1 from public.member m where m.id = auth.uid() and m.role = 'super_manager'));
create policy "manager_sees_own_scope"
on public.task for select
using (
exists (select 1 from public.member m where m.id = auth.uid() and m.role = 'manager')
and (created_by = auth.uid() or manager = auth.uid() or owner = auth.uid())
);
create policy "employee_sees_assigned"
on public.task for select
using (
exists (select 1 from public.member m where m.id = auth.uid() and m.role = 'employee')
and owner = auth.uid()
);
Point d'attention : ces policies utilisent auth.uid() directement (le JWT Supabase natif), pas de claims custom - cohérent avec l'abandon du JWT personnalisé.
6. Fichiers fournis avec ce handoff
sql/01_cleanup.sql- nettoyage complet : tables du plan validé + tables/fonctions/policies de la dérive (pluriel, JWT custom, pin_hash), avec requêtes de vérification finale.02_schema.sql- recréation complète du schéma cible (§4), RLS activée (policies à appliquer via05_rls_policies.sqlséparé, voir §5), Realtime activé surtask/task_notes. (à produire au Sprint 05)03_seed-members.js- script Node.js (Admin API,service_rolekey) pour recréer le super admin + premiers membres connus avec leurs PIN par défaut. La liste des membres et leurs emails réels doit être confirmée avec Marco avant exécution - le fichier contient des emails placeholder sur le patternprenom@mjyholding.fr. (à produire au Sprint 05)create-member/index.ts- Edge Function pour la création de membre en production par un admin/manager authentifié (vérifie le rôle appelant, hash le PIN via Admin API, insère dansmember+project_members). (à produire au Sprint 05)
7. Ordre d'exécution recommandé
- Confirmer avec Marco/Julien : la liste exacte des membres à recréer (noms + emails réels), et le choix RLS strict vs permissif en V1 (brief §6 tolère les deux).
- Sauvegarder si besoin l'état actuel de la base avant nettoyage (Dashboard Supabase → Database → Backups, ou
pg_dump) - le nettoyage est irréversible. - Exécuter
01_cleanup.sqldans le SQL Editor Supabase. Vérifier que les requêtes de contrôle en fin de script renvoient bien des résultats vides/zéro. - Exécuter
02_schema.sql. - Appliquer les policies RLS du §5.
- Compléter la liste de membres dans
03_seed-members.jsavec les vraies données, puis l'exécuter en local avecSUPABASE_URLetSUPABASE_SERVICE_ROLE_KEYen variables d'environnement (jamais commit ces clés). - Rattacher chaque membre seedé à ses projets via
project_members(à faire une fois les projets créés). - Remplacer la couche
localStorage/Netlify Blobs deindex.html/srcpar des appelssupabase-js, en respectant : session PIN locale (comportement par défaut depersistSession), Realtime surtask/task_notes, visibilitéisMinedéléguée à la RLS plutôt qu'à un filtre client. - Écrire le script de migration des données existantes (localStorage/Blobs → Supabase) si des données de prod sont encore récupérables avant le nettoyage - sinon repartir strictement du seed.
8. Points encore ouverts (à trancher, pas à deviner)
- Liste réelle des membres et leurs emails (§6.1).
- RLS stricte dès le départ vs version permissive temporaire (§2, brief §6).
- Sort des données existantes en
localStorage/Netlify Blobs : migration ou abandon pur (le brief mentionne un script de migration en option, non prioritaire si l'option B est retenue directement). - Articulation avec le Sprint 04 bis : ses stories
US-MIG-03(migrationpin_hashen prod) etUS-TEST-04(loginsuper@manager) reposent sur la dérive abandonnée ici. Ne pas déployer la couche auth actuelle en production avant l'atterrissage du Sprint 05.
