chore : rename Coltura to Starseed
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled

- Rename project name across code, configs, docs, dev/prod infra
- Dev: DOCKER_APP_NAME + POSTGRES_DB switched to starseed, containers become php-starseed-fpm / nginx-starseed / starseed-db-1
- Dev: mount nginx.conf on default.conf instead of starseed.conf to avoid alphabetical-order clash with image's default site
- Makefile: export CURRENT_UID/CURRENT_GID at top level so docker compose builds (db-reset etc.) get them
- Prod: image registry path, container_name, volumes, vhost server_name + paths, DATABASE_URL, CORS, CI workflow
- Add doc/prompt-rename-prod.md with the migration runbook for the prod server (DB rename, FS move, vhost, Let's Encrypt)
This commit is contained in:
Matthieu
2026-05-19 08:24:19 +02:00
parent cd17248427
commit 93cbd48bf5
40 changed files with 476 additions and 234 deletions

View File

@@ -10,14 +10,14 @@ Le resultat attendu est un socle de persistance activable par tenant via `config
### IN
- Creer le module `/home/m-tristan/workspace/Coltura/src/Module/Sites/SitesModule.php` avec `ID = 'sites'`, `LABEL = 'Sites'`, `REQUIRED = false`, et une methode statique `permissions()` declarant les deux codes RBAC `sites.view` et `sites.manage`.
- Creer le module `/home/m-tristan/workspace/Starseed/src/Module/Sites/SitesModule.php` avec `ID = 'sites'`, `LABEL = 'Sites'`, `REQUIRED = false`, et une methode statique `permissions()` declarant les deux codes RBAC `sites.view` et `sites.manage`.
- Creer l'entite Doctrine `Site` avec `id`, `name` (unique), `city`, `postalCode`, `color`, `fullAddress`, `createdAt`, `updatedAt` et les contraintes de validation applicatives associees (NotBlank, Length, Regex hex `#RRGGBB`, Regex CP FR `^\d{5}$`, UniqueEntity).
- Creer l'interface `SiteRepositoryInterface` et son implementation Doctrine `DoctrineSiteRepository`, avec un contrat CRUD complet (`findById`, `findByName`, `findAllOrderedByName`, `save`, `remove`) en anticipation du ticket 2.
- Creer une migration Doctrine creant la table `site` avec son index unique `uniq_site_name`. La migration est placee dans `/home/m-tristan/workspace/Coltura/migrations/` au namespace racine `DoctrineMigrations` conformement a l'exception documentee dans `CLAUDE.md` (bug de tri alphabetique des migrations multi-namespaces dans Doctrine Migrations 3.x).
- Creer une migration Doctrine creant la table `site` avec son index unique `uniq_site_name`. La migration est placee dans `/home/m-tristan/workspace/Starseed/migrations/` au namespace racine `DoctrineMigrations` conformement a l'exception documentee dans `CLAUDE.md` (bug de tri alphabetique des migrations multi-namespaces dans Doctrine Migrations 3.x).
- Creer `SitesFixtures` creant trois sites de demonstration : `Chatellerault` (`#056CF2`), `Saint-Jean` (`#10B981`), `Pommevic` (`#F59E0B`). Fixtures idempotentes via lookup par nom lorsque le purger Doctrine est desactive.
- Enregistrer `SitesModule::class` dans `/home/m-tristan/workspace/Coltura/config/modules.php` pour l'activer par defaut.
- Declarer le mapping Doctrine du module dans `/home/m-tristan/workspace/Coltura/config/packages/doctrine.yaml` (inconditionnel, le mapping reste charge meme si le module est retire de `modules.php`).
- Enregistrer l'alias service `SiteRepositoryInterface → DoctrineSiteRepository` dans `/home/m-tristan/workspace/Coltura/config/services.yaml`.
- Enregistrer `SitesModule::class` dans `/home/m-tristan/workspace/Starseed/config/modules.php` pour l'activer par defaut.
- Declarer le mapping Doctrine du module dans `/home/m-tristan/workspace/Starseed/config/packages/doctrine.yaml` (inconditionnel, le mapping reste charge meme si le module est retire de `modules.php`).
- Enregistrer l'alias service `SiteRepositoryInterface → DoctrineSiteRepository` dans `/home/m-tristan/workspace/Starseed/config/services.yaml`.
- Ajouter deux suites de tests PHPUnit :
- `SiteTest` (pure `TestCase`) pour le comportement de l'entite (constructeur, getters/setters, lifecycle `PreUpdate`).
- `SiteValidationTest` (`KernelTestCase`) pour la validation complete : regex hex, regex CP FR, NotBlank, Length, UniqueEntity via Doctrine.
@@ -25,7 +25,7 @@ Le resultat attendu est un socle de persistance activable par tenant via `config
### OUT
- Ticket `#02` : relation `User ↔ Site` (FK ou ManyToMany selon decision UX), expose les sites de l'utilisateur courant via `/api/me` et propage l'autorisation au niveau des ressources decoupees par site.
- Ticket `#03` : integration dans la navbar Coltura (selecteur de site actif, persistance du choix cote front, consommation du flux issu du ticket 2).
- Ticket `#03` : integration dans la navbar Starseed (selecteur de site actif, persistance du choix cote front, consommation du flux issu du ticket 2).
- Ticket `#04` : ecran d'administration CRUD des sites (page admin/sites, DataTable, drawer creation/edition, modale suppression, API Platform `Site` resource avec voters RBAC).
- Gestion des soft-deletes sur `Site` : non introduite dans ce ticket.
- Rattachement historique ou audit trail des modifications : hors scope.
@@ -34,38 +34,38 @@ Le resultat attendu est un socle de persistance activable par tenant via `config
### Domaine — Entité
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Domain/Entity/Site.php` : entite Doctrine porteuse des attributs metier (nom unique, ville, code postal FR, couleur hex, adresse complete multi-ligne) et des timestamps auto-maintenus via lifecycle callbacks.
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Domain/Entity/Site.php` : entite Doctrine porteuse des attributs metier (nom unique, ville, code postal FR, couleur hex, adresse complete multi-ligne) et des timestamps auto-maintenus via lifecycle callbacks.
### Domaine — Repository
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Domain/Repository/SiteRepositoryInterface.php` : contrat d'acces domaine a l'entite Site (CRUD applicatif ; l'acces API Platform du ticket 4 utilisera le provider Doctrine par defaut).
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Domain/Repository/SiteRepositoryInterface.php` : contrat d'acces domaine a l'entite Site (CRUD applicatif ; l'acces API Platform du ticket 4 utilisera le provider Doctrine par defaut).
### Infrastructure — Doctrine
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/Doctrine/DoctrineSiteRepository.php` : implementation Doctrine de `SiteRepositoryInterface` basee sur `ServiceEntityRepository`.
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/Doctrine/DoctrineSiteRepository.php` : implementation Doctrine de `SiteRepositoryInterface` basee sur `ServiceEntityRepository`.
### Infrastructure — Migration
- `/home/m-tristan/workspace/Coltura/migrations/Version<timestamp>.php` : migration racine (namespace `DoctrineMigrations`) qui cree la table `site` et son index unique. Emplacement racine et non modulaire, cf. exception documentee dans `CLAUDE.md` (bug Doctrine 3.x sur le tri alphabetique des migrations multi-namespaces).
- `/home/m-tristan/workspace/Starseed/migrations/Version<timestamp>.php` : migration racine (namespace `DoctrineMigrations`) qui cree la table `site` et son index unique. Emplacement racine et non modulaire, cf. exception documentee dans `CLAUDE.md` (bug Doctrine 3.x sur le tri alphabetique des migrations multi-namespaces).
### Infrastructure — DataFixtures
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/DataFixtures/SitesFixtures.php` : fixture Doctrine seedant les 3 sites de demonstration. Ne declare pas de `DependentFixtureInterface` (aucune dependance a AppFixtures dans ce ticket).
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/DataFixtures/SitesFixtures.php` : fixture Doctrine seedant les 3 sites de demonstration. Ne declare pas de `DependentFixtureInterface` (aucune dependance a AppFixtures dans ce ticket).
### Module — Declaration
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/SitesModule.php` : marker class du module avec `ID`, `LABEL`, `REQUIRED` et `permissions()`. Meme pattern que `CoreModule`.
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/SitesModule.php` : marker class du module avec `ID`, `LABEL`, `REQUIRED` et `permissions()`. Meme pattern que `CoreModule`.
### Tests
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Domain/Entity/SiteTest.php` : tests unitaires purs (`TestCase`) couvrant constructeur, getters, setters et lifecycle `PreUpdate`.
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Domain/Entity/SiteValidationTest.php` : tests de validation (`KernelTestCase`) couvrant regex hex, regex CP FR, NotBlank, Length sur tous les champs, et `UniqueEntity` via la DB de test.
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Domain/Entity/SiteTest.php` : tests unitaires purs (`TestCase`) couvrant constructeur, getters, setters et lifecycle `PreUpdate`.
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Domain/Entity/SiteValidationTest.php` : tests de validation (`KernelTestCase`) couvrant regex hex, regex CP FR, NotBlank, Length sur tous les champs, et `UniqueEntity` via la DB de test.
## 4. Fichiers à modifier
- `/home/m-tristan/workspace/Coltura/config/modules.php` : ajouter `App\Module\Sites\SitesModule::class` dans le tableau de retour. Le module est actif par defaut. Le commenter suffit a le desactiver sans autre intervention (les permissions deviendront orphelines a la prochaine sync mais la table reste).
- `/home/m-tristan/workspace/Coltura/config/packages/doctrine.yaml` : ajouter une mapping `Sites:` alignee sur le pattern du module `Core:`. Le mapping est inconditionnel : il reste declare meme si `SitesModule::class` est retire de `modules.php`. Le commentaire doit etre explicite sur cette decoupe (activation fonctionnelle via `modules.php`, structure DB via la mapping Doctrine).
- `/home/m-tristan/workspace/Coltura/config/services.yaml` : ajouter l'alias `App\Module\Sites\Domain\Repository\SiteRepositoryInterface``App\Module\Sites\Infrastructure\Doctrine\DoctrineSiteRepository`. Pattern aligne sur les trois aliases Core existants.
- `/home/m-tristan/workspace/Starseed/config/modules.php` : ajouter `App\Module\Sites\SitesModule::class` dans le tableau de retour. Le module est actif par defaut. Le commenter suffit a le desactiver sans autre intervention (les permissions deviendront orphelines a la prochaine sync mais la table reste).
- `/home/m-tristan/workspace/Starseed/config/packages/doctrine.yaml` : ajouter une mapping `Sites:` alignee sur le pattern du module `Core:`. Le mapping est inconditionnel : il reste declare meme si `SitesModule::class` est retire de `modules.php`. Le commentaire doit etre explicite sur cette decoupe (activation fonctionnelle via `modules.php`, structure DB via la mapping Doctrine).
- `/home/m-tristan/workspace/Starseed/config/services.yaml` : ajouter l'alias `App\Module\Sites\Domain\Repository\SiteRepositoryInterface``App\Module\Sites\Infrastructure\Doctrine\DoctrineSiteRepository`. Pattern aligne sur les trois aliases Core existants.
## 5. Schéma cible — mapping Doctrine
@@ -152,7 +152,7 @@ Sites:
## 6. Plan de migration Doctrine
La migration est placee dans `/home/m-tristan/workspace/Coltura/migrations/Version<timestamp>.php` au namespace racine `DoctrineMigrations`, conformement a l'exception documentee dans `CLAUDE.md`. Tant que le bug de tri alphabetique des `MigrationsComparator` multi-namespaces n'est pas resolu (via un comparator custom ou un upgrade Doctrine), toute migration d'initialisation (creation de table sur base vide) reste au namespace racine.
La migration est placee dans `/home/m-tristan/workspace/Starseed/migrations/Version<timestamp>.php` au namespace racine `DoctrineMigrations`, conformement a l'exception documentee dans `CLAUDE.md`. Tant que le bug de tri alphabetique des `MigrationsComparator` multi-namespaces n'est pas resolu (via un comparator custom ou un upgrade Doctrine), toute migration d'initialisation (creation de table sur base vide) reste au namespace racine.
### `up()` — ordre des instructions
@@ -289,7 +289,7 @@ Trois sites de demonstration, avec des couleurs distinctes suffisamment contrast
| Nom | Ville | CP | Couleur | Commentaire |
|-----|-------|-----|---------|-------------|
| Chatellerault | Chatellerault | 86100 | `#056CF2` | Couleur imposee par le ticket (bleu Coltura). |
| Chatellerault | Chatellerault | 86100 | `#056CF2` | Couleur imposee par le ticket (bleu Starseed). |
| Saint-Jean | Saint-Jean-de-Sauves | 86330 | `#10B981` | Vert emeraude (contraste avec le bleu). |
| Pommevic | Pommevic | 82400 | `#F59E0B` | Ambre (troisieme teinte nettement distincte). |

View File

@@ -40,70 +40,70 @@ Le resultat attendu est un module Sites utilisable de bout en bout cote admin (c
### Backend — Module Sites
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Domain/Exception/SiteNotAuthorizedException.php` : exception domaine levee si un user tente de switcher vers un site qui ne fait pas partie de ses sites autorises. Porte un message i18n-able et le code du site cible.
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/ApiPlatform/Resource/CurrentSiteResource.php` : ressource API Platform **virtuelle** (pas de mapping Doctrine, pas de `#[ORM\Entity]`). Sert uniquement a porter l'operation `Patch` `/me/current-site`. Expose une propriete `site: Site` en denormalisation pour recevoir l'IRI du site cible, et re-expose l'user courant en normalisation via le groupe `me:read`.
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/ApiPlatform/State/Processor/CurrentSiteProcessor.php` : processor dedie a l'operation de switch. Valide l'appartenance du site aux `user.sites`, positionne `user.currentSite`, flush, retourne l'user.
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/ApiPlatform/EventListener/SiteNotAuthorizedExceptionListener.php` : listener Kernel qui convertit `SiteNotAuthorizedException` en `ForbiddenHttpException` (403) avec un code i18n stable (cf. pattern `SystemRoleDeletionException` du module Core dans les tickets RBAC precedents).
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Domain/Exception/SiteNotAuthorizedException.php` : exception domaine levee si un user tente de switcher vers un site qui ne fait pas partie de ses sites autorises. Porte un message i18n-able et le code du site cible.
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/ApiPlatform/Resource/CurrentSiteResource.php` : ressource API Platform **virtuelle** (pas de mapping Doctrine, pas de `#[ORM\Entity]`). Sert uniquement a porter l'operation `Patch` `/me/current-site`. Expose une propriete `site: Site` en denormalisation pour recevoir l'IRI du site cible, et re-expose l'user courant en normalisation via le groupe `me:read`.
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/ApiPlatform/State/Processor/CurrentSiteProcessor.php` : processor dedie a l'operation de switch. Valide l'appartenance du site aux `user.sites`, positionne `user.currentSite`, flush, retourne l'user.
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/ApiPlatform/EventListener/SiteNotAuthorizedExceptionListener.php` : listener Kernel qui convertit `SiteNotAuthorizedException` en `ForbiddenHttpException` (403) avec un code i18n stable (cf. pattern `SystemRoleDeletionException` du module Core dans les tickets RBAC precedents).
### Backend — Migration
- `/home/m-tristan/workspace/Coltura/migrations/Version<timestamp2>.php` : migration au namespace racine `DoctrineMigrations` (cf. exception Doctrine documentee dans `CLAUDE.md`). Cree la table `user_site` et la colonne `user.current_site_id` avec les FKs et cascades appropriees.
- `/home/m-tristan/workspace/Starseed/migrations/Version<timestamp2>.php` : migration au namespace racine `DoctrineMigrations` (cf. exception Doctrine documentee dans `CLAUDE.md`). Cree la table `user_site` et la colonne `user.current_site_id` avec les FKs et cascades appropriees.
### Backend — Tests API
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Api/SiteApiTest.php` : CRUD complet `/api/sites` avec matrices RBAC (admin, user avec `sites.view`, user avec `sites.manage`, user sans permission).
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Api/CurrentSiteSwitchApiTest.php` : PATCH `/me/current-site` (OK avec site autorise, 403 avec site non autorise, 400 avec IRI invalide).
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Api/MeEndpointSitesTest.php` : `/api/me` expose bien `sites` et `currentSite` en objets. User sans site : `sites: []`, `currentSite: null`.
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Api/SiteCascadeTest.php` : suppression d'un site `X` → toutes les lignes `user_site` referencant `X` sont supprimees, tous les users ayant `X` en `currentSite` voient leur `currentSite` repasser a `NULL`.
- `/home/m-tristan/workspace/Coltura/tests/Module/Core/Api/UserRbacSitesApiTest.php` : extension du endpoint `/api/users/{id}/rbac` — ajout de `sites: []` dans le payload, retrait du `currentSite` quand le site retire etait le courant.
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Api/SiteApiTest.php` : CRUD complet `/api/sites` avec matrices RBAC (admin, user avec `sites.view`, user avec `sites.manage`, user sans permission).
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Api/CurrentSiteSwitchApiTest.php` : PATCH `/me/current-site` (OK avec site autorise, 403 avec site non autorise, 400 avec IRI invalide).
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Api/MeEndpointSitesTest.php` : `/api/me` expose bien `sites` et `currentSite` en objets. User sans site : `sites: []`, `currentSite: null`.
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Api/SiteCascadeTest.php` : suppression d'un site `X` → toutes les lignes `user_site` referencant `X` sont supprimees, tous les users ayant `X` en `currentSite` voient leur `currentSite` repasser a `NULL`.
- `/home/m-tristan/workspace/Starseed/tests/Module/Core/Api/UserRbacSitesApiTest.php` : extension du endpoint `/api/users/{id}/rbac` — ajout de `sites: []` dans le payload, retrait du `currentSite` quand le site retire etait le courant.
### Frontend — Module Sites (nouveau layer)
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/nuxt.config.ts` : marker de layer Nuxt (vide). Declenche l'auto-detection par `nuxt.config.ts` racine.
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/pages/admin/sites.vue` : page `/admin/sites`. Reutilise les composants Malio UI (`MalioDataTable`, `MalioButton`, `MalioInputText`, `MalioInputTextArea`). Pattern identique a `frontend/modules/core/pages/admin/roles.vue`.
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/components/SiteDrawer.vue` : drawer creation/edition. Formulaire 5 champs (nom, ville, CP, couleur avec preview puce, adresse). Valide cote front sur le submit avant d'envoyer.
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/components/SiteDeleteModal.vue` : modale de confirmation suppression. Pattern aligne sur `RoleDeleteModal.vue`.
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/nuxt.config.ts` : marker de layer Nuxt (vide). Declenche l'auto-detection par `nuxt.config.ts` racine.
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/pages/admin/sites.vue` : page `/admin/sites`. Reutilise les composants Malio UI (`MalioDataTable`, `MalioButton`, `MalioInputText`, `MalioInputTextArea`). Pattern identique a `frontend/modules/core/pages/admin/roles.vue`.
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/components/SiteDrawer.vue` : drawer creation/edition. Formulaire 5 champs (nom, ville, CP, couleur avec preview puce, adresse). Valide cote front sur le submit avant d'envoyer.
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/components/SiteDeleteModal.vue` : modale de confirmation suppression. Pattern aligne sur `RoleDeleteModal.vue`.
### Frontend — Types partages
- `/home/m-tristan/workspace/Coltura/frontend/shared/types/sites.ts` : types `Site`, `SiteInput`. Pattern identique a `frontend/shared/types/rbac.ts`.
- `/home/m-tristan/workspace/Starseed/frontend/shared/types/sites.ts` : types `Site`, `SiteInput`. Pattern identique a `frontend/shared/types/rbac.ts`.
### Tests frontend (optionnels mais recommandes)
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/pages/admin/sites.spec.ts` : smoke test Vitest (rendu + clic bouton "Nouveau site" ouvre le drawer).
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/pages/admin/sites.spec.ts` : smoke test Vitest (rendu + clic bouton "Nouveau site" ouvre le drawer).
## 4. Fichiers à modifier
### Backend — Module Core
- `/home/m-tristan/workspace/Coltura/src/Module/Core/Domain/Entity/User.php` :
- `/home/m-tristan/workspace/Starseed/src/Module/Core/Domain/Entity/User.php` :
- Ajouter `private Collection $sites;` (M2M, `fetch: EAGER`, `JoinTable: user_site`), groupes `me:read`, `user:list`, `user:rbac:read`, `user:rbac:write`.
- Ajouter `private ?Site $currentSite = null;` (M2O, `fetch: EAGER`, `onDelete: 'SET NULL'`), groupe `me:read`.
- Initialiser `$this->sites = new ArrayCollection();` dans le constructeur.
- Ajouter les accesseurs `getSites()`, `addSite(Site)`, `removeSite(Site)`, `hasSite(Site)`, `getCurrentSite()`, `setCurrentSite(?Site)`.
- **Important** : `import` direct `App\Module\Sites\Domain\Entity\Site`. Ce ticket assume le couplage Core → Sites au niveau code PHP (cf. Risque 1).
- `/home/m-tristan/workspace/Coltura/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessor.php` :
- `/home/m-tristan/workspace/Starseed/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessor.php` :
- Etendre le contrat d'entree pour accepter le champ `sites` (collection d'IRIs denormalisees en `Collection<Site>`).
- Apres l'application des roles et permissions directes, detecter si `currentSite` du user cible n'est plus dans la nouvelle collection `sites` → basculer `currentSite` a `null`.
- Conserver toutes les gardes existantes (auto-suicide admin, dernier admin global).
- `/home/m-tristan/workspace/Coltura/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php` :
- `/home/m-tristan/workspace/Starseed/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php` :
- Declarer l'implementation `DependentFixtureInterface` avec `getDependencies(): [SitesFixtures::class]` (inversion de l'ordre actuel : AppFixtures doit tourner **apres** SitesFixtures pour pouvoir reference les sites).
- Rattacher chaque user a au moins un site : `admin` a tous les sites (`Chatellerault`, `Saint-Jean`, `Pommevic`), `alice` a `Chatellerault`, `bob` a `Saint-Jean`.
- Positionner `currentSite` : `admin.currentSite = Chatellerault`, `alice.currentSite = Chatellerault`, `bob.currentSite = Saint-Jean`.
### Backend — Module Sites
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Domain/Entity/Site.php` :
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Domain/Entity/Site.php` :
- Ajouter les attributs `#[ApiResource]` + operations (cf. section 5 Schema).
- Ajouter les groupes de serialisation `site:read`, `site:write`, `me:read` sur les proprietes scalaires.
- Ajouter la relation inverse `private Collection $users;` (M2M mappedBy=`sites`), **sans** groupe de serialisation (pas d'exposition API cote Site).
- Initialiser `$this->users = new ArrayCollection();` dans le constructeur.
- Ajouter les accesseurs `getUsers()` pour les besoins metier (count / cascade manuel si besoin).
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/DataFixtures/SitesFixtures.php` : aucun changement de contenu, mais verifier que la fixture n'est plus en bout de chaine de dependance (AppFixtures depend d'elle maintenant).
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/DataFixtures/SitesFixtures.php` : aucun changement de contenu, mais verifier que la fixture n'est plus en bout de chaine de dependance (AppFixtures depend d'elle maintenant).
### Backend — Configuration
- `/home/m-tristan/workspace/Coltura/config/sidebar.php` : inserer l'entree `Sites` dans la section `sidebar.general.section` entre `sidebar.core.users` et `sidebar.general.logout` :
- `/home/m-tristan/workspace/Starseed/config/sidebar.php` : inserer l'entree `Sites` dans la section `sidebar.general.section` entre `sidebar.core.users` et `sidebar.general.logout` :
```php
[
'label' => 'sidebar.core.sites',
@@ -113,18 +113,18 @@ Le resultat attendu est un module Sites utilisable de bout en bout cote admin (c
'permission' => 'sites.view',
],
```
- `/home/m-tristan/workspace/Coltura/config/services.yaml` : aucun changement requis. `CurrentSiteProcessor`, `SiteNotAuthorizedExceptionListener` sont autoconfigures.
- `/home/m-tristan/workspace/Starseed/config/services.yaml` : aucun changement requis. `CurrentSiteProcessor`, `SiteNotAuthorizedExceptionListener` sont autoconfigures.
### Frontend
- `/home/m-tristan/workspace/Coltura/frontend/modules/core/components/UserRbacDrawer.vue` :
- `/home/m-tristan/workspace/Starseed/frontend/modules/core/components/UserRbacDrawer.vue` :
- Charger `GET /api/sites?itemsPerPage=999` a l'ouverture du drawer (parallelement aux roles et permissions deja charges).
- Ajouter une section `sidebar.admin.usersDrawer.sitesSection` sous la section permissions directes, avec un groupe de `MalioCheckbox` par site (ou un `MalioMultiSelect` si le composant existe dans `@malio/layer-ui`).
- Etendre le payload `PATCH /api/users/{id}/rbac` avec `sites: Array<string>` (IRIs).
- Auto-refresh de l'auth store apres save si `isSelfEdit` (deja present, conserver).
- `/home/m-tristan/workspace/Coltura/frontend/shared/types/rbac.ts` : ajouter le champ `sites: string[]` a `UserListItem` (IRIs de sites attaches).
- `/home/m-tristan/workspace/Coltura/frontend/shared/stores/auth.ts` : le store auth expose deja `user` via `/api/me`. Aucune modification requise, les nouveaux champs `sites` et `currentSite` suivent automatiquement via la typologie — a condition de mettre a jour le type `UserData` dans `shared/types/` (ajouter `sites: Site[]` et `currentSite: Site | null`).
- `/home/m-tristan/workspace/Coltura/frontend/i18n/locales/fr.json` : cles
- `/home/m-tristan/workspace/Starseed/frontend/shared/types/rbac.ts` : ajouter le champ `sites: string[]` a `UserListItem` (IRIs de sites attaches).
- `/home/m-tristan/workspace/Starseed/frontend/shared/stores/auth.ts` : le store auth expose deja `user` via `/api/me`. Aucune modification requise, les nouveaux champs `sites` et `currentSite` suivent automatiquement via la typologie — a condition de mettre a jour le type `UserData` dans `shared/types/` (ajouter `sites: Site[]` et `currentSite: Site | null`).
- `/home/m-tristan/workspace/Starseed/frontend/i18n/locales/fr.json` : cles
- `sidebar.core.sites` = "Sites".
- `admin.sites.title`, `admin.sites.newSite`, `admin.sites.editSite`, `admin.sites.createSite`, `admin.sites.noSites`.
- `admin.sites.table.{name, city, postalCode, color, fullAddress}`.
@@ -228,7 +228,7 @@ final class CurrentSiteResource
## 6. Plan de migration Doctrine
La migration est placee dans `/home/m-tristan/workspace/Coltura/migrations/Version<timestamp2>.php` au namespace racine (cf. Risque 2 du ticket 1 et `CLAUDE.md`).
La migration est placee dans `/home/m-tristan/workspace/Starseed/migrations/Version<timestamp2>.php` au namespace racine (cf. Risque 2 du ticket 1 et `CLAUDE.md`).
### `up()` — ordre des instructions

View File

@@ -77,42 +77,42 @@ Resultat attendu : apres merge, un user avec ≥ 1 site voit une barre sous la n
### Frontend — Module Sites (layer deja cree au ticket 2)
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/components/SiteSelector.vue` : wrapper Vue autour de `MalioSiteSelector`. Branche `useCurrentSite()`, gere l'optimistic update et les toasts.
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/composables/useCurrentSite.ts` : composable global exposant l'etat `currentSite` / `availableSites`, les actions `switchSite`, `resetCurrentSite`, et un flag `switching: Ref<boolean>` pour desactiver le selecteur pendant une requete en vol.
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/components/SiteSelector.vue` : wrapper Vue autour de `MalioSiteSelector`. Branche `useCurrentSite()`, gere l'optimistic update et les toasts.
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/composables/useCurrentSite.ts` : composable global exposant l'etat `currentSite` / `availableSites`, les actions `switchSite`, `resetCurrentSite`, et un flag `switching: Ref<boolean>` pour desactiver le selecteur pendant une requete en vol.
### Frontend — Shared
- `/home/m-tristan/workspace/Coltura/frontend/shared/composables/useModules.ts` : composable qui charge `/api/modules` et expose `isModuleActive(id: string): boolean`. Pattern aligne sur `useSidebar()` : ref singleton au niveau module, chargement idempotent, `resetModules()` expose pour le logout.
- `/home/m-tristan/workspace/Coltura/frontend/shared/utils/color.ts` : fonctions utilitaires de couleur, au minimum :
- `/home/m-tristan/workspace/Starseed/frontend/shared/composables/useModules.ts` : composable qui charge `/api/modules` et expose `isModuleActive(id: string): boolean`. Pattern aligne sur `useSidebar()` : ref singleton au niveau module, chargement idempotent, `resetModules()` expose pour le logout.
- `/home/m-tristan/workspace/Starseed/frontend/shared/utils/color.ts` : fonctions utilitaires de couleur, au minimum :
- `parseHex(hex: string): { r: number; g: number; b: number }` — tolere la casse, rejette les formats hors `#RRGGBB`.
- `getRelativeLuminance({r, g, b}): number` — formule WCAG standard.
- `getReadableTextColor(hex: string): 'black' | 'white'` — renvoie `'black'` si la luminance > 0.5, `'white'` sinon. Seuil simple, suffisant pour un CRM interne (pas WCAG AAA).
### Frontend — Tests
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/composables/__tests__/useCurrentSite.spec.ts` : Vitest. Tests :
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/composables/__tests__/useCurrentSite.spec.ts` : Vitest. Tests :
- `switchSite` met a jour l'etat localement avant la requete (optimistic).
- Si la requete reussit, l'etat reste aligne.
- Si la requete echoue, l'etat rollback a l'ancien `currentSite`.
- `resetCurrentSite` vide l'etat.
- `/home/m-tristan/workspace/Coltura/frontend/shared/composables/__tests__/useModules.spec.ts` : Vitest. Tests `isModuleActive` apres chargement, `resetModules` vide l'etat.
- `/home/m-tristan/workspace/Coltura/frontend/shared/utils/__tests__/color.spec.ts` : Vitest. Jeu de donnees sur `getReadableTextColor` : `#000000` → white, `#FFFFFF` → black, `#056CF2` (bleu Coltura) → white, `#F59E0B` (ambre) → black, `#10B981` (vert) → black ou white selon seuil (a verifier). Tester aussi le rejet de formats invalides.
- `/home/m-tristan/workspace/Coltura/frontend/modules/sites/components/__tests__/SiteSelector.spec.ts` : smoke test Vitest.
- `/home/m-tristan/workspace/Starseed/frontend/shared/composables/__tests__/useModules.spec.ts` : Vitest. Tests `isModuleActive` apres chargement, `resetModules` vide l'etat.
- `/home/m-tristan/workspace/Starseed/frontend/shared/utils/__tests__/color.spec.ts` : Vitest. Jeu de donnees sur `getReadableTextColor` : `#000000` → white, `#FFFFFF` → black, `#056CF2` (bleu Starseed) → white, `#F59E0B` (ambre) → black, `#10B981` (vert) → black ou white selon seuil (a verifier). Tester aussi le rejet de formats invalides.
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/components/__tests__/SiteSelector.spec.ts` : smoke test Vitest.
## 4. Fichiers à modifier
- `/home/m-tristan/workspace/Coltura/frontend/package.json` : upgrade `@malio/layer-ui` vers la version qui inclut `MalioSiteSelector`. Commit du `package-lock.json` dans le meme changeset.
- `/home/m-tristan/workspace/Coltura/frontend/shared/types/user-data.ts` : ajouter les champs
- `/home/m-tristan/workspace/Starseed/frontend/package.json` : upgrade `@malio/layer-ui` vers la version qui inclut `MalioSiteSelector`. Commit du `package-lock.json` dans le meme changeset.
- `/home/m-tristan/workspace/Starseed/frontend/shared/types/user-data.ts` : ajouter les champs
```ts
sites: Site[]
currentSite: Site | null
```
Import du type `Site` depuis `./sites`. Note : si le type `Site` a deja ete introduit au ticket 2, reutiliser ; sinon, ce ticket le cree dans `frontend/shared/types/sites.ts`.
- `/home/m-tristan/workspace/Coltura/frontend/shared/types/sites.ts` : si absent, creer avec l'interface `Site` (cf. section Schema ticket 2 pour la forme). Si present, aucune modification.
- `/home/m-tristan/workspace/Coltura/frontend/app/layouts/default.vue` : integrer `SiteSelector` sous le header, avant `<main>`, dans le flex column. Rendu conditionnel via `v-if="showSiteSelector"` ou via un `defineAsyncComponent` chargement lazy si on veut eviter l'import statique quand le module est off.
- `/home/m-tristan/workspace/Coltura/frontend/app/middleware/auth.global.ts` : ajouter le chargement de `useModules().loadModules()` apres `loadSidebar()`. Necessaire pour que `isModuleActive` soit resolu quand le layout se rend.
- `/home/m-tristan/workspace/Coltura/frontend/modules/core/pages/logout.vue` : appeler `useCurrentSite().resetCurrentSite()` et `useModules().resetModules()` apres le `auth.logout()`, aligne sur le pattern `resetSidebar()` deja present.
- `/home/m-tristan/workspace/Coltura/frontend/i18n/locales/fr.json` : ajouter les cles
- `/home/m-tristan/workspace/Starseed/frontend/shared/types/sites.ts` : si absent, creer avec l'interface `Site` (cf. section Schema ticket 2 pour la forme). Si present, aucune modification.
- `/home/m-tristan/workspace/Starseed/frontend/app/layouts/default.vue` : integrer `SiteSelector` sous le header, avant `<main>`, dans le flex column. Rendu conditionnel via `v-if="showSiteSelector"` ou via un `defineAsyncComponent` chargement lazy si on veut eviter l'import statique quand le module est off.
- `/home/m-tristan/workspace/Starseed/frontend/app/middleware/auth.global.ts` : ajouter le chargement de `useModules().loadModules()` apres `loadSidebar()`. Necessaire pour que `isModuleActive` soit resolu quand le layout se rend.
- `/home/m-tristan/workspace/Starseed/frontend/modules/core/pages/logout.vue` : appeler `useCurrentSite().resetCurrentSite()` et `useModules().resetModules()` apres le `auth.logout()`, aligne sur le pattern `resetSidebar()` deja present.
- `/home/m-tristan/workspace/Starseed/frontend/i18n/locales/fr.json` : ajouter les cles
```json
"sites": {
"selector": {

View File

@@ -34,50 +34,50 @@ Le ticket livre aussi une documentation developpeur (`docs/modules/site-aware.md
### Shared — Contrat
- `/home/m-tristan/workspace/Coltura/src/Shared/Domain/Contract/SiteAwareInterface.php` : interface minimale. Depends uniquement du type `App\Module\Sites\Domain\Entity\Site`, qui est deja couple cote Core depuis le ticket 2 — le placement dans Shared n'introduit pas de nouvelle dependance transversale non souhaitee.
- `/home/m-tristan/workspace/Starseed/src/Shared/Domain/Contract/SiteAwareInterface.php` : interface minimale. Depends uniquement du type `App\Module\Sites\Domain\Entity\Site`, qui est deja couple cote Core depuis le ticket 2 — le placement dans Shared n'introduit pas de nouvelle dependance transversale non souhaitee.
### Module Sites — Application
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Application/Service/CurrentSiteProvider.php` : service injecte partout ou le site courant doit etre lu (extensions, processor, futurs voters). Gere les trois cas de retour `null` : pas d'user, `currentSite` null, module desactive.
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Application/Service/CurrentSiteProvider.php` : service injecte partout ou le site courant doit etre lu (extensions, processor, futurs voters). Gere les trois cas de retour `null` : pas d'user, `currentSite` null, module desactive.
### Module Sites — Infrastructure
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/ApiPlatform/Extension/SiteScopedQueryExtension.php` : une seule classe, implementant a la fois `QueryCollectionExtensionInterface` et `QueryItemExtensionInterface`. Le comportement est identique pour les deux, modulo que l'item manque retourne 404 (API Platform converti un `getOneOrNullResult` null en 404).
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/ApiPlatform/State/Processor/SiteAwareInjectionProcessor.php` : decorator sur le persist processor Doctrine. Injecte le site courant sur `$data` si applicable, puis delegue a `$persistProcessor`.
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/ApiPlatform/Extension/SiteScopedQueryExtension.php` : une seule classe, implementant a la fois `QueryCollectionExtensionInterface` et `QueryItemExtensionInterface`. Le comportement est identique pour les deux, modulo que l'item manque retourne 404 (API Platform converti un `getOneOrNullResult` null en 404).
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/ApiPlatform/State/Processor/SiteAwareInjectionProcessor.php` : decorator sur le persist processor Doctrine. Injecte le site courant sur `$data` si applicable, puis delegue a `$persistProcessor`.
### Documentation
- `/home/m-tristan/workspace/Coltura/docs/modules/site-aware.md` : guide developpeur (cf. contenu section 10).
- `/home/m-tristan/workspace/Starseed/docs/modules/site-aware.md` : guide developpeur (cf. contenu section 10).
### Tests
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Infrastructure/ApiPlatform/Extension/SiteScopedQueryExtensionTest.php` : tests d'integration (`KernelTestCase`) avec l'entite `FakeSiteAwareEntity` (declaree uniquement dans le dossier de tests). Verifie :
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Infrastructure/ApiPlatform/Extension/SiteScopedQueryExtensionTest.php` : tests d'integration (`KernelTestCase`) avec l'entite `FakeSiteAwareEntity` (declaree uniquement dans le dossier de tests). Verifie :
- Le filtre s'applique sur une resource `SiteAware` quand le provider retourne un site.
- Le filtre est no-op si `SiteAware` mais provider null.
- Le filtre est no-op si resource non `SiteAware`.
- Le filtre est no-op si user a `sites.bypass_scope`.
- `totalItems` Hydra reflete bien le filtrage.
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Infrastructure/ApiPlatform/State/Processor/SiteAwareInjectionProcessorTest.php` : tests unitaires (`TestCase` pur) avec mocks. Verifie :
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Infrastructure/ApiPlatform/State/Processor/SiteAwareInjectionProcessorTest.php` : tests unitaires (`TestCase` pur) avec mocks. Verifie :
- `$data` SiteAware sans site → injection du site courant.
- `$data` SiteAware avec site deja positionne → pas d'overwrite.
- `$data` non-SiteAware → delegation directe sans modification.
- Provider retourne null (module off ou user sans site) ET `$data` SiteAware sans site → BadRequestHttpException (400) "aucun site selectionne".
- `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Application/Service/CurrentSiteProviderTest.php` : tests unitaires `TestCase`. Couvre :
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Application/Service/CurrentSiteProviderTest.php` : tests unitaires `TestCase`. Couvre :
- User authentifie avec currentSite → retourne le Site.
- User authentifie sans currentSite → null.
- Pas d'user → null.
- Module desactive dans config/modules.php de test → null meme si user.currentSite existe.
- `/home/m-tristan/workspace/Coltura/tests/Fixtures/SiteAware/FakeSiteAwareEntity.php` : entite Doctrine minimale (`id`, `name`, `site`) utilisee **uniquement** en tests. Mapping Doctrine declare via un `#[ORM\Entity]` mais la table n'existe jamais en prod car la fixture n'est jamais chargee hors tests. **Alternative** : utiliser un schema DB dedie au dossier de tests, cree a la volee par un helper setUp. A trancher a l'implementation.
- `/home/m-tristan/workspace/Starseed/tests/Fixtures/SiteAware/FakeSiteAwareEntity.php` : entite Doctrine minimale (`id`, `name`, `site`) utilisee **uniquement** en tests. Mapping Doctrine declare via un `#[ORM\Entity]` mais la table n'existe jamais en prod car la fixture n'est jamais chargee hors tests. **Alternative** : utiliser un schema DB dedie au dossier de tests, cree a la volee par un helper setUp. A trancher a l'implementation.
## 4. Fichiers à modifier
- `/home/m-tristan/workspace/Coltura/src/Module/Sites/SitesModule.php` : ajouter la permission `sites.bypass_scope` dans `permissions()` :
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/SitesModule.php` : ajouter la permission `sites.bypass_scope` dans `permissions()` :
```php
['code' => 'sites.bypass_scope', 'label' => 'Voir les donnees site-scoped de tous les sites (bypass du filtrage)'],
```
**Note importante** : la methode `permissions()` signale l'existence de la permission mais c'est la commande `app:sync-permissions` (inchangee) qui la positionne en base.
- `/home/m-tristan/workspace/Coltura/config/services.yaml` : aucun changement requis. `SiteScopedQueryExtension`, `SiteAwareInjectionProcessor` et `CurrentSiteProvider` sont autoconfigures via les `_defaults` du module. Le decorator du persist processor est declare via `#[AsDecorator]` ou via tag (cf. section 8).
- `/home/m-tristan/workspace/Coltura/phpunit.dist.xml` : aucune modification requise si la config des fixtures de tests est autonome. Si `FakeSiteAwareEntity` necessite un mapping dedie, l'option la plus propre est un `doctrine.yaml.test` ajoute via `when@test`, sans polluer la config dev/prod (cf. Risque 3).
- `/home/m-tristan/workspace/Starseed/config/services.yaml` : aucun changement requis. `SiteScopedQueryExtension`, `SiteAwareInjectionProcessor` et `CurrentSiteProvider` sont autoconfigures via les `_defaults` du module. Le decorator du persist processor est declare via `#[AsDecorator]` ou via tag (cf. section 8).
- `/home/m-tristan/workspace/Starseed/phpunit.dist.xml` : aucune modification requise si la config des fixtures de tests est autonome. Si `FakeSiteAwareEntity` necessite un mapping dedie, l'option la plus propre est un `doctrine.yaml.test` ajoute via `when@test`, sans polluer la config dev/prod (cf. Risque 3).
## 5. Contrat `SiteAwareInterface`
@@ -459,7 +459,7 @@ A mitiger par un test qui genere une entite `FakeSiteAwareEntity` via un POST `a
### Risque 8 — Doc developpeur en francais vs anglais
Le fichier `docs/modules/site-aware.md` s'adresse aux developpeurs de Coltura. Il est redige en **francais**, aligne sur la convention projet (CLAUDE.md : "commentaires en francais, code en anglais"). Aucun extrait de code ne doit etre traduit, seules les explications.
Le fichier `docs/modules/site-aware.md` s'adresse aux developpeurs de Starseed. Il est redige en **francais**, aligne sur la convention projet (CLAUDE.md : "commentaires en francais, code en anglais"). Aucun extrait de code ne doit etre traduit, seules les explications.
## 12. Plan de tests