DOC
Accueil/ Projets/ MJY Task Platform/ Handoff - Plateforme MJY Holding : migration Supabase

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és bloquant/haute/normale, champs created_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_by OU manager OU owner ; 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 colonne members.pin_hash, avec triggers hash_member_pin() et fonction verify_member_pin() en SECURITY DEFINER.
  • Un système de JWT personnalisé lisant des claims user_role/email via current_setting('request.jwt.claims'), avec des fonctions jwt_user_role(), jwt_user_email(), is_project_manager(), is_project_member().
  • Des policies RLS utilisant les rôles admin/employee - admin ne fait partie d'aucun rôle du brief officiel (qui définit super_manager/manager/employee).
  • Des tables au pluriel (projects, phases, members, tasks) avec members.project_id en text - 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.fr avec 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 (JWT signJWT custom, rôles admin/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_MEMBERS mono-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_id nullable (tâches "sans phase" existent, voir logique d'import IA)
  • task.created_by, task.manager, task.owner sont trois FK distinctes vers member
  • Trigger touch_updated_at() sur task pour updated_at automatique

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 via 05_rls_policies.sql séparé, voir §5), Realtime activé sur task/task_notes. (à produire au Sprint 05)
  • 03_seed-members.js - script Node.js (Admin API, service_role key) 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 pattern prenom@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 dans member + project_members). (à produire au Sprint 05)

7. Ordre d'exécution recommandé

  1. 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).
  2. Sauvegarder si besoin l'état actuel de la base avant nettoyage (Dashboard Supabase → Database → Backups, ou pg_dump) - le nettoyage est irréversible.
  3. Exécuter 01_cleanup.sql dans 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.
  4. Exécuter 02_schema.sql.
  5. Appliquer les policies RLS du §5.
  6. Compléter la liste de membres dans 03_seed-members.js avec les vraies données, puis l'exécuter en local avec SUPABASE_URL et SUPABASE_SERVICE_ROLE_KEY en variables d'environnement (jamais commit ces clés).
  7. Rattacher chaque membre seedé à ses projets via project_members (à faire une fois les projets créés).
  8. Remplacer la couche localStorage/Netlify Blobs de index.html/src par des appels supabase-js, en respectant : session PIN locale (comportement par défaut de persistSession), Realtime sur task/task_notes, visibilité isMine déléguée à la RLS plutôt qu'à un filtre client.
  9. É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 (migration pin_hash en prod) et US-TEST-04 (login super@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.