Files
Coltura/CLAUDE.md
THOLOT DECHENE Matthieu e8c2789435
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
RBAC - Système complet de permissions (Backend + Frontend) (#7)
## Résumé

Implémentation complète du système RBAC (Role-Based Access Control) pour Coltura.

### Backend
- Entités Permission et Role avec API Platform CRUD
- PermissionVoter : vérification des permissions effectives (rôles + directes), admin bypass
- Endpoints `PATCH /users/{id}/rbac` pour assigner rôles, permissions directes et isAdmin
- AdminHeadcountGuard : protection contre la suppression du dernier admin
- Commande `app:sync-permissions` pour synchroniser les permissions déclarées par les modules
- Filtrage sidebar par permission RBAC (`permission` key optionnelle dans sidebar.php)
- 115 tests PHPUnit (fonctionnels + unitaires)

### Frontend
- Composable `usePermissions()` avec `can()`, `canAny()`, `canAll()` et admin bypass
- Page `/admin/roles` : DataTable, création/édition via drawer, suppression avec confirmation
- Page `/admin/users` : DataTable, drawer RBAC avec rôles, permissions directes, résumé effectif
- PermissionGroup : checkboxes groupées par module avec "tout sélectionner"
- EffectivePermissions : résumé lecture seule avec badges source ("via Rôle X" / "Direct")
- Warning auto-édition, toggle isAdmin
- Tests Vitest pour usePermissions

### Permissions déclarées
- `core.users.view` — Voir les utilisateurs
- `core.users.manage` — Gérer les utilisateurs
- `core.roles.view` — Voir les rôles RBAC
- `core.roles.manage` — Gérer les rôles et permissions
- `GET /api/permissions` accessible à tout utilisateur authentifié (catalogue read-only)

## Tickets Lesstime

- ERP-23 (#343) — Entités Permission et Role
- ERP-24 (#344) — API CRUD Roles & Permissions
- ERP-25 (#345) — Voter Symfony + usePermissions
- ERP-26 (#346) — Interface Admin : Gestion des Rôles
- ERP-27 (#347) — Interface Admin : Permissions Utilisateur

## Test plan

- [ ] `make db-reset` puis vérifier les fixtures (admin/alice/bob, rôles système)
- [ ] Login admin : sidebar affiche Gestion des rôles + Utilisateurs
- [ ] Login alice : sidebar masque ces onglets (pas de permission)
- [ ] Page /admin/roles : CRUD rôles, permissions groupées, protection rôles système
- [ ] Page /admin/users : assignation rôles + permissions directes, résumé effectif
- [ ] Warning auto-édition quand admin modifie ses propres droits
- [ ] `make test` : 115 tests PHPUnit passent
- [ ] `cd frontend && npm run test` : tests Vitest passent

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Matthieu <mtholot19@gmail.com>
Co-authored-by: tristan <tristan@yuno.malio.fr>
Reviewed-on: #7
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-04-17 12:34:38 +00:00

14 KiB

Coltura

CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4. Architecture Modular Monolith DDD.

Architecture Modulaire

Le projet suit une architecture modular monolith pilotee par le backend : chaque module metier est un bounded context autonome, activable/desactivable par tenant. Le module Core est obligatoire.

Principe fondamental : le backend est la source de verite unique.

  • Le backend dicte quels modules sont actifs (config/modules.php).
  • Le backend dicte l'organisation de la sidebar (config/sidebar.php), decouplee des modules eux-memes.
  • Le frontend ne connait rien : il scanne automatiquement les modules comme layers Nuxt et demande la sidebar au backend.

Backend — Organisation par module

src/
  Kernel.php
  Shared/                           # Noyau technique partage
    Domain/
      ValueObject/                  # VO de base (Email...)
      Event/                        # DomainEventInterface
      Contract/                     # Interfaces inter-modules (UserResolverInterface, TenantAwareInterface)
    Application/
      Bus/                          # CommandBusInterface, QueryBusInterface (interfaces seules)
    Infrastructure/
      ApiPlatform/
        Resource/                   # AppVersion, ModulesResource, SidebarResource
        State/                      # AppVersionProvider, ModulesProvider, SidebarProvider
  Module/
    Core/                           # Module obligatoire (auth, users)
      CoreModule.php                # Declaration (ID, LABEL, REQUIRED)
      Domain/
        Entity/                     # Entites Doctrine + API Platform (User)
        Repository/                 # Interfaces repositories (UserRepositoryInterface)
        Event/                      # Domain events (UserCreated)
      Application/
        DTO/                        # UserOutput
      Infrastructure/
        Doctrine/                   # DoctrineUserRepository, Migrations/
        ApiPlatform/
          State/
            Provider/               # MeProvider
            Processor/              # UserPasswordHasherProcessor
        Console/                    # CreateUserCommand
        DataFixtures/               # AppFixtures
    Commercial/                     # Autre module (exemple)
      CommercialModule.php
config/
  modules.php                       # Liste des modules actifs (source de verite activation)
  sidebar.php                       # Structure de la sidebar (source de verite navigation)
  version.yaml
  jwt/                              # Cles JWT
  packages/                         # Config Symfony
migrations/                         # Anciennes migrations Doctrine
infra/dev/                          # Docker dev
infra/prod/                         # Docker prod (multi-stage)

Frontend — Organisation modulaire (auto-detectee)

frontend/
  app/                              # Shell applicatif
    layouts/                        # default.vue, auth.vue
    middleware/                     # auth.global.ts, modules.global.ts
  shared/                           # Code partage (hors modules)
    composables/                    # useApi, useAppVersion, useSidebar
    components/ui/                  # AppTopNav, ...
    stores/                         # auth, ui
    services/                       # auth
    types/                          # SidebarSection, SidebarItem, UserData
    utils/                          # api (Hydra)
  modules/                          # Modules auto-detectes comme layers Nuxt
    core/
      nuxt.config.ts                # Marqueur layer (vide)
      pages/                        # index.vue, login.vue
    commercial/
      nuxt.config.ts
      pages/                        # commercial.vue
  app.vue                           # Composant racine
  nuxt.config.ts                    # Scanne modules/*/ automatiquement
  i18n/locales/                     # Traductions (cles sidebar.*, etc.)
  assets/                           # CSS, images
  public/                           # Fichiers statiques

Endpoints API cles

  • GET /api/version (public) — version de l'app
  • GET /api/modules (public) — liste des IDs de modules actifs
  • GET /api/sidebar (public) — sections de la sidebar + disabledRoutes
    • Filtre automatiquement les items dont le module owner n'est pas actif
    • Les sections vides apres filtrage sont supprimees
    • disabledRoutes = to des items filtres (utilise par le middleware front)
  • GET /api/me (auth) — user courant

Flux d'activation/desactivation d'un module

Pour activer/desactiver un module, tu touches uniquement config/modules.php :

return [
    \App\Module\Core\CoreModule::class,
    // \App\Module\Commercial\CommercialModule::class,  // commente = desactive
];

Cascade automatique :

  1. GET /api/modules ne retourne plus commercial
  2. GET /api/sidebar filtre les items module: 'commercial' → section "Commercial" disparait, ses routes passent dans disabledRoutes
  3. Frontend : sidebar se met a jour, middleware modules.global.ts redirige toute navigation vers /commercial ou /commercial/*
  4. Le code du module reste dans le bundle Nuxt (layer auto-detecte) → reactivation instantanee sans rebuild

Reorganiser la sidebar sans toucher aux modules

Pour deplacer un item (ex: "Commandes fournisseurs") d'une section a une autre, tu edites juste config/sidebar.php :

// Avant : sous Commercial
['label' => 'sidebar.commercial.suppliers', 'to' => '/commercial/suppliers', 'module' => 'commercial'],

// Apres : sous Production (l'item reste "owned" par Commercial, seule sa place change)
[
    'label' => 'sidebar.production.section',
    'items' => [
        ['label' => 'sidebar.commercial.suppliers', 'to' => '/commercial/suppliers', 'module' => 'commercial'],
    ],
],

Le code du module Commercial n'est pas touche.

Regles d'architecture

Backend :

  • Le domaine (Domain/) peut garder les attributs ORM (approche pragmatique) mais les repositories sont des interfaces
  • Communication inter-modules par Shared/Domain/Contract/ ou domain events — jamais d'import direct entre modules
  • Chaque module declare un *Module.php avec ID, LABEL, REQUIRED
  • config/modules.php = seule source de verite pour l'activation
  • config/sidebar.php = seule source de verite pour l'organisation de la sidebar (chaque item reference son module owner via la cle module)
  • Migrations par module dans src/Module/{Module}/Infrastructure/Doctrine/Migrations/
  • Exception connue : avec plusieurs migrations_paths configures, Doctrine Migrations 3.x trie les migrations par FQCN alphabetique et non par version timestamp → ordre d'execution incorrect entre namespaces sur une base vide. Tant que ce n'est pas resolu (via un MigrationsComparator custom ou un upgrade), les migrations d'initialisation critiques (setup user, RBAC, etc.) vivent au namespace racine DoctrineMigrations dans migrations/. Le namespace modulaire reste configure pour les futures migrations applicatives (qui dependent d'un schema deja cree).

Frontend :

  • Chaque module est un layer Nuxt auto-detecte (modules/*/nuxt.config.ts minimal)
  • Un module front ne doit pas importer depuis un autre module — utiliser shared/
  • useSidebar() fetch /api/sidebar et expose sections, disabledRoutes, isRouteDisabled()
  • Le layout default.vue itere sur les sections retournees par l'API, applique t() sur les labels
  • Middleware auth.global.ts charge la sidebar apres authentification
  • Middleware modules.global.ts redirige si la route demandee est dans disabledRoutes
  • Les composables avec state singleton (refs module-level) doivent exposer une fonction reset*() et etre reinitialises au logout (ex: useSidebar().resetSidebar())
  • Interdit : .module.ts, modules-loader.ts, hardcode de la sidebar, edition manuelle de extends dans nuxt.config.ts

Stack

  • Backend : PHP 8.4, Symfony 8.0, API Platform 4, Doctrine ORM, PostgreSQL 16
  • Frontend : Nuxt 4 (SSR off / SPA), Vue 3, Pinia, Tailwind CSS, @malio/layer-ui, nuxt-toast, @nuxtjs/i18n, @nuxt/icon
  • Auth : JWT HTTP-only cookie (lexik/jwt-authentication-bundle), login a /login_check, cookie BEARER
  • Docker : PHP-FPM + Node 24, Nginx (port 8083), PostgreSQL (port 5437)

Commandes

make start           # Demarrer les containers
make stop            # Arreter les containers
make restart         # Redemarrer les containers
make install         # Install complet (composer, migrations, fixtures, build Nuxt)
make reset           # Tout supprimer et reinstaller (supprime la BDD)
make dev-nuxt        # Dev server Nuxt (hot reload, port 3004)
make shell           # Shell dans le container PHP
make shell-root      # Shell root dans le container PHP
make cache-clear     # Vider le cache Symfony
make migration-migrate # Lancer les migrations
make fixtures        # Charger les fixtures
make db-reset        # Reset BDD + migrations + fixtures
make test            # PHPUnit
make php-cs-fixer-allow-risky # Fix code style PHP
make logs-dev        # Tail logs Symfony

Si make cache-clear echoue pour cause de permissions sur var/ :

docker exec -t -u root php-coltura-fpm chown -R www-data:www-data /var/www/html/var
docker exec -t -u www-data php-coltura-fpm php bin/console cache:clear

Conventions

Commits

Format : <type>(<scope optionnel>) : <message> (espace avant et apres :)

Types autorises (minuscules) : build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test

Exemples : feat : add login page, fix(auth) : prevent null token crash

Tags & Versioning

  • La version de l'app est dans config/version.yaml (parametre app.version)
  • A chaque creation de tag, toujours mettre a jour config/version.yaml avec la meme version
  • Faire un commit separe de bump : chore : bump version to v<X.Y.Z>
  • Puis creer le tag et pusher : git tag v<X.Y.Z> && git push origin develop --tags

Nommage

Element Convention Exemple
Module back PascalCase Module/Commercial/
Module front kebab-case modules/commercial/
Module ID snake_case commercial, gestion_rh
Entity PascalCase singulier User.php
Repository interface *RepositoryInterface UserRepositoryInterface.php
Repository impl Doctrine*Repository DoctrineUserRepository.php
DTO *Output / *Input UserOutput.php
API Resource classe dans Infrastructure/ApiPlatform/Resource/ UserResource.php
Provider *Provider MeProvider.php
Processor *Processor UserPasswordHasherProcessor.php
Module declaration back *Module.php CommercialModule.php
Composable front use* useSidebar.ts
Cles i18n sidebar sidebar.<module>.* sidebar.commercial.overview

Backend

  • Toujours declare(strict_types=1) en haut des fichiers PHP
  • Commentaires en francais : tout commentaire PHP (docblock, inline, bloc) doit etre redige en francais. Le code (noms de classes, methodes, variables) reste en anglais. Objectif : faciliter la relecture par l'equipe FR sans polluer l'API publique du code.
  • API Platform : utiliser ApiResource, Providers, Processors — pas de controllers
  • Routes API prefixees /api (via config/routes/api_platform.yaml)
  • Le login (/login_check) est hors prefix /api, nginx reecrit REQUEST_URI vers /login_check
  • PHP CS Fixer : regles Symfony + PSR-12 + strict types
  • Roles : ROLE_ADMIN, ROLE_USER — hierarchie dans security.yaml
  • Permissions RBAC : format obligatoire module.resource[.subresource].action en snake_case, ex : core.users.view, commercial.clients.contacts.edit. Declarees via la methode statique permissions() des *Module.php, synchronisees par la commande app:sync-permissions. Verification via is_granted('module.resource.action') cote API Platform et usePermissions() cote front.
  • PostgreSQL : noms de colonnes toujours en minuscules dans le SQL brut
  • Controllers custom sous /api/ : ajouter priority: 1 sur #[Route] pour eviter le conflit avec API Platform {id}
  • Serialization : pour embarquer une relation (pas IRI), ajouter le groupe du parent aux proprietes de l'entite cible
  • Upload fichiers : utiliser $file->getMimeType() (pas getClientMimeType()) pour valider cote serveur

Frontend

  • TypeScript strict
  • Commentaires en francais : tout commentaire TS/Vue (JSDoc, inline, bloc) doit etre redige en francais. Le code reste en anglais. Meme regle que cote backend.
  • Composable useApi() pour tous les appels API (gere cookies, erreurs, toasts, i18n)
  • Stores Pinia : useAuthStore (auth), useUiStore (ui)
  • Middleware global auth.global.ts protege les routes + charge la sidebar apres login
  • Middleware global modules.global.ts redirige les routes des modules desactives
  • Traductions dans frontend/i18n/locales/ avec le namespace sidebar.* pour la nav
  • 4 espaces d'indentation
  • Les labels de sidebar sont des cles i18n, jamais du texte brut (le layout applique t() dessus)

Nginx

  • /api/* -> Symfony (via try_files + index.php)
  • /api/login_check -> location exact match, fastcgi direct avec REQUEST_URI reecrit en /login_check
  • / -> SPA frontend (frontend/dist/)

Docker

  • Container PHP : php-coltura-fpm
  • Container Nginx : nginx-coltura
  • Container DB : PostgreSQL sur port 5437 (interne et externe)
  • Config Docker dev : infra/dev/.env.docker (override local : infra/dev/.env.docker.local)
  • Config Docker prod : infra/prod/ (Dockerfile multi-stage, docker-compose.prod.yml)
  • Apres modif nginx : docker restart nginx-coltura

Fixtures

  • User admin : admin / admin (ROLE_ADMIN)
  • Users internes : alice / alice, bob / bob (ROLE_USER)

Delegation Codex

Pour les taches mecaniques (tests, boilerplate, renommages, refacto repetitif), delegue a Codex via le plugin codex. Garde Claude pour la reflexion, l'architecture et la verification.

  • Codex = junior dev rapide et pas cher (executions mecaniques)
  • Claude = senior dev qui verifie et reflechit (design, review, decisions)

C'est le meilleur ratio qualite/credits.