Files
Coltura/CLAUDE.md
Matthieu e3025bf2c9 docs(rbac) : plan et spec ticket #343 + conventions permissions
- Spec detaillee des fondations RBAC backend (entites Role/Permission, sync
  command, migration, fixtures, tests) dans docs/rbac/ticket-343-spec.md
- Ajout CLAUDE.md des regles projet : commentaires francais (PHP + TS/Vue)
  et convention de nommage des permissions module.resource[.sub].action

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:26:49 +02:00

13 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/

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)