Groupes hierarchiques a 6 niveaux : la feature la plus complexe
Points clés : Construire un système de groupes hiérarchiques à 6 niveaux de profondeur avec 4 rôles, des requêtes récursives PostgreSQL (CTE), et 31 politiques RLS, c'est la feature la plus complexe de TAMSIV. Cet article couvre le modèle de données, l'héritage des permissions, le composant frontend HierarchicalGroupPicker, et les leçons apprises.
Il y a des features qui semblent simples sur le papier. "Ajoute des groupes" — ça a l'air innocent. Comme ajouter un panier dans une app e-commerce. Puis tu commences à creuser et tu réalises que tu viens d'ouvrir la boîte de Pandore.
TAMSIV supporte des groupes hiérarchiques jusqu'à 6 niveaux de profondeur. Mon club de plongée a été le cas d'usage parfait : Club → Commission Technique → Niveau 1 → Groupe du mardi. Une famille utilise : Famille → Maison → Cuisine / Jardin / Garage. Une PME : Entreprise → Département → Équipe → Projet.
C'est la feature qui m'a pris le plus de temps. C'est aussi celle qui rend TAMSIV utilisable pour des organisations réelles, pas juste pour un usage solo.
Comment modéliser des groupes hiérarchiques en PostgreSQL ?
Le modèle de données repose sur un concept simple : chaque groupe a un parent_id qui pointe vers son groupe parent. Un groupe racine a parent_id = NULL.
-- Table simplifiee
CREATE TABLE collaborative.groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
parent_id UUID REFERENCES collaborative.groups(id),
created_by UUID REFERENCES auth.users(id),
depth INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now()
);
Pour afficher l'arbre complet d'un groupe, j'utilise des requêtes récursives (Common Table Expressions, ou CTE) en PostgreSQL :
WITH RECURSIVE group_tree AS (
-- Base : le groupe racine
SELECT id, name, parent_id, 0 AS depth
FROM collaborative.groups
WHERE id = p_group_id
UNION ALL
-- Recursion : les sous-groupes
SELECT g.id, g.name, g.parent_id, gt.depth + 1
FROM collaborative.groups g
JOIN group_tree gt ON g.parent_id = gt.id
WHERE gt.depth < 6 -- Garde-fou
)
SELECT * FROM group_tree;
La clause WHERE depth < 6 est un garde-fou critique. Sans elle, une erreur de données (un cycle de références par exemple) ferait tourner la requête en boucle infinie. Six niveaux couvrent tous les cas d'usage réels que j'ai rencontrés.
Ce modèle de données vit dans le schéma collaborative, séparé du reste — j'explique pourquoi dans l'article sur la structuration de la base de données.
Quels sont les quatre rôles et comment fonctionnent-ils ?
Chaque membre d'un groupe a un rôle qui détermine ses permissions :
- Admin — Tout faire : créer, modifier, supprimer, gérer les membres, inviter, modifier les paramètres du groupe. C'est le créateur du groupe par défaut.
- Manager — Gérer le contenu : créer/modifier/supprimer les tâches et mémos, assigner des membres, valider les checklists. Mais pas gérer les membres ni les paramètres.
- Member — Contribuer : créer du contenu, voir tout le contenu du groupe, valider ses propres items de checklist.
- Viewer — Lecture seule : voir le contenu, rien d'autre. Utile pour les observateurs ou les parties prenantes qui veulent suivre l'avancement sans intervenir.
Le piège de l'héritage des permissions
Si tu es Admin du groupe "Club de plongée", est-ce que tu es automatiquement Admin de "Commission Technique" (un sous-groupe) ? Dans TAMSIV, la réponse est oui — mais avec des nuances.
L'héritage fonctionne vers le bas : un Admin d'un groupe parent a les mêmes droits dans tous les sous-groupes. Mais un Admin d'un sous-groupe n'a aucun droit dans le groupe parent. Cette asymétrie est naturelle (le directeur voit tout, le chef d'équipe voit son équipe), mais elle ajoute de la complexité aux requêtes SQL.
Pour vérifier les permissions, chaque requête doit remonter l'arbre hiérarchique jusqu'à trouver un rôle ou atteindre la racine :
-- L'utilisateur a-t-il un role dans ce groupe ou un parent ?
WITH RECURSIVE parent_chain AS (
SELECT id, parent_id FROM collaborative.groups WHERE id = p_group_id
UNION ALL
SELECT g.id, g.parent_id
FROM collaborative.groups g
JOIN parent_chain pc ON g.id = pc.parent_id
)
SELECT role FROM collaborative.group_members
WHERE group_id IN (SELECT id FROM parent_chain)
AND user_id = auth.uid()
ORDER BY role ASC -- Admin > Manager > Member > Viewer
LIMIT 1;
Comment écrire et tester 31 politiques RLS ?
C'est le chiffre qui fait mal : 31 politiques Row Level Security pour le schéma collaborative. Supabase utilise RLS pour sécuriser l'accès aux données — chaque table a des règles qui déterminent qui peut lire, écrire, modifier, supprimer.
Voici des exemples concrets :
Politique de lecture simple
-- Un membre peut voir les taches de ses groupes
CREATE POLICY "members_read_tasks" ON collaborative.group_tasks
FOR SELECT USING (
EXISTS (
SELECT 1 FROM collaborative.group_members gm
WHERE gm.group_id = group_tasks.group_id
AND gm.user_id = auth.uid()
)
);
Politique avec héritage hiérarchique
-- Un admin peut supprimer les taches de ses groupes ET sous-groupes
CREATE POLICY "admin_delete_tasks" ON collaborative.group_tasks
FOR DELETE USING (
EXISTS (
WITH RECURSIVE parent_chain AS (
SELECT id, parent_id FROM collaborative.groups
WHERE id = group_tasks.group_id
UNION ALL
SELECT g.id, g.parent_id
FROM collaborative.groups g
JOIN parent_chain pc ON g.id = pc.parent_id
)
SELECT 1 FROM collaborative.group_members gm
WHERE gm.group_id IN (SELECT id FROM parent_chain)
AND gm.user_id = auth.uid()
AND gm.role = 'admin'
)
);
Tester 31 politiques, c'est 31 scénarios de test minimum. En réalité, c'est bien plus, parce que chaque politique doit être testée positivement (l'accès est bien accordé) ET négativement (l'accès est bien refusé). Méthodique, ennuyeux, indispensable. Le détail sur l'approche de test est dans l'article sur l'audit de sécurité.
Comment construire le composant HierarchicalGroupPicker ?
Côté frontend, afficher un arbre de groupes avec indentation, icônes de rôle, et interactions, c'est un défi UI à part entière.
Le HierarchicalGroupPicker est un composant React Native qui :
- Affiche l'arbre avec indentation proportionnelle à la profondeur (utilisant
spacing()du système de scaling). - Indique le rôle de l'utilisateur dans chaque groupe via une icône colorée.
- Permet la sélection avec un toggle "inclure les sous-groupes" — quand tu sélectionnes un groupe parent, les sous-groupes peuvent être inclus automatiquement.
- Supporte le fold/unfold : les sous-groupes sont repliables pour ne pas encombrer l'écran sur les hiérarchies profondes.
Ce composant est réutilisé partout : dans le filtre de l'agenda, dans la création de tâches, dans l'écran de gestion des groupes. C'est un investissement de temps énorme au départ, mais un gain de temps colossal ensuite.
Le pattern FilterBar
Le HierarchicalGroupPicker fonctionne avec la FilterBar, un composant de filtrage avec 3 contextes (Privé / Partagé / Tout) et des modes (toutes les tâches / créées par moi / assignées à moi). La combinaison des deux permet des requêtes très précises : "montre-moi les tâches assignées à moi dans le groupe Famille et ses sous-groupes".
Quels sont les cas d'usage réels des groupes hiérarchiques ?
Voici les cas d'usage que mes testeurs utilisent activement :
- Famille : Famille → Maison (tâches ménagères) / Vacances (planning) / École (devoirs des enfants). J'en parle dans l'article sur l'organisation familiale.
- Club sportif : Club → Commissions → Niveaux → Groupes par créneau.
- Petite équipe : Entreprise → Département → Projet → Sprint.
- Association : Association → Pôle → Événement.
La limite de 6 niveaux est rarement atteinte. En pratique, 3-4 niveaux couvrent 95% des besoins. Mais cette profondeur maximale est un filet de sécurité — mieux vaut l'avoir et ne pas en avoir besoin.
Quelles leçons tirer de cette implémentation ?
- Les requêtes récursives PostgreSQL sont puissantes mais coûteuses : Chaque requête qui remonte la hiérarchie fait N jointures (où N = la profondeur). Un index sur
parent_idest indispensable. - Les politiques RLS s'accumulent vite : Chaque table × chaque opération (SELECT, INSERT, UPDATE, DELETE) × chaque rôle = beaucoup de politiques. Documente-les dans un fichier dédié.
- L'héritage des permissions est le point le plus délicat : Décide tôt si les permissions se propagent vers le bas, vers le haut, ou les deux. Changer cette décision plus tard est extrêmement coûteux.
- Le composant frontend est un investissement : Un bon HierarchicalGroupPicker prend une semaine à construire correctement, mais se réutilise partout.
- Teste avec des données réelles : Un arbre de 3 groupes en dev ne révèle pas les mêmes bugs qu'un arbre de 15 groupes sur 4 niveaux en production.
Si tu construis un système collaboratif, la hiérarchie est ce qui transforme une simple "liste de groupes" en un outil que les organisations adoptent réellement. C'est dur, c'est long, mais c'est ce qui fait la différence avec les concurrents qui se limitent à des groupes plats.
FAQ
Pourquoi limiter à 6 niveaux de profondeur ?
Six niveaux couvrent tous les cas d'usage réels que j'ai rencontrés. Au-delà, l'arbre devient ingérable pour l'utilisateur. La limite protège aussi contre les requêtes récursives trop profondes qui pourraient impacter les performances. En pratique, 3-4 niveaux suffisent pour 95% des organisations.
Est-ce que les requêtes récursives posent des problèmes de performance ?
Avec un index sur parent_id et une limite de profondeur, les performances sont excellentes même avec des centaines de groupes. Une requête récursive sur 6 niveaux prend moins de 10ms sur Supabase PostgreSQL. Le vrai risque est un cycle de références (group A parent de B, B parent de A) — la clause WHERE depth < 6 protège contre ça.
Comment gérer l'invitation de nouveaux membres ?
L'admin d'un groupe peut inviter par email ou par lien de partage. L'invité rejoint le groupe avec le rôle choisi par l'admin. Si le groupe a des sous-groupes, l'invité n'a accès qu'au groupe dans lequel il a été invité — sauf si un admin du parent lui donne explicitement accès.
Les checklists de groupe fonctionnent-elles différemment des checklists personnelles ?
Oui. Les checklists de groupe ont deux modes de validation : "single" (une seule personne valide pour tout le monde) et "everyone" (chaque membre doit valider individuellement). Le mode "everyone" est parfait pour les listes de courses familiales où personne n'achète la même chose.
Peut-on déplacer un groupe dans un autre groupe parent ?
Oui, un admin peut réorganiser la hiérarchie en changeant le parent_id d'un groupe. Mais c'est une opération sensible : les permissions héritées changent immédiatement, et les membres du nouveau parent voient le contenu du groupe déplacé. Un message de confirmation explicite avertit l'admin des conséquences.