Compare commits

..

38 Commits

Author SHA1 Message Date
Matthieu
d64c74a120 Merge remote-tracking branch 'origin/develop' into feat/audit-log
# Conflicts:
#	config/version.yaml
2026-05-13 10:27:02 +02:00
7c6103e395 docs(audit-log) : ajoute findings review additionnels au backlog
Ajoute :
- C-5 : enumeration laterale via entity_type cross-permission
- I-20 : framework.trusted_proxies absent → ip_address inutilisable
- I-21 : test du contrat rollback metier manquant
- I-22 : filtres performed_at[after|before] timezone-naifs
- I-23 : auth.logout() ne reset pas le cache useAuditLog
- I-24 : pas de tests Vitest sur useAuditLog ni AuditTimeline
- I-25 : pas de rate limiter sur /api/audit-logs
- I-26 : suite PHPUnit non-deterministe (cross-class pollution)
- M-9 : logs Monolog audit_write_failures incluent changes complet
- M-10 : audit_log sans REVOKE UPDATE/DELETE PG (defense-in-depth)
- M-11 : entity_type non valide cote provider

Retire M-4 et M-7 (traites dans cette serie de commits).
Reorganise la priorite avec un Bloc 0 securite avant prod
et un Bloc 0bis DX bloquante.
2026-05-02 17:18:36 +02:00
b2f17b0522 test(audit-log) : ajoute 403 sur GET item et invariant page=0
- testItemEndpointWithoutPermissionGets403 : symetrique de
  testAuthenticatedUserWithoutPermissionGets403 sur /api/audit-logs/{id},
  prouve que la security expression `is_granted('core.audit_log.view')`
  est appliquee aussi sur l'operation Get item (couvre M-4 du backlog).
- testPageZeroDoesNotProduceServerError : verrouille l'invariant
  fonctionnel "?page=0 ne produit jamais 500 PG", quel que soit le
  mecanisme protecteur (clamp provider ou validation API Platform amont).
  Couvre M-7 du backlog.
2026-05-02 17:18:25 +02:00
c09501d321 fix(audit-log) : clamp page minimum a 1 dans AuditLogProvider
API Platform 4 valide deja `page >= 1` en amont (rejette en 400) avant que
le provider ne soit appele. Le clamp `max(1, $page)` reste en place comme
defense-in-depth si un futur upgrade ou une modification de configuration
leve cette validation : garantit qu'aucune `page=0` ne produira de
SQLSTATE[22023] OFFSET must not be negative.
2026-05-02 17:18:16 +02:00
2645a335c4 docs(audit-log) : corrige description ManyToMany dans la spec
AuditListener::captureCollectionChange utilise getScheduledCollectionUpdates
et getScheduledCollectionDeletions pour produire {fieldName: {added, removed}}.
La spec (lignes 197 et 418) annoncait l'inverse - alignement sur le code reel
et sur .claude/rules/backend.md.
2026-05-02 17:18:06 +02:00
fcc7a2e3d4 docs(audit-log) : ajoute backlog des findings review non-traites dans la PR
Resume structure des 9 Important + 8 Minor + C-4 parke + C-3 documente.

Chaque entree contient : severite, fichier:ligne, strategie recommandee,
effort estime, declencheur (quand / pourquoi faire). Trois blocs
priorises pour faciliter le picking en tickets (quick wins, mecaniques,
scaling produit).

Permet de ne pas perdre la vue d'ensemble du review et d'ouvrir les
tickets dedies avec contexte complet sans re-analyser le diff.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:21:49 +02:00
269c232bfc chore(test) : supprime env APP_ENV redondant dans phpunit.dist.xml
Le <server name="APP_ENV" value="test" force="true"> fait deja le job ;
la ligne <env name="APP_ENV" value="test"/> en double creait un piege a
regression : un dev qui mettrait "dev" en pensant que <server> gere
tout, puis supprimerait <server>, verrait <env> reprendre la main
silencieusement et reintroduirait le bug framework.test=false (cf.
commit 37eafd2 du fix initial).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:21:49 +02:00
fa8e46471d fix(frontend) : auto-reset useSidebar et useModules sur 401/logout
Les deux composables ont un state singleton au niveau module mais
n'etaient reinitialises que dans logout.vue — un 401 silencieux (JWT
expire) laissait la sidebar et la liste de modules actifs de l'ancien
user visible jusqu'a ce qu'un nouveau login complete `loadSidebar()`.

Aligne le pattern sur useAuditLog (deja conforme) : enregistrement
automatique sur `onAuthSessionCleared` au niveau module, via une
fonction `reset*State()` privee reutilisee par la methode publique
`reset*()` exposee dans le composable.

Respect de la regle CLAUDE.md : "composables avec state singleton
doivent etre reinitialises au logout".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:21:49 +02:00
277178cf19 fix(users) : guard anti-bypass sur sites null dans UserRbacProcessor
restoreAbsentCollections utilisait `array_key_exists('sites', $payload)`
qui retourne true pour une valeur null, sautant la restauration.

API Platform rejette deja `sites: null` au denormalize (400 type mismatch),
donc le bypass n'est pas reellement exploitable aujourd'hui via l'API HTTP.
Mais le test `&& is_array(...)` reste une defense-in-depth si la config
denormalizer change un jour, et rend l'intention explicite.

Test de regression : PATCH {sites: null} -> 400 + collection sites intacte
en DB (aucune trace de l'echec).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:21:49 +02:00
e0624eace0 fix(audit-log) : reset pendingLogs sur onFlush + valide filtres + documente contrat rollback
Trois corrections issues du code review multi-agent sur la PR audit-log :

- AuditListener : reset defensif de pendingLogs en debut de onFlush. Si
  un flush precedent a leve une exception avant postFlush (qui n'est
  jamais appele sur un flush rate), le state listener gardait des
  changements jamais committes, ecrits a tort par le prochain postFlush
  reussi — audit_log pouvait donc contenir des lignes decrivant des
  evenements qui n'ont pas eu lieu en DB. Test de regression via
  Reflection pour injecter un log orphelin et verifier qu'il n'arrive
  pas dans audit_log.

- AuditLogProvider : validation explicite des filtres performed_at[after]
  et performed_at[before] (strtotime) + whitelist stricte sur `action`
  (create|update|delete). Avant, un input malforme remontait jusqu'a
  Postgres et faisait un 500 (SQLSTATE[22007]). Desormais 400 explicite,
  pas de log pollue.

- doc/audit-log.md : ajoute une section "Contrat" qui explicite ce que
  audit_log garantit (journal des intentions appliquees par l'ORM) et ne
  garantit PAS (reflet exact du commit outermost — une ligne audit peut
  persister si une transaction outermost rollback apres un flush inner
  reussi, parce que l'audit ecrit sur une connexion DBAL dediee).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:21:49 +02:00
d3e30e55b2 fix(security) : empeche SeedE2ECommand de tourner hors dev/test + exclusion Docker
Cette commande cree un compte admin (e2e.super-admin, isAdmin=true) avec
un mot de passe hardcode. Le Dockerfile prod copie src/ verbatim, donc le
fichier embarquait un backdoor admin potentiel dans l'image de production.

Defense en profondeur sur deux couches independantes :

- Garde runtime : execute() refuse tout APP_ENV autre que dev|test et
  retourne FAILURE avec message explicite.
- Filet de build : .dockerignore a la racine exclut le fichier du contexte
  de build, donc meme si la garde runtime sautait le fichier ne serait pas
  dans l'image prod.

Injecte egalement SiteProviderInterface (Shared) au lieu de
SiteRepositoryInterface (Module/Sites) en coherence avec le refactor
d'isolation Core/Sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:21:49 +02:00
18e79a643b refactor(core) : isole Core du module Sites via SiteProviderInterface + resolve_target_entities
Supprime les imports directs de App\Module\Sites\* depuis le module Core :

- SiteProviderInterface (Shared/Contract) : contrat minimal findByName(),
  etendu par SiteRepositoryInterface pour reutilisation DI.
- AppFixtures : injecte SiteProviderInterface au lieu de SiteRepositoryInterface.
- User.php : targetEntity des mappings ORM pointe desormais sur SiteInterface,
  resolu a la classe concrete via doctrine.orm.resolve_target_entities
  (pattern officiel Doctrine pour les bounded contexts DDD).
- JoinColumn/InverseJoinColumn explicites sur la ManyToMany user_site pour
  forcer les noms de colonnes (sinon Doctrine derive site_interface_id).

Respecte la regle CLAUDE.md "jamais d'import direct entre modules" — il
reste l'exception SitesFixtures::class dans getDependencies() (contrainte
d'API DependentFixtureInterface, meme registre que resolve_target_entities).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:21:49 +02:00
Matthieu
48f314f09e docs(make) : ajoute une cible help listant les commandes par categorie 2026-04-23 11:49:30 +02:00
Matthieu
e933c31e0f refactor(i18n) : sidebar.sites.admin + cles audit.entity.*
T-011 — deplace la cle sidebar.core.sites sous son module owner
(sidebar.sites.admin). Aligne sur la convention naming.md : les cles
sidebar doivent vivre sous le namespace du module qui expose l'item.

T-015 — traduit entityType dans la page d'audit via des cles i18n
audit.entity.core_user / core_role / core_permission / sites_site.
Helper formatEntityType avec fallback sur l'identifiant brut pour
rester debug-friendly si une traduction manque. Applique sur :
- la cellule du tableau (tooltip garde l'identifiant technique)
- les options du filtre multi-select MalioSelectCheckbox
- le titre du drawer de detail + h3 interne
2026-04-23 11:47:16 +02:00
Matthieu
4c142aecbb refactor(front) : extrait debounce dans shared/utils + tests Vitest
T-013 — sort la fonction debounce inline de audit-log.vue vers
frontend/shared/utils/debounce.ts (auto-importe par Nuxt) et ajoute
3 tests Vitest (delay coalesce, derniere invocation gagne, plusieurs
executions espacees). Pret pour reutilisation sur les prochaines
pages avec recherche/filtres.
2026-04-23 11:46:13 +02:00
Matthieu
da35f29960 fix(audit) : MalioButton + paliers month/year dans AuditTimeline
T-010 — remplace le <button> HTML brut par MalioButton variant=tertiary,
conformement a la regle projet (tous les boutons passent par Malio*).

T-014 — etend relativeDate avec les paliers 'month' (~30.44j) et 'year'
(~365.25j). Avant, une entree vieille d'un an affichait "il y a 52
semaines" au lieu de "l'an dernier" (RelativeTimeFormat FR).
2026-04-23 11:44:55 +02:00
Matthieu
2a835855b9 refactor(api) : passe UserPasswordHasherProcessor et MeProvider en final
T-012 — aligne ces deux classes sur la convention de la PR (tous les
autres providers/processors sont final). Empeche une sous-classe de
contourner la logique de hachage ou l'auth Me par heritage accidentel.
2026-04-23 11:44:02 +02:00
Matthieu
585f3c5b79 fix(robots) : bloque l'indexation du CRM (Disallow /)
T-003 — Ticket review 4e passe.
2026-04-23 11:43:12 +02:00
Matthieu
4a8326a6b5 docs(review) : ajoute REVIEW.md 4e passe et TICKETS.md correctifs 2026-04-23 11:41:19 +02:00
Matthieu
711774425b docs(claude) : refactorise CLAUDE.md en index + extrait les regles dans .claude/rules/
- CLAUDE.md devient un index concis : contexte, stack, regles absolues
  numerotees, pointeurs vers les fichiers de regles detaillees via
  references @.claude/rules/*.md
- Les conventions detaillees (architecture, backend, frontend, testing,
  naming, git, workflow) sont extraites dans .claude/rules/ pour rester
  chargees a la demande sans gonfler le context du CLAUDE.md principal
- Ajoute la regle absolue "Ne jamais mentionner Claude/IA dans commits
  ou PR" (point 10) pour garder l'historique git signe par l'utilisateur
2026-04-23 11:02:04 +02:00
Matthieu
b1255bb57a fix(review) : resout findings 3e passe review (HIGH frontend + MEDIUMs backend/frontend/E2E)
Backend :
- AuditLogWriter::stripSensitive rendu reellement recursif (matche doc).
- Tests GET /api/permissions/{id} non-admin pour chaque branche OR (gap Codex).
- Gardes non-regression UserRbacProcessor : PATCH /rbac sans clef sites ne
  doit ni auto-selectionner currentSite ni exiger sites.manage.

Frontend :
- useAuditLog : renomme export trompeur fetchLogs -> fetchLogsCached, le
  nom reflete desormais le comportement (cache pollue sinon).
- RoleDrawer / UserRbacDrawer : catch explicite + message d'erreur +
  bouton save disabled si le chargement des referentiels a echoue (evite
  un ecrasement silencieux des droits).
- AuditTimeline / AuditLogDetail : `oui`/`non` passent par common.yes/no.
- AuditTimeline : Intl.RelativeTimeFormat et toLocaleString suivent la
  locale i18n courante (plus de hardcode 'fr').

E2E :
- sidebar-visibility.spec : remplace waitForLoadState('networkidle')
  fragile par attente semantique sur accountDashboardLink (stable en CI).

Tests : 237/237 green, eslint clean, php-cs-fixer clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:31:03 +02:00
Matthieu
25cd6a1ecc fix(review) : resout la regression drawers RBAC + race snapshot + stale-data admin
Issues remontees par la seconde passe de review de la PR #9 :

- Regression `GET /api/permissions` 403 silencieux sur les drawers RBAC
  (UserRbacDrawer, RoleDrawer) apres le fix precedent qui imposait
  `core.permissions.view`. Les users porteurs de `core.users.manage` /
  `core.roles.manage` ne voyaient plus le catalogue pour hydrater leurs
  checkboxes. Elargit la security expression sur Permission en OR avec
  ces deux codes : les gestionnaires ont par nature besoin du catalogue
  (codes/libelles seuls, pas de secret expose).

- Race condition dans UserRbacProcessor : `restoreAbsentCollections()`
  lisait le snapshot Doctrine hors transaction, puis `wrapInTransaction()`
  flushait plus tard. Fenetre courte mais reelle ou une modification
  concurrente aurait pu etre annulee par une restauration depuis un
  snapshot stale. Deplace l'appel a l'interieur de la transaction.

- Stale-data sur les pages admin users / roles / sites : meme pattern
  try/finally sans catch que sur audit-log (deja corrige). Aligne les
  trois pages avec un catch qui reset la liste locale.

- Tests manquants : garde de non-regression sur PATCH /rbac sans `sites`
  (assure que la collection elle-meme est preservee, pas seulement le
  currentSite). Couverture positive sur GET /api/permissions pour les
  trois branches OR de la security expression (permissions.view,
  users.manage, roles.manage) via des users non-admin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:10:30 +02:00
Matthieu
bb6a4c387b fix(review) : applique fixes blockers review PR #9 (permission guard, sites LAZY, audit UI stale)
- Permission entity : remplace le guard `ROLE_USER` par `core.permissions.view`
  sur GetCollection/Get. Le catalogue complet des permissions RBAC etait
  accessible a tout utilisateur authentifie. Ajoute la permission manquante
  dans CoreModule::permissions() et inverse les tests standardUser*
  (attendent maintenant un 403 pour un user sans la permission).

- UserRbacProcessor::restoreAbsentCollections() : force
  PersistentCollection::initialize() avant de lire le snapshot. Pour une
  association fetch=LAZY (ex: User::$sites), le snapshot est vide tant que
  la collection n'est pas materialisee, ce qui faisait vider silencieusement
  tous les sites d'un user sur un PATCH ne contenant pas la cle `sites`.

- admin/audit-log.vue : ajoute un catch sur loadEntries() qui reset
  entries/totalItems pour ne pas afficher de donnees stale si le fetch echoue
  (reseau coupe, 403 inopinee...). Le toast d'erreur reste gere par useApi.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:29:34 +02:00
Matthieu
793e816f3e chore : untrack auto-generated config/reference.php
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:55:05 +02:00
Matthieu
4d2b5ad62f chore : regenerate config/reference.php after twig-bundle install
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:47:19 +02:00
Matthieu
a3f2671eec chore : ignore php-cs-fixer local files
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:35:58 +02:00
4603ab2832 test(e2e) : initialise la suite Playwright (login + sidebar RBAC)
- 11 tests couvrant le login (3) et la visibilite sidebar par RBAC (8)
- 6 personas seedes via la commande app:seed-e2e, miroir cote front
  dans frontend/tests/e2e/_fixtures/personas.ts
- Page Objects (LoginPage, SidebarComponent) avec selecteurs stables
  par href + loginAs programmatique via cookie BEARER
- Targets Makefile : seed-e2e, test-e2e, test-e2e-ui, install-e2e-deps
- CLAUDE.md + README.md : workflow E2E + regle d'or "un E2E par bug
  prod uniquement" pour garder la suite maintenable dans la duree

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:27:05 +02:00
99c77eb7b6 fix(audit-log) : applique fixes code review (precision timestamp, ESCAPE LIKE, pagination max)
- TIMESTAMP(6) WITH TIME ZONE + tie-breaker id DESC sur l'ORDER BY pour
  garantir un tri deterministe quand plusieurs lignes partagent la meme
  timestamp (batch fixture, bulk flush < 1µs).
- Suppression de la clause ESCAPE '\\' redondante (`\` est deja
  l'echappement LIKE par defaut en PostgreSQL) et fragile sur
  standard_conforming_strings. Le str_replace des wildcards reste.
- paginationMaximumItemsPerPage : 100 -> 50. Reduit le pire cas de
  reponse lourde sur un endpoint admin (changes JSONB volumineux).
2026-04-22 16:10:03 +02:00
68f072ef46 fix(audit-log) : exclut audit_log du schema_filter Doctrine
La table audit_log n'a pas d'entite ORM (ecriture DBAL brut via
AuditLogWriter pour eviter la recursion du listener). doctrine:schema:update
la considerait donc comme orpheline et la droppait systematiquement, ce qui
cassait la base de test apres chaque make test-db-setup (DROP TABLE audit_log
genere par schema:update --force, apres les migrations qui l'avaient creee).

Un schema_filter en negative lookahead sur la connexion default exclut la
table de toute comparaison de schema (schema:update, schema:validate, diff
de migrations). La creation / suppression reste pilotee exclusivement par
la migration Version20260420202749.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:51:32 +02:00
e2fbf51e19 refactor(sidebar) : deplace Tableau de bord dans Mon compte + documente la convention admin
- Dashboard ("/") sort de la section Administration et rejoint "Mon compte" (accessible a tout user authentifie, comme Deconnexion).
- Cle i18n migree : sidebar.general.dashboard -> sidebar.account.dashboard. Le namespace "general" devenu vide est supprime.
- Documentation inline dans config/sidebar.php : formalise la convention "etre admin = detenir au moins une permission admin-scoped (core.* ou equivalent sites.view)". Le gate implicite via filtrage d'items rend inutile un gate section-level tant que chaque item porte sa permission.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:40:53 +02:00
Matthieu
701a480442 feat(sidebar) : section Administration + groupe Mon compte + gate de section
- Section "Général" renommée en "Administration" (label i18n sidebar.administration.section).
- Item "Administration" (/admin) retiré : la route n'existait pas cote front, generait un 404 Nuxt silencieux a chaque clic.
- "Deconnexion" sortie de la section admin, deplacee dans une nouvelle section "Mon compte" (sidebar.account.section) sans permission RBAC — accessible a tout user authentifie.
- SidebarProvider supporte desormais un champ `permission` au niveau section : umbrella gate qui masque toute la section et bascule toutes ses routes dans disabledRoutes. Voir doc inline dans config/sidebar.php pour le pattern d'usage.

Avantage : pour gater toute l'administration derriere une permission coarse (ex: 'core.admin.access' future), ajouter 'permission' => 'core.admin.access' sur la section suffit — pas besoin de dupliquer la permission sur chaque item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 11:28:44 +02:00
Matthieu
5f5afccac0 docs(specs) : documente GET /users/{id}/rbac et garde anti-ecrasement merge-patch
Ajoute les sections "Evolutions post-livraison" aux specs Sites #02 et RBAC #345
pour refleter les modifs apportees apres la livraison initiale :

- GET /users/{id}/rbac symetrique au PATCH, pour charger le detail d'edition
  sans elargir le groupe user:list (le payload de liste reste leger, la
  dependance Core → Sites reste scopee a cet endpoint et a /api/me).
- Garde restoreAbsentCollections() dans UserRbacProcessor qui respecte la
  semantique merge-patch+json : cle absente = preservee, cle = [] = videe,
  cle = [...] = remplacee. Restauration a partir du snapshot Doctrine des
  PersistentCollection pour roles / directPermissions / sites.
- Nouveaux criteres de validation + matrice de semantique.

Verification archi modular monolith : Commercial et Sites peuvent etre
desactives dans config/modules.php sans casser l'app (sidebar filtree,
switcher masque, endpoints admin rediriges via disabledRoutes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 11:21:49 +02:00
Matthieu
617ee314b3 fix(users) : corrige l'affichage et l'ecrasement des sites sur le drawer RBAC
Le drawer RBAC de /admin/users initialisait l'etat des sites a partir du payload
/api/users (groupe user:list) qui n'expose pas la collection sites. Consequence :
la section "Sites autorises" affichait toujours 0 case cochee, et la sauvegarde
ecrasait silencieusement les sites existants en BDD.

- Ajout d'une operation GET /users/{id}/rbac (groupe user:rbac:read) dediee au
  chargement du detail pour l'edition : payload list reste leger, detail riche
  sur une URI symetrique au PATCH existant.
- Drawer charge desormais GET /users/{id}/rbac pour initialiser sites, roles
  et directPermissions ; UserListItem ne contient plus sites (inutilise).
- Colonne "Sites" retiree de la table /admin/users : l'info est consultee via
  le drawer, pas la liste (evite aussi la fuite cross-site pour les users avec
  core.users.view mais sans sites.bypass_scope).
- Garde anti-ecrasement dans UserRbacProcessor : respect de la semantique
  merge-patch+json (cle absente = preservee, cle = [] = vidage explicite).
  Restaure les collections ManyToMany absentes du payload a partir du snapshot
  Doctrine. Couvre roles, directPermissions et sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 11:17:40 +02:00
Matthieu
6db955f65c fix(api-docs) : reactive swagger ui en ajoutant symfony/twig-bundle
API Platform 4 active swagger_ui/re_doc/scalar uniquement si TwigBundle
est present (les UI de docs sont rendues via Twig). Sans lui les flags
tombaient a false et /api/docs renvoyait 404 "Swagger UI, ReDoc and
Scalar are disabled." sur Accept: text/html.
2026-04-22 11:09:37 +02:00
Matthieu
1505e84926 fix(audit-log) : applique fixes code review PR #9
Resout les 5 findings de la review automatique + couverture ManyToMany
annoncee dans CLAUDE.md :

- AuditListener : resolution de la classe via ClassMetadata plutot que
  `$entity::class` direct (defense proxy Doctrine : sous ORM 2 les lazies
  sont des `Proxies\__CG__\...`). Test de regression via getReference().
- AuditListener : capture des modifications de collections to-many
  (OneToMany / ManyToMany) via getScheduledCollectionUpdates /
  getScheduledCollectionDeletions. Les diffs sont mergees dans le
  changeset existant ou creent une entree "update" dediee.
- AuditLogResource + Provider : filtre multi-valeurs
  `entity_type[]=X&entity_type[]=Y` (IN clause DBAL via
  ArrayParameterType::STRING), endpoint `/audit-log-entity-types` pour
  alimenter le MalioSelectCheckbox cote front.
- audit-log.vue : refonte complete. Passage a `MalioDataTable`,
  composants `Malio*` (MalioInputText, MalioSelectCheckbox, MalioButton),
  suppression complete de la persistance URL (`readQuery` / `syncQuery`
  / `route.query`). `datetime-local` conserve avec TODO pointant
  l'exception CLAUDE.md.
- AuditTimeline : fix du saut d'items 11-30. `PAGE_SIZE = 10` aligne
  avec un `itemsPerPage=10` passe au backend. Token anti-race pour
  ignorer les reponses tardives quand l'entite affichee change.
- AuditLogDetail : affichage des diffs de collections to-many (+ / -)
  dans le tableau field/old/new existant.
- logout.vue : ajout du `resetAuditLog()` au logout pour eviter qu'un
  user suivant (meme onglet) voie l'etat audit de l'ancien.
- Permission / Role / Site : marquage `#[Auditable]`.
- Version bump 0.1.32 → 0.1.34.

Tests : 228 / 228 (221 assertions → 851, dont regressions proxy + M2M).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:28:44 +02:00
Matthieu
a95bb6c629 docs(claude) : ajoute conventions MalioDataTable, Malio* components, pas de persistance URL, audit ManyToMany 2026-04-21 16:03:26 +02:00
37eafd276c fix(audit-log) : address code review findings
Blocker
- Frontend attendait `hydra:member` / `hydra:totalItems` / `hydra:view` mais
  API Platform 4 sert `member` / `totalItems` / `view` (sans prefixe) sous
  ld+json, et un tableau plat sous json. Consequence : tableau admin et
  timeline silencieusement vides.
  Fix : `useAuditLog` force `Accept: application/ld+json` (necessaire pour
  obtenir l'objet Hydra avec pagination), types `HydraCollection`/`HydraView`
  renommes, composants accedent aux proprietes sans prefixe. Nouveau test
  fonctionnel verrouille le format.

Should-fix
- `AuditLogWriter` : ajout de `'id' => Types::GUID` pour expliciter le type
  natif PG `uuid` (fonctionnait par cast implicite mais l'intention etait
  floue).
- `AuditListener` docblock : documente que le DQL bulk DELETE/UPDATE et
  `Connection::executeStatement()` bypassent le listener (onFlush non
  appele). Piege pour les futures commandes de purge.
- `AuditLogResource` : ajout d'une regex UUID dans `requirements` de
  l'operation Get — un `GET /api/audit-logs/not-a-uuid` produisait un 500
  (cast PG rejete) au lieu d'un 404.
- `audit-log.vue` : le watcher des filtres faisait `filters.page = 1` ce
  qui declenchait le watcher de `page`, causant deux `loadEntries()` en
  parallele. Fusionne : la navigation page appelle `loadEntries()`
  directement depuis `goPrevious`/`goNext`, plus de watcher dedie.
- `useAuditLog.fetchEntityLogs` : bypass du cache `lastCollection` pour ne
  pas polluer la reference page-level quand la timeline est ouverte.
- `AuditTimeline.vue` : remplacement du `<div v-if="!canView"/>` vide par
  un `v-if` sur le wrapper — aucun DOM quand l'utilisateur n'a pas le droit.
- `AuditListenerTest` tag : retire le `_` (wildcard LIKE SQL) du prefix
  pour eviter un faux negatif de match cross-test.
- `AuditLogApiTest` : proprietes `auditConnection` / `runTag` nullable et
  tearDown guarde, sinon un echec setUp provoquait un fatal typed-property
  au lieu de propager l'exception d'origine.

Stabilite suite de tests
- `doctrine.yaml when@test` : `idle_connection_ttl: 1` sur les deux
  connexions pour eviter l'accumulation de connexions orphelines.
- tearDown des tests audit : `close()` explicite sur la connexion audit
  apres chaque test.
- `docker-compose.yml` : `max_connections=300` sur la DB dev (defaut PG=100
  insuffisant pour 220+ tests * 2 connexions/test).
2026-04-20 21:10:46 +02:00
de39fe6a3e feat : add audit log (table, writer, listener, API, admin UI, timeline)
Implemente le journal d'audit append-only sur toutes les mutations Doctrine
des entites portant #[Auditable]. Couvre les 5 tickets de doc/audit-log.md :

1. Table PG audit_log (uuid PK, jsonb changes, index entity/time/performer)
   + AuditLogWriter (DBAL connexion dediee audit, blacklist defense-in-depth
   sur password/plainPassword/token/secret) + RequestIdProvider (UUID v4 par
   requete HTTP principale).
2. Attributs Auditable / AuditIgnore dans Shared/Domain/Attribute/
   + AuditListener (onFlush capture + postFlush ecriture hors transaction ORM,
   pattern swap-and-clear, erreurs loguees jamais propagees). User annote.
3. API Platform read-only /api/audit-logs (permission core.audit_log.view)
   avec filtres entity_type / entity_id / action / performed_by / plage
   performed_at + DbalPaginator implementant PaginatorInterface (hydra:view
   genere automatiquement).
4. Page admin /admin/audit-log : tableau pagine, filtres persistes en query
   params, row expandable (diff + timeline de l'entite), entree sidebar avec
   permission. Composable useAuditLog avec resetAuditLog() auto-enregistre
   sur onAuthSessionCleared.
5. Composant AuditTimeline reutilisable : garde permission, lazy loading,
   dates relatives FR, skeleton loader.

Fix connexe : phpunit.dist.xml forcait APP_ENV=dev via <env> ce qui cablait
framework.test=false et rendait test.service_container indisponible ; le
JWT_PASSPHRASE ne matchait pas non plus les cles dev. Corrige en meme temps
pour debloquer la suite de tests.
2026-04-20 20:51:10 +02:00
46 changed files with 292 additions and 1321 deletions

View File

@@ -41,8 +41,8 @@ Si une verification echoue ou ne peut pas etre lancee (ex : container pas demarr
## Time tracking Lesstime ## Time tracking Lesstime
Au demarrage de toute tache de dev sur Starseed, creer une time entry via l'API Lesstime (cf. `~/.claude/CLAUDE.md` pour la procedure complete). Au demarrage de toute tache de dev sur Coltura, creer une time entry via l'API Lesstime (cf. `~/.claude/CLAUDE.md` pour la procedure complete).
- Projet : `/api/projects/6` (STARSEED) - Projet : `/api/projects/6` (COLTURA)
- Tags : choisir selon le type (Backend `3`, Frontend `2`, Infra `5`, UI/UX `4`, Maintenance `6`, Gestion projet `9`, etc.) - Tags : choisir selon le type (Backend `3`, Frontend `2`, Infra `5`, UI/UX `4`, Maintenance `6`, Gestion projet `9`, etc.)
## Fix `make cache-clear` (permissions `var/`) ## Fix `make cache-clear` (permissions `var/`)
@@ -50,17 +50,17 @@ Au demarrage de toute tache de dev sur Starseed, creer une time entry via l'API
Si `make cache-clear` echoue sur les permissions de `var/` : Si `make cache-clear` echoue sur les permissions de `var/` :
```bash ```bash
docker exec -t -u root php-starseed-fpm chown -R www-data:www-data /var/www/html/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-starseed-fpm php bin/console cache:clear docker exec -t -u www-data php-coltura-fpm php bin/console cache:clear
``` ```
A terme : integrer ce fix dans le `makefile` lui-meme. A terme : integrer ce fix dans le `makefile` lui-meme.
## Docker — references utiles ## Docker — references utiles
- Container PHP : `php-starseed-fpm` - Container PHP : `php-coltura-fpm`
- Container Nginx : `nginx-starseed` (port 8083) - Container Nginx : `nginx-coltura` (port 8083)
- Container DB : PostgreSQL port **5437** (interne et externe) - Container DB : PostgreSQL port **5437** (interne et externe)
- Config dev : `infra/dev/.env.docker` (override local : `infra/dev/.env.docker.local`) - Config dev : `infra/dev/.env.docker` (override local : `infra/dev/.env.docker.local`)
- Config prod : `infra/prod/` (Dockerfile multi-stage, `docker-compose.prod.yml`) - Config prod : `infra/prod/` (Dockerfile multi-stage, `docker-compose.prod.yml`)
- Apres modif nginx : `docker restart nginx-starseed` - Apres modif nginx : `docker restart nginx-coltura`

View File

@@ -1,11 +1,11 @@
--- ---
name: create-module name: create-module
description: Scaffold a new Starseed module (backend + frontend) and optionally wire its entries into the sidebar config. Use when the user asks to create, add, scaffold, or generate a new module — e.g., "crée un module Paie", "add a Pointage module", "ajoute un module RH". The backend is the source of truth for activation and sidebar layout; the frontend scans modules automatically. description: Scaffold a new Coltura module (backend + frontend) and optionally wire its entries into the sidebar config. Use when the user asks to create, add, scaffold, or generate a new module — e.g., "crée un module Paie", "add a Pointage module", "ajoute un module RH". The backend is the source of truth for activation and sidebar layout; the frontend scans modules automatically.
--- ---
# Create a new Starseed module # Create a new Coltura module
Scaffolds a new module across backend and frontend following Starseed's modular monolith DDD architecture. Scaffolds a new module across backend and frontend following Coltura's modular monolith DDD architecture.
## Architecture reminder — read before acting ## Architecture reminder — read before acting
@@ -178,8 +178,8 @@ Execute in this exact order:
6. **Backend: sidebar** — if the user wants sidebar entries, edit `config/sidebar.php`. 6. **Backend: sidebar** — if the user wants sidebar entries, edit `config/sidebar.php`.
7. **Frontend: translations** — edit `frontend/i18n/locales/fr.json`. 7. **Frontend: translations** — edit `frontend/i18n/locales/fr.json`.
8. **Verify** — run: 8. **Verify** — run:
- `docker exec -t -u root php-starseed-fpm chown -R www-data:www-data /var/www/html/var` (avoid permission issues) - `docker exec -t -u root php-coltura-fpm chown -R www-data:www-data /var/www/html/var` (avoid permission issues)
- `docker exec -t -u www-data php-starseed-fpm php bin/console cache:clear` (validates backend) - `docker exec -t -u www-data php-coltura-fpm php bin/console cache:clear` (validates backend)
- `cd frontend && npx nuxi prepare` (validates Nuxt auto-detection of the new layer) - `cd frontend && npx nuxi prepare` (validates Nuxt auto-detection of the new layer)
9. **Report** — list files created, the route(s) to test, and the sidebar items added. 9. **Report** — list files created, the route(s) to test, and the sidebar items added.

View File

@@ -20,11 +20,11 @@ jobs:
run: | run: |
docker build \ docker build \
-f infra/prod/Dockerfile \ -f infra/prod/Dockerfile \
-t gitea.malio.fr/malio-dev/starseed:${{ gitea.ref_name }} \ -t gitea.malio.fr/malio-dev/coltura:${{ gitea.ref_name }} \
-t gitea.malio.fr/malio-dev/starseed:latest \ -t gitea.malio.fr/malio-dev/coltura:latest \
. .
- name: Push Docker image - name: Push Docker image
run: | run: |
docker push gitea.malio.fr/malio-dev/starseed:${{ gitea.ref_name }} docker push gitea.malio.fr/malio-dev/coltura:${{ gitea.ref_name }}
docker push gitea.malio.fr/malio-dev/starseed:latest docker push gitea.malio.fr/malio-dev/coltura:latest

1
.gitignore vendored
View File

@@ -3,7 +3,6 @@
/.env.local.php /.env.local.php
/.env.*.local /.env.*.local
/config/secrets/dev/dev.decrypt.private.php /config/secrets/dev/dev.decrypt.private.php
/config/reference.php
/public/bundles/ /public/bundles/
/var/ /var/
/vendor/ /vendor/

View File

@@ -1,6 +1,6 @@
# Changelog # Changelog
Liste des évolutions du projet Starseed Liste des évolutions du projet Coltura
## [0.0.0] ## [0.0.0]

View File

@@ -1,4 +1,4 @@
# Starseed # Coltura
## Contexte ## Contexte
CRM/ERP en architecture **modular monolith DDD**. Le backend est la source de verite unique (modules actifs, sidebar). Le frontend scanne `frontend/modules/*/` comme layers Nuxt et consomme l'API pour la navigation. Multi-tenant : chaque module est activable/desactivable. CRM/ERP en architecture **modular monolith DDD**. Le backend est la source de verite unique (modules actifs, sidebar). Le frontend scanne `frontend/modules/*/` comme layers Nuxt et consomme l'API pour la navigation. Multi-tenant : chaque module est activable/desactivable.
@@ -9,7 +9,7 @@ Doc humaine : @README.md — Spec audit : @doc/audit-log.md
- Backend : PHP 8.4, Symfony 8, API Platform 4, Doctrine ORM, PostgreSQL 16 (port 5437) - Backend : PHP 8.4, Symfony 8, API Platform 4, Doctrine ORM, PostgreSQL 16 (port 5437)
- Frontend : Nuxt 4 (SPA), Vue 3, Pinia, Tailwind, @malio/layer-ui, @nuxtjs/i18n - Frontend : Nuxt 4 (SPA), Vue 3, Pinia, Tailwind, @malio/layer-ui, @nuxtjs/i18n
- Auth : JWT HTTP-only cookie (Lexik), login a `/login_check` - Auth : JWT HTTP-only cookie (Lexik), login a `/login_check`
- Containers : `php-starseed-fpm`, `nginx-starseed` (port 8083), dev Nuxt port **3004** - Containers : `php-coltura-fpm`, `nginx-coltura` (port 8083), dev Nuxt port **3004**
## Regles ABSOLUES ## Regles ABSOLUES

View File

@@ -1,4 +1,4 @@
# Starseed # Coltura
CRM/ERP — Symfony 8 (API Platform 4) + Nuxt 4 CRM/ERP — Symfony 8 (API Platform 4) + Nuxt 4

View File

@@ -39,7 +39,7 @@ La branche est globalement solide : les trois miroirs RBAC sont synchronises, le
- { path: ^/api/docs, roles: PUBLIC_ACCESS } - { path: ^/api/docs, roles: PUBLIC_ACCESS }
``` ```
La documentation Swagger/OpenAPI d'API Platform est accessible sans authentification, quel que soit l'environnement — y compris en production sur `starseed.malio-dev.fr`. Elle expose : La documentation Swagger/OpenAPI d'API Platform est accessible sans authentification, quel que soit l'environnement — y compris en production sur `coltura.malio-dev.fr`. Elle expose :
- la liste complete des endpoints (`/api/audit-logs`, `/api/users/{id}/rbac`, `/api/sites`, etc.) - la liste complete des endpoints (`/api/audit-logs`, `/api/users/{id}/rbac`, `/api/sites`, etc.)
- les schemas de securite (`is_granted('core.audit_log.view')`) - les schemas de securite (`is_granted('core.audit_log.view')`)
@@ -94,7 +94,7 @@ Le reverse proxy ecoute uniquement sur le port 80 (HTTP), sans redirection 301 v
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
server_name starseed.malio-dev.fr; server_name coltura.malio-dev.fr;
# Redirection HTTPS obligatoire (ajouter un server block HTTPS par ailleurs). # Redirection HTTPS obligatoire (ajouter un server block HTTPS par ailleurs).
# Tant que le TLS n'est pas en place, au minimum poser les en-tetes suivants. # Tant que le TLS n'est pas en place, au minimum poser les en-tetes suivants.
@@ -123,7 +123,7 @@ User-Agent: *
Disallow: Disallow:
``` ```
La valeur `Disallow:` (vide) signifie "rien n'est interdit" — tous les crawlers peuvent indexer la totalite du site. Pour un outil CRM interne accessible sur un DNS public (`starseed.malio-dev.fr`), c'est un leak inutile : la page de login, les URLs `/admin/*`, les URLs des fiches clients peuvent remonter dans Google. La valeur `Disallow:` (vide) signifie "rien n'est interdit" — tous les crawlers peuvent indexer la totalite du site. Pour un outil CRM interne accessible sur un DNS public (`coltura.malio-dev.fr`), c'est un leak inutile : la page de login, les URLs `/admin/*`, les URLs des fiches clients peuvent remonter dans Google.
**Correction** : **Correction** :
@@ -581,7 +581,7 @@ Et ajouter les cles manquantes dans `fr.json` :
await loadSidebar() // apres chaque switch await loadSidebar() // apres chaque switch
``` ```
Commentaire : *"les filtres de modules peuvent dependre du site courant"*. En pratique, dans `config/sidebar.php` de Starseed aucun item ne depend du site. C'est un aller-retour reseau inutile a chaque switch, et la sidebar peut "flicker" pour l'utilisateur. Commentaire : *"les filtres de modules peuvent dependre du site courant"*. En pratique, dans `config/sidebar.php` de Coltura aucun item ne depend du site. C'est un aller-retour reseau inutile a chaque switch, et la sidebar peut "flicker" pour l'utilisateur.
**Correction** : rendre le rechargement opt-in ou documenter la raison actuelle (prevoir le futur). **Correction** : rendre le rechargement opt-in ou documenter la raison actuelle (prevoir le futur).

View File

@@ -39,7 +39,7 @@ access_control:
Comme `/api/docs` tombe desormais dans le dernier pattern (`^/api`), il faudra etre authentifie pour le voir. Les devs continueront de l'utiliser apres login — les attaquants non. Comme `/api/docs` tombe desormais dans le dernier pattern (`^/api`), il faudra etre authentifie pour le voir. Les devs continueront de l'utiliser apres login — les attaquants non.
3. Recharger : `make cache-clear` puis `make restart`. 3. Recharger : `make cache-clear` puis `make restart`.
4. Tester : `curl -i https://starseed.malio-dev.fr/api/docs` doit retourner `401 Unauthorized` (avant : `200`). 4. Tester : `curl -i https://coltura.malio-dev.fr/api/docs` doit retourner `401 Unauthorized` (avant : `200`).
**Fichiers :** `config/packages/security.yaml` **Fichiers :** `config/packages/security.yaml`
@@ -47,12 +47,12 @@ Comme `/api/docs` tombe desormais dans le dernier pattern (`^/api`), il faudra e
### T-002 — Ajouter les en-tetes de securite HTTP de base en prod ### T-002 — Ajouter les en-tetes de securite HTTP de base en prod
**Pourquoi :** sans `X-Frame-Options`, quelqu'un peut integrer Starseed dans une iframe sur un site tiers et faire du clickjacking (faire croire a l'utilisateur qu'il clique sur un bouton anodin alors qu'il valide une action dans Starseed). Sans `X-Content-Type-Options: nosniff`, un navigateur peut deviner le type MIME et executer un fichier qui n'aurait pas du l'etre. Ce sont 3 lignes de config Nginx pour proteger l'application. **Pourquoi :** sans `X-Frame-Options`, quelqu'un peut integrer Coltura dans une iframe sur un site tiers et faire du clickjacking (faire croire a l'utilisateur qu'il clique sur un bouton anodin alors qu'il valide une action dans Coltura). Sans `X-Content-Type-Options: nosniff`, un navigateur peut deviner le type MIME et executer un fichier qui n'aurait pas du l'etre. Ce sont 3 lignes de config Nginx pour proteger l'application.
**A faire :** **A faire :**
1. Ouvrir `infra/prod/nginx-proxy.conf` (c'est le proxy expose au public). 1. Ouvrir `infra/prod/nginx-proxy.conf` (c'est le proxy expose au public).
2. Ajouter juste apres `server_name starseed.malio-dev.fr;` : 2. Ajouter juste apres `server_name coltura.malio-dev.fr;` :
```nginx ```nginx
# En-tetes de securite applicables a toutes les reponses # En-tetes de securite applicables a toutes les reponses
@@ -62,13 +62,13 @@ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
``` ```
Explication : Explication :
- `X-Frame-Options: DENY` : personne ne peut mettre Starseed dans une iframe. - `X-Frame-Options: DENY` : personne ne peut mettre Coltura dans une iframe.
- `X-Content-Type-Options: nosniff` : le navigateur ne devine pas les types MIME, il fait confiance a ce que le serveur annonce. - `X-Content-Type-Options: nosniff` : le navigateur ne devine pas les types MIME, il fait confiance a ce que le serveur annonce.
- `Referrer-Policy: strict-origin-when-cross-origin` : limite ce que Starseed envoie comme Referer a des sites externes (evite de leaker `/admin/users/42` a un site tiers). - `Referrer-Policy: strict-origin-when-cross-origin` : limite ce que Coltura envoie comme Referer a des sites externes (evite de leaker `/admin/users/42` a un site tiers).
- `always` : envoyer ces en-tetes meme sur les reponses d'erreur (4xx/5xx). - `always` : envoyer ces en-tetes meme sur les reponses d'erreur (4xx/5xx).
3. Recharger Nginx : `docker restart nginx-starseed` (ou celui qui fait office de proxy public). 3. Recharger Nginx : `docker restart nginx-coltura` (ou celui qui fait office de proxy public).
4. Verifier : `curl -I https://starseed.malio-dev.fr/` doit afficher ces trois en-tetes. 4. Verifier : `curl -I https://coltura.malio-dev.fr/` doit afficher ces trois en-tetes.
**Note :** si un reverse proxy externe (Traefik, Cloudflare) ajoute deja ces en-tetes, les poser ici ne fait que dupliquer, c'est sans risque (meme valeur). **Note :** si un reverse proxy externe (Traefik, Cloudflare) ajoute deja ces en-tetes, les poser ici ne fait que dupliquer, c'est sans risque (meme valeur).
@@ -813,7 +813,7 @@ if (!is_array($payload)) {
```markdown ```markdown
# Changelog # Changelog
Liste des evolutions du projet Starseed. Liste des evolutions du projet Coltura.
## [0.1.34] - 2026-04-XX ## [0.1.34] - 2026-04-XX
@@ -859,7 +859,7 @@ Liste des evolutions du projet Starseed.
### T-019 — Conditionner `loadSidebar()` apres switch de site ### T-019 — Conditionner `loadSidebar()` apres switch de site
**Pourquoi :** apres chaque switch de site, `useCurrentSite` recharge la sidebar — mais la sidebar de Starseed ne depend d'aucun site. C'est un aller-retour reseau inutile par switch (~100ms + possible flicker visuel). **Pourquoi :** apres chaque switch de site, `useCurrentSite` recharge la sidebar — mais la sidebar de Coltura ne depend d'aucun site. C'est un aller-retour reseau inutile par switch (~100ms + possible flicker visuel).
**A faire :** **A faire :**

View File

@@ -1,5 +1,5 @@
api_platform: api_platform:
title: Starseed API title: Coltura API
version: 1.0.0 version: 1.0.0
# Scan du module Core pour decouvrir les classes ApiResource et ApiFilter. # Scan du module Core pour decouvrir les classes ApiResource et ApiFilter.
# Ajouter un chemin par module lors de l'ajout d'entites ApiResource dans d'autres modules. # Ajouter un chemin par module lors de l'ajout d'entites ApiResource dans d'autres modules.

View File

@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '0.1.38' app.version: '0.1.34'

View File

@@ -210,7 +210,7 @@ Le `requestId` est set en `kernel.request` mais jamais cleared. En deploiement F
**Fichiers** : `config/packages/framework.yaml`, `src/Module/Core/Infrastructure/Audit/AuditLogWriter.php:69` **Fichiers** : `config/packages/framework.yaml`, `src/Module/Core/Infrastructure/Audit/AuditLogWriter.php:69`
Aucune entree `trusted_proxies` ni env `TRUSTED_PROXIES`. Starseed tourne derriere `nginx-starseed``php-starseed-fpm`. `Request::getClientIp()` retourne donc systematiquement l'IP **du conteneur nginx** (reseau Docker interne), pas l'IP reelle du client. Toute la valeur forensique de `ip_address` est nulle en prod. Aucune entree `trusted_proxies` ni env `TRUSTED_PROXIES`. Coltura tourne derriere `nginx-coltura``php-coltura-fpm`. `Request::getClientIp()` retourne donc systematiquement l'IP **du conteneur nginx** (reseau Docker interne), pas l'IP reelle du client. Toute la valeur forensique de `ip_address` est nulle en prod.
Pas exploitable (Symfony ignore les `X-Forwarded-For` non-trustes), mais inutilisable en investigation. Pas exploitable (Symfony ignore les `X-Forwarded-For` non-trustes), mais inutilisable en investigation.

View File

@@ -1,4 +1,4 @@
# Deploiement Docker — Starseed # Deploiement Docker — Coltura
## Pre-requis ## Pre-requis
@@ -29,9 +29,9 @@ sudo systemctl start nginx
### PostgreSQL ### PostgreSQL
PostgreSQL tourne dans un conteneur Docker separe (voir le repo `infra-postgres`). PostgreSQL tourne dans un conteneur Docker separe (voir le repo `infra-postgres`).
Il doit etre installe et accessible avant de deployer Starseed. Il doit etre installe et accessible avant de deployer Coltura.
Creer la base de donnees pour Starseed : Creer la base de donnees pour Coltura :
```bash ```bash
cd /var/www/postgres cd /var/www/postgres
@@ -43,7 +43,7 @@ docker compose exec postgres psql -U admin
CREATE USER malio WITH PASSWORD 'motdepasse'; CREATE USER malio WITH PASSWORD 'motdepasse';
-- Creer la base -- Creer la base
CREATE DATABASE starseed_prod OWNER malio; CREATE DATABASE coltura_prod OWNER malio;
\q \q
``` ```
@@ -51,7 +51,7 @@ CREATE DATABASE starseed_prod OWNER malio;
## Premiere installation (nouvelle machine) ## Premiere installation (nouvelle machine)
Guide complet pour mettre en ligne Starseed sur une machine vierge. Inclut les pre-requis, la BDD et l'app. Guide complet pour mettre en ligne Coltura sur une machine vierge. Inclut les pre-requis, la BDD et l'app.
### 1. Installer les pre-requis ### 1. Installer les pre-requis
@@ -60,9 +60,9 @@ Installer Docker, Nginx et PostgreSQL (voir section Pre-requis ci-dessus).
### 2. Creer le dossier de deploiement ### 2. Creer le dossier de deploiement
```bash ```bash
sudo mkdir -p /var/www/starseed sudo mkdir -p /var/www/coltura
sudo chown -R $(whoami):$(whoami) /var/www/starseed sudo chown -R $(whoami):$(whoami) /var/www/coltura
cd /var/www/starseed cd /var/www/coltura
``` ```
### 3. Se connecter au registry Docker de Gitea ### 3. Se connecter au registry Docker de Gitea
@@ -83,8 +83,8 @@ Creer `docker-compose.yml` :
```yaml ```yaml
services: services:
app: app:
image: gitea.malio.fr/malio-dev/starseed:${STARSEED_IMAGE_TAG:-latest} image: gitea.malio.fr/malio-dev/coltura:${COLTURA_IMAGE_TAG:-latest}
container_name: starseed-app container_name: coltura-app
env_file: .env env_file: .env
ports: ports:
- "8083:80" - "8083:80"
@@ -105,9 +105,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
TAG="${1:-latest}" TAG="${1:-latest}"
export STARSEED_IMAGE_TAG="$TAG" export COLTURA_IMAGE_TAG="$TAG"
echo "==> Deploying starseed:${TAG}..." echo "==> Deploying coltura:${TAG}..."
echo "==> Pulling image..." echo "==> Pulling image..."
docker compose pull docker compose pull
@@ -146,22 +146,22 @@ APP_DEBUG=0
APP_SECRET=<generer avec: openssl rand -hex 32> APP_SECRET=<generer avec: openssl rand -hex 32>
# Database (host.docker.internal = la machine hote, ou le PG tourne en Docker) # Database (host.docker.internal = la machine hote, ou le PG tourne en Docker)
DATABASE_URL="postgresql://malio:password@host.docker.internal:5432/starseed_prod?serverVersion=16&charset=utf8" DATABASE_URL="postgresql://malio:password@host.docker.internal:5432/coltura_prod?serverVersion=16&charset=utf8"
# JWT # JWT
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=<generer avec: openssl rand -hex 32> JWT_PASSPHRASE=<generer avec: openssl rand -hex 32>
JWT_COOKIE_SECURE=0 JWT_COOKIE_SECURE=1
JWT_COOKIE_SAMESITE=lax JWT_COOKIE_SAMESITE=lax
JWT_TOKEN_TTL=86400 JWT_TOKEN_TTL=86400
JWT_COOKIE_TTL=86400 JWT_COOKIE_TTL=86400
# CORS # CORS
CORS_ALLOW_ORIGIN='^https?://starseed\.malio-dev\.fr$' CORS_ALLOW_ORIGIN='^https?://coltura\.malio-dev\.fr$'
# App # App
DEFAULT_URI=http://starseed.malio-dev.fr DEFAULT_URI=https://coltura.malio-dev.fr
``` ```
### 6. Generer les cles JWT ### 6. Generer les cles JWT
@@ -190,17 +190,17 @@ mkdir -p uploads
Copier la config reverse proxy depuis le repo : Copier la config reverse proxy depuis le repo :
```bash ```bash
sudo cp infra/prod/nginx-proxy.conf /etc/nginx/sites-available/starseed.conf sudo cp infra/prod/nginx-proxy.conf /etc/nginx/sites-available/coltura.conf
``` ```
Ou creer `/etc/nginx/sites-available/starseed.conf` manuellement (voir `infra/prod/nginx-proxy.conf`). Ou creer `/etc/nginx/sites-available/coltura.conf` manuellement (voir `infra/prod/nginx-proxy.conf`).
La config inclut le **mode maintenance** : si le fichier `/var/www/starseed/maintenance.on` existe, Nginx renvoie une 503 avec `maintenance.html`. La config inclut le **mode maintenance** : si le fichier `/var/www/coltura/maintenance.on` existe, Nginx renvoie une 503 avec `maintenance.html`.
Activer le site : Activer le site :
```bash ```bash
sudo ln -sf /etc/nginx/sites-available/starseed.conf /etc/nginx/sites-enabled/starseed.conf sudo ln -sf /etc/nginx/sites-available/coltura.conf /etc/nginx/sites-enabled/coltura.conf
sudo nginx -t && sudo systemctl reload nginx sudo nginx -t && sudo systemctl reload nginx
``` ```
@@ -208,13 +208,13 @@ sudo nginx -t && sudo systemctl reload nginx
```bash ```bash
# Activer la maintenance # Activer la maintenance
touch /var/www/starseed/maintenance.on touch /var/www/coltura/maintenance.on
# Desactiver la maintenance # Desactiver la maintenance
rm /var/www/starseed/maintenance.on rm /var/www/coltura/maintenance.on
``` ```
Optionnel : creer une page `/var/www/starseed/public/maintenance.html` personnalisee. Optionnel : creer une page `/var/www/coltura/public/maintenance.html` personnalisee.
### 9. Deployer ### 9. Deployer
@@ -232,7 +232,7 @@ Choisir `App\Entity\User`, taper le mdp, copier le hash. Puis :
```bash ```bash
cd /var/www/postgres cd /var/www/postgres
docker compose exec -T postgres psql -U malio starseed_prod -c "INSERT INTO \"user\" (username, roles, password, created_at) VALUES ('admin', '[\"ROLE_ADMIN\"]', '<le-hash>', NOW());" docker compose exec -T postgres psql -U malio coltura_prod -c "INSERT INTO \"user\" (username, roles, password, created_at) VALUES ('admin', '[\"ROLE_ADMIN\"]', '<le-hash>', NOW());"
``` ```
Ou charger les fixtures (dev uniquement) : Ou charger les fixtures (dev uniquement) :
@@ -244,7 +244,7 @@ docker compose exec -T -u www-data app php bin/console doctrine:fixtures:load --
### Structure finale du dossier ### Structure finale du dossier
``` ```
/var/www/starseed/ /var/www/coltura/
├── docker-compose.yml ├── docker-compose.yml
├── deploy.sh ├── deploy.sh
├── .env ├── .env
@@ -261,7 +261,7 @@ docker compose exec -T -u www-data app php bin/console doctrine:fixtures:load --
Quand l'app est deja installee, deployer une mise a jour : Quand l'app est deja installee, deployer une mise a jour :
```bash ```bash
cd /var/www/starseed cd /var/www/coltura
./deploy.sh # deploie la derniere version (latest) ./deploy.sh # deploie la derniere version (latest)
./deploy.sh v0.2.0 # deploie une version specifique ./deploy.sh v0.2.0 # deploie une version specifique
``` ```
@@ -293,7 +293,7 @@ docker compose exec -T -u www-data app php bin/console doctrine:migrations:migra
Le workflow `.gitea/workflows/build-docker.yml` se declenche automatiquement sur push de tag `v*` : Le workflow `.gitea/workflows/build-docker.yml` se declenche automatiquement sur push de tag `v*` :
1. Build l'image multi-stage 1. Build l'image multi-stage
2. Push vers `gitea.malio.fr/malio-dev/starseed:<tag>` et `:latest` 2. Push vers `gitea.malio.fr/malio-dev/coltura:<tag>` et `:latest`
Combine avec `auto-tag-develop.yml`, chaque push sur `develop` cree automatiquement un tag → build → image disponible. Combine avec `auto-tag-develop.yml`, chaque push sur `develop` cree automatiquement un tag → build → image disponible.
@@ -302,7 +302,7 @@ Combine avec `auto-tag-develop.yml`, chaque push sur `develop` cree automatiquem
## Voir les logs ## Voir les logs
```bash ```bash
cd /var/www/starseed cd /var/www/coltura
docker compose logs -f # tous les logs docker compose logs -f # tous les logs
docker compose logs -f --tail=100 # 100 dernieres lignes docker compose logs -f --tail=100 # 100 dernieres lignes
``` ```

View File

@@ -1,231 +0,0 @@
# Prompt — Migration prod Coltura -> Starseed
Copier-coller integralement dans une session Claude lancee **sur le serveur de prod** apres que :
- le push develop + build CI ont publie l'image `gitea.malio.fr/malio-dev/starseed:latest`,
- la resolution reseau local (DNS interne ou `/etc/hosts` des postes clients) pour `starseed.malio-dev.fr` est en place.
> Setup : HTTP en reseau local, pas de TLS. Pas de Let's Encrypt.
---
## Prompt a fournir au Claude prod
Tu es sur le serveur de production d'une app Symfony+Nuxt qui s'appelait **Coltura** et qui doit etre renommee en **Starseed**. Le rename cote code est deja fait et merge. Le repo Gitea s'appelle deja `starseed`. L'image `gitea.malio.fr/malio-dev/starseed:latest` est publiee.
L'app est servie en **HTTP sur reseau local** (pas de TLS, pas de Let's Encrypt). La resolution `starseed.malio-dev.fr` est faite via DNS interne ou `/etc/hosts` cote postes clients — pas de certificat a gerer.
Objectif : basculer la prod sur le nouveau nom (registry, container, DB, path FS, vhost) **sans perdre les donnees** et avec downtime minimal (mode maintenance pendant la migration).
**Etat actuel a verifier en premier** (donne-moi le retour de chaque commande avant de continuer) :
```bash
# 1. Container actuel + image
sudo docker ps --filter name=coltura-app --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
# 2. DB existante
sudo -u postgres psql -c "\l" | grep -E "coltura|starseed"
# 3. Path FS app
ls -la /var/www/coltura/ 2>/dev/null | head -5
ls -la /var/www/starseed/ 2>/dev/null | head -5
# 4. Vhost nginx system
sudo ls -la /etc/nginx/sites-enabled/ | grep -E "coltura|starseed"
```
**Apres confirmation de l'etat, executer dans cet ordre, en demandant validation utilisateur AVANT chaque etape destructive (DB drop, rm -rf, certificat) :**
### Etape 1 — Mode maintenance
```bash
cd /var/www/coltura
touch maintenance.on
# Verifier qu'une requete renvoie 503
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://coltura.malio-dev.fr/
```
Doit renvoyer `503`.
### Etape 2 — Backup DB (CRITIQUE — ne pas skipper)
```bash
BACKUP_FILE="/root/coltura_prod_backup_$(date +%Y%m%d_%H%M%S).sql"
sudo -u postgres pg_dump -F c -f "$BACKUP_FILE" coltura_prod
ls -lh "$BACKUP_FILE"
```
**Stocker ce chemin** — il sera utilise pour le rollback.
### Etape 3 — Creer la DB cible et migrer
Recuperer l'owner et le user de connexion actuels :
```bash
sudo -u postgres psql -c "\l coltura_prod"
grep DATABASE_URL /var/www/coltura/.env
```
Puis (adapter l'owner si different de `malio`) :
```bash
sudo -u postgres psql <<'SQL'
CREATE DATABASE starseed_prod OWNER malio;
SQL
sudo -u postgres pg_dump coltura_prod | sudo -u postgres psql starseed_prod
sudo -u postgres psql starseed_prod -c "\dt" | head -20
```
Verifier que les tables sont bien copiees. Si le user PG s'appelle `coltura`, le renommer ou en creer un `starseed` est OPTIONNEL — la connexion peut continuer avec `coltura` tant que `GRANT` est OK. **Confirmer avec l'utilisateur** s'il veut renommer le role PG :
```bash
# Optionnel : renommer le role PG (si user de connexion s'appelle 'coltura')
# sudo -u postgres psql -c "ALTER ROLE coltura RENAME TO starseed;"
```
### Etape 4 — Renommer le path FS
```bash
sudo mv /var/www/coltura /var/www/starseed
# Verifier le contenu
sudo ls -la /var/www/starseed/ | head -10
# Verifier que .env existe encore
sudo test -f /var/www/starseed/.env && echo ".env OK"
```
### Etape 5 — Mettre a jour .env de prod
Editer `/var/www/starseed/.env` :
- `DATABASE_URL` : remplacer `/coltura_prod` -> `/starseed_prod` (et user si renomme a etape 3)
- `CORS_ALLOW_ORIGIN` : remplacer `coltura.malio-dev.fr` -> `starseed.malio-dev.fr`
- `DEFAULT_URI` : `http://starseed.malio-dev.fr`
- `JWT_COOKIE_SECURE` : doit etre `0` (HTTP, pas de TLS) — verifier qu'il l'est deja
Diff attendu :
```diff
- DATABASE_URL="postgresql://malio:xxx@host.docker.internal:5432/coltura_prod?..."
+ DATABASE_URL="postgresql://malio:xxx@host.docker.internal:5432/starseed_prod?..."
- CORS_ALLOW_ORIGIN='^http://coltura\.malio-dev\.fr$'
+ CORS_ALLOW_ORIGIN='^http://starseed\.malio-dev\.fr$'
- DEFAULT_URI=http://coltura.malio-dev.fr
+ DEFAULT_URI=http://starseed.malio-dev.fr
```
### Etape 6 — Stopper et supprimer l'ancien container
```bash
cd /var/www/starseed
sudo docker compose down
# Verifier qu'il n'y a plus de coltura-app
sudo docker ps -a --filter name=coltura
```
### Etape 7 — Pull la nouvelle image et demarrer
Le `docker-compose.prod.yml` du dossier deja a jour pointe sur `gitea.malio.fr/malio-dev/starseed:latest` et `container_name: starseed-app`.
```bash
cd /var/www/starseed
sudo docker compose pull
sudo docker compose up -d
sleep 5
sudo docker ps --filter name=starseed-app
sudo docker logs starseed-app --tail 30
```
### Etape 8 — Migrations Doctrine + cache
```bash
cd /var/www/starseed
sudo docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate --no-interaction
sudo docker compose exec -T -u www-data app php bin/console cache:clear --env=prod
sudo docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod
```
### Etape 9 — Vhost nginx system (HTTP only)
Copier le nouveau vhost (a jour avec `server_name starseed.malio-dev.fr` et `root /var/www/starseed/public`, `listen 80` uniquement) :
```bash
sudo cp /var/www/starseed/infra/prod/nginx-proxy.conf /etc/nginx/sites-available/starseed.conf
sudo ln -sf /etc/nginx/sites-available/starseed.conf /etc/nginx/sites-enabled/starseed.conf
sudo rm -f /etc/nginx/sites-enabled/coltura.conf
sudo nginx -t
```
Verifier la resolution reseau local avant reload :
```bash
getent hosts starseed.malio-dev.fr || echo "ATTENTION : starseed.malio-dev.fr ne resout pas localement"
```
Puis :
```bash
sudo systemctl reload nginx
```
### Etape 10 — Desactiver le mode maintenance et tester
```bash
rm -f /var/www/starseed/maintenance.on
# Tests externes (HTTP local)
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://starseed.malio-dev.fr/
curl -s http://starseed.malio-dev.fr/api/version
```
`/api/version` doit renvoyer du JSON avec la version courante.
### Etape 11 — Cleanup (apres 24-48h de stabilite)
A faire **plus tard**, seulement quand on est sur que tout marche :
```bash
# Backup deja conserve en /root/coltura_prod_backup_*.sql.
# Apres validation utilisateur :
sudo -u postgres psql -c "DROP DATABASE coltura_prod;"
sudo rm -f /etc/nginx/sites-available/coltura.conf
sudo docker image prune # nettoie les vieilles images coltura
```
---
## Rollback (si echec apres etape 5)
```bash
# 1. Remettre maintenance
touch /var/www/starseed/maintenance.on 2>/dev/null || touch /var/www/coltura/maintenance.on
# 2. Restaurer le path FS
sudo mv /var/www/starseed /var/www/coltura 2>/dev/null || true
# 3. Restaurer le vhost coltura
sudo rm -f /etc/nginx/sites-enabled/starseed.conf
sudo ln -sf /etc/nginx/sites-available/coltura.conf /etc/nginx/sites-enabled/coltura.conf
sudo systemctl reload nginx
# 4. Redemarrer l'ancien container (l'image coltura est encore dans le registry)
cd /var/www/coltura
# Editer docker-compose.prod.yml pour pointer sur coltura:latest si necessaire
sudo docker compose up -d
# 5. Si la DB starseed_prod a ete modifiee, restaurer depuis le backup
sudo -u postgres psql -c "DROP DATABASE IF EXISTS coltura_prod;"
sudo -u postgres pg_restore -C -d postgres "$BACKUP_FILE"
# 6. Lever maintenance
rm -f /var/www/coltura/maintenance.on
```
---
## Regles de comportement pour le Claude prod
- **Ne jamais skipper le backup** (etape 2).
- **Demander confirmation utilisateur** avant : `DROP DATABASE`, `rm -rf`, et avant de lever le mode maintenance final.
- **Une seule operation destructive a la fois**, attendre le retour utilisateur entre chaque.
- **Logger systematiquement** la sortie des commandes critiques (pg_dump, docker compose up, nginx -t / reload).
- **Si une etape echoue**, NE PAS continuer — declencher le rollback.
- **Ne commit rien** sur le repo depuis le serveur prod.

View File

@@ -9,7 +9,7 @@
## Résumé de la PR ## Résumé de la PR
Cette PR restructure Starseed (CRM/ERP) en **architecture modulaire DDD** (Domain-Driven Design) : Cette PR restructure Coltura (CRM/ERP) en **architecture modulaire DDD** (Domain-Driven Design) :
- **Backend** : introduction de bounded contexts (`Module/Core`, `Module/Commercial`) avec séparation Domain / Application / Infrastructure - **Backend** : introduction de bounded contexts (`Module/Core`, `Module/Commercial`) avec séparation Domain / Application / Infrastructure
- **Shared** : couche partagée (events, value objects, contracts, bus interfaces) - **Shared** : couche partagée (events, value objects, contracts, bus interfaces)
@@ -36,9 +36,9 @@ Cette PR restructure Starseed (CRM/ERP) en **architecture modulaire DDD** (Domai
Liste des évolutions du projet Ferme Liste des évolutions du projet Ferme
``` ```
Ce fichier appartient à **Starseed**, pas au projet Ferme. C'est une erreur de copier-coller lors du scaffolding initial. Ce fichier appartient à **Coltura**, pas au projet Ferme. C'est une erreur de copier-coller lors du scaffolding initial.
**Correction** : Remplacer "Ferme" par "Starseed". **Correction** : Remplacer "Ferme" par "Coltura".
--- ---
@@ -76,10 +76,10 @@ Mais la seule page du module commercial est `frontend/modules/commercial/pages/c
|---|---| |---|---|
| **Sévérité** | Majeure | | **Sévérité** | Majeure |
| **Fichier** | `infra/dev/.env.docker` | | **Fichier** | `infra/dev/.env.docker` |
| **Règle violée** | Workspace `CLAUDE.md` : "Starseed — 8083 / 3003 / **5436**" | | **Règle violée** | Workspace `CLAUDE.md` : "Coltura — 8083 / 3003 / **5436**" |
| **Confiance** | 75/100 | | **Confiance** | 75/100 |
**Constat** : Le fichier `.env.docker` définit `POSTGRES_PORT=5437`, alors que le port documenté pour Starseed est `5436`. **Constat** : Le fichier `.env.docker` définit `POSTGRES_PORT=5437`, alors que le port documenté pour Coltura est `5436`.
**Impact** : Tout développeur qui suit les ports documentés (ou qui utilise des scripts basés sur ces ports) ne pourra pas se connecter à la base. **Impact** : Tout développeur qui suit les ports documentés (ou qui utilise des scripts basés sur ces ports) ne pourra pas se connecter à la base.
@@ -93,7 +93,7 @@ Mais la seule page du module commercial est `frontend/modules/commercial/pages/c
|---|---| |---|---|
| **Sévérité** | Majeure | | **Sévérité** | Majeure |
| **Fichiers** | `frontend/nuxt.config.ts` (ligne 40), `docker-compose.yml` (ligne 33) | | **Fichiers** | `frontend/nuxt.config.ts` (ligne 40), `docker-compose.yml` (ligne 33) |
| **Règle violée** | Workspace `CLAUDE.md` : "Starseed — 8083 / **3003** / 5436" et `CLAUDE.md` projet : "make dev-nuxt # port 3003" | | **Règle violée** | Workspace `CLAUDE.md` : "Coltura — 8083 / **3003** / 5436" et `CLAUDE.md` projet : "make dev-nuxt # port 3003" |
| **Confiance** | 75/100 (confirmé par 3 agents indépendants) | | **Confiance** | 75/100 (confirmé par 3 agents indépendants) |
**Constat** : **Constat** :

View File

@@ -41,7 +41,7 @@ services:
- "8083:80" - "8083:80"
volumes: volumes:
- ./:/var/www/html:ro - ./:/var/www/html:ro
- ./infra/dev/nginx.conf:/etc/nginx/conf.d/default.conf:ro - ./infra/dev/nginx.conf:/etc/nginx/conf.d/coltura.conf:ro
restart: unless-stopped restart: unless-stopped
db: db:
image: postgres:16-alpine image: postgres:16-alpine

View File

@@ -13,7 +13,7 @@ Ce ticket livre la base RBAC backend de l'epic en 5 tickets en remplacant le sto
- Faire evoluer `User` avec une relation ManyToMany vers `Role`, une relation ManyToMany vers `Permission` pour les permissions directes et un booleen `is_admin`. - Faire evoluer `User` avec une relation ManyToMany vers `Role`, une relation ManyToMany vers `Permission` pour les permissions directes et un booleen `is_admin`.
- Faire evoluer `User::getRoles()` pour rester compatible Symfony en retournant toujours `ROLE_USER` et `ROLE_ADMIN` si `is_admin = true`. - Faire evoluer `User::getRoles()` pour rester compatible Symfony en retournant toujours `ROLE_USER` et `ROLE_ADMIN` si `is_admin = true`.
- Ajouter `User::getEffectivePermissions()` pour retourner l'union des codes de permissions provenant des roles et des permissions directes. - Ajouter `User::getEffectivePermissions()` pour retourner l'union des codes de permissions provenant des roles et des permissions directes.
- Ajouter une methode statique `permissions()` sur `/home/matthieu/dev_malio/Starseed/src/Module/Core/CoreModule.php` et definir le pattern a reproduire pour les autres modules. - Ajouter une methode statique `permissions()` sur `/home/matthieu/dev_malio/Coltura/src/Module/Core/CoreModule.php` et definir le pattern a reproduire pour les autres modules.
- Ajouter une commande console `app:sync-permissions` transactionnelle, idempotente et non destructive avec gestion `orphan`. - Ajouter une commande console `app:sync-permissions` transactionnelle, idempotente et non destructive avec gestion `orphan`.
- Ajouter une migration Doctrine modulaire Core qui cree les tables RBAC, migre les donnees depuis `user.roles`, cree les roles systeme `admin` et `user`, puis supprime la colonne JSON `roles`. - Ajouter une migration Doctrine modulaire Core qui cree les tables RBAC, migre les donnees depuis `user.roles`, cree les roles systeme `admin` et `user`, puis supprime la colonne JSON `roles`.
- Mettre a jour les fixtures Core pour creer les roles systeme et rattacher l'utilisateur admin au role `admin`. - Mettre a jour les fixtures Core pour creer les roles systeme et rattacher l'utilisateur admin au role `admin`.
@@ -31,30 +31,30 @@ Ce ticket livre la base RBAC backend de l'epic en 5 tickets en remplacant le sto
### Domaine - Entités ### Domaine - Entités
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/Permission.php` : entite Doctrine de permission RBAC, code unique, module source et etat `orphan`. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/Permission.php` : entite Doctrine de permission RBAC, code unique, module source et etat `orphan`.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/Role.php` : entite Doctrine de role RBAC avec relations vers permissions et garde de role systeme. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/Role.php` : entite Doctrine de role RBAC avec relations vers permissions et garde de role systeme.
### Domaine - Repositories ### Domaine - Repositories
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Repository/PermissionRepositoryInterface.php` : contrat de lecture/ecriture des permissions pour la commande de sync et les fixtures. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Repository/PermissionRepositoryInterface.php` : contrat de lecture/ecriture des permissions pour la commande de sync et les fixtures.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Repository/RoleRepositoryInterface.php` : contrat de lecture/ecriture des roles pour migration fonctionnelle, fixtures et usages futurs. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Repository/RoleRepositoryInterface.php` : contrat de lecture/ecriture des roles pour migration fonctionnelle, fixtures et usages futurs.
### Domaine - Exceptions ### Domaine - Exceptions
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Exception/SystemRoleDeletionException.php` : exception domaine levee si une suppression vise un role systeme. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Exception/SystemRoleDeletionException.php` : exception domaine levee si une suppression vise un role systeme.
### Infrastructure - Doctrine ### Infrastructure - Doctrine
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Doctrine/DoctrinePermissionRepository.php` : implementation Doctrine de `PermissionRepositoryInterface`. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Doctrine/DoctrinePermissionRepository.php` : implementation Doctrine de `PermissionRepositoryInterface`.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Doctrine/DoctrineRoleRepository.php` : implementation Doctrine de `RoleRepositoryInterface`. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Doctrine/DoctrineRoleRepository.php` : implementation Doctrine de `RoleRepositoryInterface`.
### Infrastructure - Doctrine Migrations ### Infrastructure - Doctrine Migrations
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Doctrine/Migrations/Version<timestamp>.php` : migration modulaire RBAC Core avec schema + migration de donnees + rollback minimal. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Doctrine/Migrations/Version<timestamp>.php` : migration modulaire RBAC Core avec schema + migration de donnees + rollback minimal.
### Infrastructure - Console ### Infrastructure - Console
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Console/SyncPermissionsCommand.php` : commande `app:sync-permissions` qui scanne les modules actifs et synchronise la table `permission`. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Console/SyncPermissionsCommand.php` : commande `app:sync-permissions` qui scanne les modules actifs et synchronise la table `permission`.
### Infrastructure - DataFixtures ### Infrastructure - DataFixtures
@@ -62,12 +62,12 @@ Ce ticket livre la base RBAC backend de l'epic en 5 tickets en remplacant le sto
### Constantes domaine ### Constantes domaine
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Security/SystemRoles.php` : constantes partagees `ADMIN_CODE = 'admin'` et `USER_CODE = 'user'`, utilisees a la fois par les fixtures et par la migration SQL. Place dans `Domain/Security/` (pas `ValueObject/` : ce n'est pas un VO, c'est un conteneur de constantes metier laissant de la place pour d'autres constantes de securite plus tard). - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Security/SystemRoles.php` : constantes partagees `ADMIN_CODE = 'admin'` et `USER_CODE = 'user'`, utilisees a la fois par les fixtures et par la migration SQL. Place dans `Domain/Security/` (pas `ValueObject/` : ce n'est pas un VO, c'est un conteneur de constantes metier laissant de la place pour d'autres constantes de securite plus tard).
## 4. Fichiers à modifier ## 4. Fichiers à modifier
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/User.php` : supprimer le stockage JSON `roles`, ajouter `isAdmin`, `roles`, `directPermissions`, initialiser les collections, configurer les relations ManyToMany en `fetch=EAGER`, ajouter `getEffectivePermissions()` et adapter `getRoles()` / mutateurs. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/User.php` : supprimer le stockage JSON `roles`, ajouter `isAdmin`, `roles`, `directPermissions`, initialiser les collections, configurer les relations ManyToMany en `fetch=EAGER`, ajouter `getEffectivePermissions()` et adapter `getRoles()` / mutateurs.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/CoreModule.php` : ajouter une methode statique `public static function permissions(): array` qui declare les permissions natives du module Core et sert de reference pour les autres modules. Contenu initial exact : - `/home/matthieu/dev_malio/Coltura/src/Module/Core/CoreModule.php` : ajouter une methode statique `public static function permissions(): array` qui declare les permissions natives du module Core et sert de reference pour les autres modules. Contenu initial exact :
```php ```php
public static function permissions(): array public static function permissions(): array
{ {
@@ -80,17 +80,17 @@ Ce ticket livre la base RBAC backend de l'epic en 5 tickets en remplacant le sto
} }
``` ```
La cle `module` n'est PAS presente dans le payload : elle est auto-injectee par la commande de sync a partir de `CoreModule::ID`. Le code de permission doit obligatoirement commencer par `self::ID . '.'` sous peine d'echec de la sync (garde anti-typo). La cle `module` n'est PAS presente dans le payload : elle est auto-injectee par la commande de sync a partir de `CoreModule::ID`. Le code de permission doit obligatoirement commencer par `self::ID . '.'` sous peine d'echec de la sync (garde anti-typo).
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Doctrine/DoctrineUserRepository.php` : aucun changement attendu dans ce ticket. Les nouvelles relations `$roles`, `$directPermissions` sont chargees par Doctrine via leurs mappings `fetch=EAGER` declares sur l'entite. Si les tests d'integration revelent un lazy-load non voulu au refresh JWT ou a la desserialisation, ajouter une methode `findForSecurity(string $username): ?User` avec `leftJoin` + `addSelect` explicites sur `roles`, `roles.permissions`, `directPermissions`, et brancher le user provider dessus. A trancher par les tests, pas en prevention. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Doctrine/DoctrineUserRepository.php` : aucun changement attendu dans ce ticket. Les nouvelles relations `$roles`, `$directPermissions` sont chargees par Doctrine via leurs mappings `fetch=EAGER` declares sur l'entite. Si les tests d'integration revelent un lazy-load non voulu au refresh JWT ou a la desserialisation, ajouter une methode `findForSecurity(string $username): ?User` avec `leftJoin` + `addSelect` explicites sur `roles`, `roles.permissions`, `directPermissions`, et brancher le user provider dessus. A trancher par les tests, pas en prevention.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Repository/UserRepositoryInterface.php` : aucun changement dans ce ticket. Ajout eventuel de `findForSecurity()` uniquement si le cas ci-dessus se materialise. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Repository/UserRepositoryInterface.php` : aucun changement dans ce ticket. Ajout eventuel de `findForSecurity()` uniquement si le cas ci-dessus se materialise.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php` : remplacer l'usage de `setRoles(array)` par la creation des roles systeme, le rattachement des utilisateurs a ces roles et le positionnement de `is_admin`. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php` : remplacer l'usage de `setRoles(array)` par la creation des roles systeme, le rattachement des utilisateurs a ces roles et le positionnement de `is_admin`.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Console/CreateUserCommand.php` : remplacer la gestion historique de `ROLE_ADMIN` par `setIsAdmin(true)` et rattachement au role systeme `admin` si l'option `--admin` est conservee. - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Console/CreateUserCommand.php` : remplacer la gestion historique de `ROLE_ADMIN` par `setIsAdmin(true)` et rattachement au role systeme `admin` si l'option `--admin` est conservee.
- `/home/matthieu/dev_malio/Starseed/config/services.yaml` : ajouter 2 alias repository, aligne sur le pattern existant pour `UserRepositoryInterface` : - `/home/matthieu/dev_malio/Coltura/config/services.yaml` : ajouter 2 alias repository, aligne sur le pattern existant pour `UserRepositoryInterface` :
```yaml ```yaml
App\Module\Core\Domain\Repository\RoleRepositoryInterface: '@App\Module\Core\Infrastructure\Doctrine\DoctrineRoleRepository' App\Module\Core\Domain\Repository\RoleRepositoryInterface: '@App\Module\Core\Infrastructure\Doctrine\DoctrineRoleRepository'
App\Module\Core\Domain\Repository\PermissionRepositoryInterface: '@App\Module\Core\Infrastructure\Doctrine\DoctrinePermissionRepository' App\Module\Core\Domain\Repository\PermissionRepositoryInterface: '@App\Module\Core\Infrastructure\Doctrine\DoctrinePermissionRepository'
``` ```
La commande `SyncPermissionsCommand` est auto-configuree via `autoconfigure: true`, aucun binding manuel necessaire. La commande `SyncPermissionsCommand` est auto-configuree via `autoconfigure: true`, aucun binding manuel necessaire.
- `/home/matthieu/dev_malio/Starseed/config/modules.php` : aucun changement de contenu requis, mais la commande `app:sync-permissions` devra s'appuyer sur ce fichier comme source de verite des modules actifs. - `/home/matthieu/dev_malio/Coltura/config/modules.php` : aucun changement de contenu requis, mais la commande `app:sync-permissions` devra s'appuyer sur ce fichier comme source de verite des modules actifs.
## 5. Schéma cible — mappings Doctrine ## 5. Schéma cible — mappings Doctrine
@@ -209,7 +209,7 @@ Etat final attendu :
## 6. Plan de migration Doctrine ## 6. Plan de migration Doctrine
La migration doit etre implementée dans `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Doctrine/Migrations/Version<timestamp>.php` et executer `up()` dans cet ordre. La migration doit etre implementée dans `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Doctrine/Migrations/Version<timestamp>.php` et executer `up()` dans cet ordre.
**Workflow recommande** : **Workflow recommande** :
1. Ecrire d'abord les entites `Permission`, `Role` et la mutation de `User` (section 5). 1. Ecrire d'abord les entites `Permission`, `Role` et la mutation de `User` (section 5).
@@ -287,7 +287,7 @@ Cas couverts explicitement :
Le mapping Doctrine actuel (`array` PHP → default) peut avoir genere une colonne `JSON` OU `TEXT` selon la version de Symfony/Doctrine. Le cast `::jsonb` fonctionne directement sur `JSON`, mais pas sur `TEXT`. **Avant d'executer la migration en prod**, verifier avec : Le mapping Doctrine actuel (`array` PHP → default) peut avoir genere une colonne `JSON` OU `TEXT` selon la version de Symfony/Doctrine. Le cast `::jsonb` fonctionne directement sur `JSON`, mais pas sur `TEXT`. **Avant d'executer la migration en prod**, verifier avec :
```bash ```bash
docker exec -it db-starseed psql -U malio -d starseed -c '\d "user"' docker exec -it db-coltura psql -U malio -d coltura -c '\d "user"'
``` ```
- Si `roles | json` : le SQL ci-dessus fonctionne tel quel. - Si `roles | json` : le SQL ci-dessus fonctionne tel quel.
@@ -306,11 +306,11 @@ Le rollback ne restitue pas la granularite RBAC complete, ce qui est acceptable
## 7. Algorithme sync-permissions ## 7. Algorithme sync-permissions
La commande `app:sync-permissions` doit vivre dans `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Console/SyncPermissionsCommand.php` et encapsuler toute l'operation dans une transaction Doctrine unique. La commande `app:sync-permissions` doit vivre dans `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Console/SyncPermissionsCommand.php` et encapsuler toute l'operation dans une transaction Doctrine unique.
### Source de verite ### Source de verite
- Le scan des modules actifs vient de `/home/matthieu/dev_malio/Starseed/config/modules.php`. - Le scan des modules actifs vient de `/home/matthieu/dev_malio/Coltura/config/modules.php`.
- Chaque classe module active peut exposer `public static function permissions(): array`. - Chaque classe module active peut exposer `public static function permissions(): array`.
- Par compatibilite montante, si une classe module n'expose pas encore `permissions()`, elle est traitee comme retournant `[]`. - Par compatibilite montante, si une classe module n'expose pas encore `permissions()`, elle est traitee comme retournant `[]`.
@@ -330,7 +330,7 @@ Garde anti-typo : le sync command verifie que chaque `code` commence obligatoire
```text ```text
begin transaction begin transaction
load active module classes from /home/matthieu/dev_malio/Starseed/config/modules.php load active module classes from /home/matthieu/dev_malio/Coltura/config/modules.php
desired_permissions = empty map keyed by code desired_permissions = empty map keyed by code
for each module class: for each module class:
@@ -439,7 +439,7 @@ Repasse `orphan` a `false` et remet a jour les metadonnees issues de la declarat
## 9. Fixtures mises à jour ## 9. Fixtures mises à jour
Le fichier cible reste `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php`. Le fichier cible reste `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php`.
### Principe cle : decouplage via `is_admin` ### Principe cle : decouplage via `is_admin`
@@ -519,7 +519,7 @@ Les tests d'integration migration up/down exigent une base de test dediee avec u
- Risque de perte de donnees pendant la suppression de la colonne `user.roles`. - Risque de perte de donnees pendant la suppression de la colonne `user.roles`.
- Mitigation : creer les roles systeme et inserer les jointures `user_role` avant tout `DROP COLUMN`, avec tests de migration sur etats mixtes. - Mitigation : creer les roles systeme et inserer les jointures `user_role` avant tout `DROP COLUMN`, avec tests de migration sur etats mixtes.
- Risque de divergence entre migration SQL brute et fixtures sur les codes des roles systeme. - Risque de divergence entre migration SQL brute et fixtures sur les codes des roles systeme.
- Mitigation : centraliser `admin` et `user` dans `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Security/SystemRoles.php` et documenter que la migration doit reprendre ces valeurs telles quelles. - Mitigation : centraliser `admin` et `user` dans `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Security/SystemRoles.php` et documenter que la migration doit reprendre ces valeurs telles quelles.
- Risque d'accumulation de permissions orphelines sur des environnements de dev ou apres refactors de codes. - Risque d'accumulation de permissions orphelines sur des environnements de dev ou apres refactors de codes.
- Mitigation : conserver `orphan = true` pour la non-destruction, mais ajouter un suivi explicite dans les tests et dans la documentation d'exploitation; une strategie de purge pourra etre traitee plus tard si necessaire. - Mitigation : conserver `orphan = true` pour la non-destruction, mais ajouter un suivi explicite dans les tests et dans la documentation d'exploitation; une strategie de purge pourra etre traitee plus tard si necessaire.
- Risque de sync incoherente entre dev et prod si un module actif ne declare pas encore `permissions()`. - Risque de sync incoherente entre dev et prod si un module actif ne declare pas encore `permissions()`.
@@ -533,12 +533,12 @@ Les tests d'integration migration up/down exigent une base de test dediee avec u
1. Creer `Permission`, `Role`, `SystemRoleDeletionException` et `SystemRoles`. 1. Creer `Permission`, `Role`, `SystemRoleDeletionException` et `SystemRoles`.
2. Creer `PermissionRepositoryInterface`, `RoleRepositoryInterface` et leurs implementations Doctrine. 2. Creer `PermissionRepositoryInterface`, `RoleRepositoryInterface` et leurs implementations Doctrine.
3. Faire evoluer `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/User.php` avec `is_admin`, `roles`, `directPermissions`, `getRoles()` et `getEffectivePermissions()`. 3. Faire evoluer `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/User.php` avec `is_admin`, `roles`, `directPermissions`, `getRoles()` et `getEffectivePermissions()`.
4. Ajouter `CoreModule::permissions()` et documenter le pattern de declaration statique pour les autres modules. 4. Ajouter `CoreModule::permissions()` et documenter le pattern de declaration statique pour les autres modules.
5. Ajouter la commande `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Console/SyncPermissionsCommand.php`. 5. Ajouter la commande `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Console/SyncPermissionsCommand.php`.
6. Ecrire la migration `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Doctrine/Migrations/Version<timestamp>.php` avec schema + migration de donnees + down(). 6. Ecrire la migration `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Doctrine/Migrations/Version<timestamp>.php` avec schema + migration de donnees + down().
7. Mettre a jour `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php` et `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Console/CreateUserCommand.php`. 7. Mettre a jour `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php` et `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Console/CreateUserCommand.php`.
8. Ajouter les alias repository dans `/home/matthieu/dev_malio/Starseed/config/services.yaml`. 8. Ajouter les alias repository dans `/home/matthieu/dev_malio/Coltura/config/services.yaml`.
9. Ecrire les tests unitaires et d'integration couvrant domaine, sync, fixtures et migration. 9. Ecrire les tests unitaires et d'integration couvrant domaine, sync, fixtures et migration.
## 13. Critères d'acceptation (DoD) ## 13. Critères d'acceptation (DoD)
@@ -553,4 +553,4 @@ Les tests d'integration migration up/down exigent une base de test dediee avec u
- La suppression d'un role systeme leve `SystemRoleDeletionException` au niveau domaine. - La suppression d'un role systeme leve `SystemRoleDeletionException` au niveau domaine.
- Les associations `User::$roles`, `User::$directPermissions` et `Role::$permissions` sont explicitement configurees en `fetch=EAGER` et ce point est verifie par tests. - Les associations `User::$roles`, `User::$directPermissions` et `Role::$permissions` sont explicitement configurees en `fetch=EAGER` et ce point est verifie par tests.
- Les fixtures attribuent `is_admin = true` + role `admin` a l'utilisateur `admin`, et le role `user` aux utilisateurs standards. - Les fixtures attribuent `is_admin = true` + role `admin` a l'utilisateur `admin`, et le role `user` aux utilisateurs standards.
- Le spec est compatible avec l'architecture modulaire actuelle basee sur `/home/matthieu/dev_malio/Starseed/config/modules.php` et n'introduit aucune resource API Platform ni voter dans ce ticket. - Le spec est compatible avec l'architecture modulaire actuelle basee sur `/home/matthieu/dev_malio/Coltura/config/modules.php` et n'introduit aucune resource API Platform ni voter dans ce ticket.

View File

@@ -38,28 +38,28 @@ Le ticket n'introduit **aucune logique d'autorisation metier** : toute la verifi
### Infrastructure - Processors ### Infrastructure - Processors
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/RoleProcessor.php` - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/RoleProcessor.php`
Decorator de `ApiPlatform\Doctrine\Common\State\PersistProcessor` et `RemoveProcessor`. Charge de la garde `ensureDeletable()` et de la protection des champs immuables sur un role systeme. Decorator de `ApiPlatform\Doctrine\Common\State\PersistProcessor` et `RemoveProcessor`. Charge de la garde `ensureDeletable()` et de la protection des champs immuables sur un role systeme.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessor.php` - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessor.php`
Decorator de `PersistProcessor` specifique a l'operation `PATCH /api/users/{id}/rbac`. Persiste les mutations `isAdmin`, `roles`, `directPermissions` sans passer par `UserPasswordHasherProcessor`. Decorator de `PersistProcessor` specifique a l'operation `PATCH /api/users/{id}/rbac`. Persiste les mutations `isAdmin`, `roles`, `directPermissions` sans passer par `UserPasswordHasherProcessor`.
### Tests unitaires ### Tests unitaires
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Infrastructure/ApiPlatform/State/Processor/RoleProcessorTest.php` - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Infrastructure/ApiPlatform/State/Processor/RoleProcessorTest.php`
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessorTest.php` - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessorTest.php`
### Tests fonctionnels ### Tests fonctionnels
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Api/PermissionApiTest.php` - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Api/PermissionApiTest.php`
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Api/RoleApiTest.php` - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Api/RoleApiTest.php`
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Api/UserRbacApiTest.php` - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Api/UserRbacApiTest.php`
## 4. Fichiers a modifier ## 4. Fichiers a modifier
### Entite `Permission` ### Entite `Permission`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/Permission.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/Permission.php`
- Ajouter l'attribut `#[ApiResource]` avec operations `GetCollection` + `Get` uniquement. - Ajouter l'attribut `#[ApiResource]` avec operations `GetCollection` + `Get` uniquement.
- Normalization context : groupe `permission:read` uniquement. - Normalization context : groupe `permission:read` uniquement.
@@ -89,7 +89,7 @@ Extrait attendu :
### Entite `Role` ### Entite `Role`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/Role.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/Role.php`
- Ajouter l'attribut `#[ApiResource]` avec operations `GetCollection`, `Get`, `Post`, `Patch`, `Delete`. - Ajouter l'attribut `#[ApiResource]` avec operations `GetCollection`, `Get`, `Post`, `Patch`, `Delete`.
- Normalization context : `role:read`. Denormalization context : `role:write`. - Normalization context : `role:read`. Denormalization context : `role:write`.
@@ -107,7 +107,7 @@ Extrait attendu :
### Entite `User` ### Entite `User`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/User.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/User.php`
- Ajouter dans la liste des operations `ApiResource` existantes une operation dediee : - Ajouter dans la liste des operations `ApiResource` existantes une operation dediee :

View File

@@ -39,50 +39,50 @@ A l'issue de ce ticket, l'application dispose d'un systeme d'autorisation applic
### Domaine - Securite ### Domaine - Securite
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Security/AdminHeadcountGuard.php` - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Security/AdminHeadcountGuard.php`
Service domaine encapsulant l'invariant "au moins un admin reste apres l'operation". Depend uniquement de `UserRepositoryInterface::countAdmins()`. Aucune dependance infrastructure, testable en isolation. Service domaine encapsulant l'invariant "au moins un admin reste apres l'operation". Depend uniquement de `UserRepositoryInterface::countAdmins()`. Aucune dependance infrastructure, testable en isolation.
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Exception/LastAdminProtectionException.php` - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Exception/LastAdminProtectionException.php`
Exception metier levee par le guard. Traduite en `BadRequestHttpException` (400) dans les processors. Exception metier levee par le guard. Traduite en `BadRequestHttpException` (400) dans les processors.
### Infrastructure - Security ### Infrastructure - Security
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Security/PermissionVoter.php` - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Security/PermissionVoter.php`
Voter Symfony etendant `Symfony\Component\Security\Core\Authorization\Voter\Voter`. Decouvert automatiquement par `autoconfigure: true`. Voter Symfony etendant `Symfony\Component\Security\Core\Authorization\Voter\Voter`. Decouvert automatiquement par `autoconfigure: true`.
### Infrastructure - Processors ### Infrastructure - Processors
- `/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserProcessor.php` - `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserProcessor.php`
Decorateur de `RemoveProcessor` cible sur `DELETE /api/users/{id}`. Appelle `AdminHeadcountGuard` avant de deleguer. Meme pattern qu'`UserRbacProcessor`/`RoleProcessor` : `final class`, `#[Autowire]` sur l'inner, `LogicException` fail-fast si le type entrant n'est pas `User`. Decorateur de `RemoveProcessor` cible sur `DELETE /api/users/{id}`. Appelle `AdminHeadcountGuard` avant de deleguer. Meme pattern qu'`UserRbacProcessor`/`RoleProcessor` : `final class`, `#[Autowire]` sur l'inner, `LogicException` fail-fast si le type entrant n'est pas `User`.
### Frontend - Composable ### Frontend - Composable
- `/home/matthieu/dev_malio/Starseed/frontend/shared/composables/usePermissions.ts` - `/home/matthieu/dev_malio/Coltura/frontend/shared/composables/usePermissions.ts`
Composable stateless qui lit `useAuthStore().user`. Pas de fetch propre, pas de reset (le cycle de vie est porte par l'auth store). Composable stateless qui lit `useAuthStore().user`. Pas de fetch propre, pas de reset (le cycle de vie est porte par l'auth store).
### Tests unitaires PHP ### Tests unitaires PHP
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Infrastructure/Security/PermissionVoterTest.php` - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Infrastructure/Security/PermissionVoterTest.php`
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Domain/Security/AdminHeadcountGuardTest.php` - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Domain/Security/AdminHeadcountGuardTest.php`
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserProcessorTest.php` - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserProcessorTest.php`
### Tests fonctionnels PHP ### Tests fonctionnels PHP
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Api/MeApiTest.php` (si absent — sinon extension) - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Api/MeApiTest.php` (si absent — sinon extension)
Couvre l'enrichissement du payload `/api/me`. Couvre l'enrichissement du payload `/api/me`.
- `/home/matthieu/dev_malio/Starseed/tests/Module/Core/Api/UserApiTest.php` (si absent — sinon extension) - `/home/matthieu/dev_malio/Coltura/tests/Module/Core/Api/UserApiTest.php` (si absent — sinon extension)
Couvre la garde "dernier admin global" sur `DELETE /api/users/{id}`. Couvre la garde "dernier admin global" sur `DELETE /api/users/{id}`.
### Tests frontend ### Tests frontend
- `/home/matthieu/dev_malio/Starseed/frontend/shared/composables/__tests__/usePermissions.test.ts` - `/home/matthieu/dev_malio/Coltura/frontend/shared/composables/__tests__/usePermissions.test.ts`
Vitest. Emplacement a adapter si le projet Nuxt a une autre convention (colocalise avec un fichier `.spec.ts`, ou repertoire `tests/`). A verifier au debut de la task frontend. Vitest. Emplacement a adapter si le projet Nuxt a une autre convention (colocalise avec un fichier `.spec.ts`, ou repertoire `tests/`). A verifier au debut de la task frontend.
## 4. Fichiers a modifier ## 4. Fichiers a modifier
### `CoreModule.php` ### `CoreModule.php`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/CoreModule.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/CoreModule.php`
Ajouter une cinquieme entree au catalogue : Ajouter une cinquieme entree au catalogue :
@@ -103,7 +103,7 @@ La commande `app:sync-permissions` creera automatiquement `core.roles.view` a la
### Entite `Permission` ### Entite `Permission`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/Permission.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/Permission.php`
Remplacer les 2 gardes placeholder : Remplacer les 2 gardes placeholder :
@@ -122,7 +122,7 @@ Supprimer les commentaires `// TODO ticket #345`.
### Entite `Role` ### Entite `Role`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/Role.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/Role.php`
Remplacer les 5 gardes placeholder : Remplacer les 5 gardes placeholder :
@@ -136,7 +136,7 @@ Supprimer les commentaires `// TODO ticket #345`.
### Entite `User` ### Entite `User`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Entity/User.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Entity/User.php`
Remplacer les 6 gardes `ROLE_ADMIN` restantes : Remplacer les 6 gardes `ROLE_ADMIN` restantes :
@@ -172,7 +172,7 @@ Supprimer tous les commentaires `// TODO ticket #345` rencontres.
### `UserRepositoryInterface` ### `UserRepositoryInterface`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Domain/Repository/UserRepositoryInterface.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Domain/Repository/UserRepositoryInterface.php`
Ajouter la methode : Ajouter la methode :
@@ -187,7 +187,7 @@ public function countAdmins(): int;
### `DoctrineUserRepository` ### `DoctrineUserRepository`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/Doctrine/DoctrineUserRepository.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/Doctrine/DoctrineUserRepository.php`
Implementer `countAdmins()` via un `QueryBuilder` simple : Implementer `countAdmins()` via un `QueryBuilder` simple :
@@ -204,7 +204,7 @@ public function countAdmins(): int
### `UserRbacProcessor` ### `UserRbacProcessor`
`/home/matthieu/dev_malio/Starseed/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessor.php` `/home/matthieu/dev_malio/Coltura/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessor.php`
Ajouter la dependance `AdminHeadcountGuard` et l'invoquer **apres** la garde auto-suicide existante, **avant** de deleguer au persist processor. Supprimer le `TODO ticket #345` du docblock. Ajouter la dependance `AdminHeadcountGuard` et l'invoquer **apres** la garde auto-suicide existante, **avant** de deleguer au persist processor. Supprimer le `TODO ticket #345` du docblock.

View File

@@ -10,14 +10,14 @@ Le resultat attendu est un socle de persistance activable par tenant via `config
### IN ### IN
- 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 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 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'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 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/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 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 `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. - 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/Starseed/config/modules.php` pour l'activer par defaut. - 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/Starseed/config/packages/doctrine.yaml` (inconditionnel, le mapping reste charge meme si le module est retire de `modules.php`). - 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/Starseed/config/services.yaml`. - Enregistrer l'alias service `SiteRepositoryInterface → DoctrineSiteRepository` dans `/home/m-tristan/workspace/Coltura/config/services.yaml`.
- Ajouter deux suites de tests PHPUnit : - Ajouter deux suites de tests PHPUnit :
- `SiteTest` (pure `TestCase`) pour le comportement de l'entite (constructeur, getters/setters, lifecycle `PreUpdate`). - `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. - `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 ### 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 `#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 Starseed (selecteur de site actif, persistance du choix cote front, consommation du flux issu du ticket 2). - Ticket `#03` : integration dans la navbar Coltura (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). - 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. - Gestion des soft-deletes sur `Site` : non introduite dans ce ticket.
- Rattachement historique ou audit trail des modifications : hors scope. - 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é ### Domaine — Entité
- `/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. - `/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.
### Domaine — Repository ### Domaine — Repository
- `/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). - `/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).
### Infrastructure — Doctrine ### Infrastructure — Doctrine
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Infrastructure/Doctrine/DoctrineSiteRepository.php` : implementation Doctrine de `SiteRepositoryInterface` basee sur `ServiceEntityRepository`. - `/home/m-tristan/workspace/Coltura/src/Module/Sites/Infrastructure/Doctrine/DoctrineSiteRepository.php` : implementation Doctrine de `SiteRepositoryInterface` basee sur `ServiceEntityRepository`.
### Infrastructure — Migration ### Infrastructure — Migration
- `/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). - `/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).
### Infrastructure — DataFixtures ### Infrastructure — DataFixtures
- `/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). - `/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).
### Module — Declaration ### Module — Declaration
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/SitesModule.php` : marker class du module avec `ID`, `LABEL`, `REQUIRED` et `permissions()`. Meme pattern que `CoreModule`. - `/home/m-tristan/workspace/Coltura/src/Module/Sites/SitesModule.php` : marker class du module avec `ID`, `LABEL`, `REQUIRED` et `permissions()`. Meme pattern que `CoreModule`.
### Tests ### Tests
- `/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/Coltura/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. - `/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.
## 4. Fichiers à modifier ## 4. Fichiers à modifier
- `/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/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/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/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/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. - `/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.
## 5. Schéma cible — mapping Doctrine ## 5. Schéma cible — mapping Doctrine
@@ -152,7 +152,7 @@ Sites:
## 6. Plan de migration Doctrine ## 6. Plan de migration Doctrine
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. 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.
### `up()` — ordre des instructions ### `up()` — ordre des instructions
@@ -289,7 +289,7 @@ Trois sites de demonstration, avec des couleurs distinctes suffisamment contrast
| Nom | Ville | CP | Couleur | Commentaire | | Nom | Ville | CP | Couleur | Commentaire |
|-----|-------|-----|---------|-------------| |-----|-------|-----|---------|-------------|
| Chatellerault | Chatellerault | 86100 | `#056CF2` | Couleur imposee par le ticket (bleu Starseed). | | Chatellerault | Chatellerault | 86100 | `#056CF2` | Couleur imposee par le ticket (bleu Coltura). |
| Saint-Jean | Saint-Jean-de-Sauves | 86330 | `#10B981` | Vert emeraude (contraste avec le bleu). | | Saint-Jean | Saint-Jean-de-Sauves | 86330 | `#10B981` | Vert emeraude (contraste avec le bleu). |
| Pommevic | Pommevic | 82400 | `#F59E0B` | Ambre (troisieme teinte nettement distincte). | | 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 ### Backend — Module Sites
- `/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/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/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/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/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/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/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). - `/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).
### Backend — Migration ### Backend — Migration
- `/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. - `/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.
### Backend — Tests API ### Backend — Tests API
- `/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/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/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/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/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/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/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/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/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. - `/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.
### Frontend — Module Sites (nouveau layer) ### Frontend — Module Sites (nouveau layer)
- `/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/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/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/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/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/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/Starseed/frontend/modules/sites/components/SiteDeleteModal.vue` : modale de confirmation suppression. Pattern aligne sur `RoleDeleteModal.vue`. - `/home/m-tristan/workspace/Coltura/frontend/modules/sites/components/SiteDeleteModal.vue` : modale de confirmation suppression. Pattern aligne sur `RoleDeleteModal.vue`.
### Frontend — Types partages ### Frontend — Types partages
- `/home/m-tristan/workspace/Starseed/frontend/shared/types/sites.ts` : types `Site`, `SiteInput`. Pattern identique a `frontend/shared/types/rbac.ts`. - `/home/m-tristan/workspace/Coltura/frontend/shared/types/sites.ts` : types `Site`, `SiteInput`. Pattern identique a `frontend/shared/types/rbac.ts`.
### Tests frontend (optionnels mais recommandes) ### Tests frontend (optionnels mais recommandes)
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/pages/admin/sites.spec.ts` : smoke test Vitest (rendu + clic bouton "Nouveau site" ouvre le drawer). - `/home/m-tristan/workspace/Coltura/frontend/modules/sites/pages/admin/sites.spec.ts` : smoke test Vitest (rendu + clic bouton "Nouveau site" ouvre le drawer).
## 4. Fichiers à modifier ## 4. Fichiers à modifier
### Backend — Module Core ### Backend — Module Core
- `/home/m-tristan/workspace/Starseed/src/Module/Core/Domain/Entity/User.php` : - `/home/m-tristan/workspace/Coltura/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 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`. - Ajouter `private ?Site $currentSite = null;` (M2O, `fetch: EAGER`, `onDelete: 'SET NULL'`), groupe `me:read`.
- Initialiser `$this->sites = new ArrayCollection();` dans le constructeur. - Initialiser `$this->sites = new ArrayCollection();` dans le constructeur.
- Ajouter les accesseurs `getSites()`, `addSite(Site)`, `removeSite(Site)`, `hasSite(Site)`, `getCurrentSite()`, `setCurrentSite(?Site)`. - 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). - **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/Starseed/src/Module/Core/Infrastructure/ApiPlatform/State/Processor/UserRbacProcessor.php` : - `/home/m-tristan/workspace/Coltura/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>`). - 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`. - 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). - Conserver toutes les gardes existantes (auto-suicide admin, dernier admin global).
- `/home/m-tristan/workspace/Starseed/src/Module/Core/Infrastructure/DataFixtures/AppFixtures.php` : - `/home/m-tristan/workspace/Coltura/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). - 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`. - 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`. - Positionner `currentSite` : `admin.currentSite = Chatellerault`, `alice.currentSite = Chatellerault`, `bob.currentSite = Saint-Jean`.
### Backend — Module Sites ### Backend — Module Sites
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/Domain/Entity/Site.php` : - `/home/m-tristan/workspace/Coltura/src/Module/Sites/Domain/Entity/Site.php` :
- Ajouter les attributs `#[ApiResource]` + operations (cf. section 5 Schema). - 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 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). - 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. - Initialiser `$this->users = new ArrayCollection();` dans le constructeur.
- Ajouter les accesseurs `getUsers()` pour les besoins metier (count / cascade manuel si besoin). - Ajouter les accesseurs `getUsers()` pour les besoins metier (count / cascade manuel si besoin).
- `/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). - `/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).
### Backend — Configuration ### Backend — Configuration
- `/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` : - `/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` :
```php ```php
[ [
'label' => 'sidebar.core.sites', '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', 'permission' => 'sites.view',
], ],
``` ```
- `/home/m-tristan/workspace/Starseed/config/services.yaml` : aucun changement requis. `CurrentSiteProcessor`, `SiteNotAuthorizedExceptionListener` sont autoconfigures. - `/home/m-tristan/workspace/Coltura/config/services.yaml` : aucun changement requis. `CurrentSiteProcessor`, `SiteNotAuthorizedExceptionListener` sont autoconfigures.
### Frontend ### Frontend
- `/home/m-tristan/workspace/Starseed/frontend/modules/core/components/UserRbacDrawer.vue` : - `/home/m-tristan/workspace/Coltura/frontend/modules/core/components/UserRbacDrawer.vue` :
- Charger `GET /api/sites?itemsPerPage=999` a l'ouverture du drawer (parallelement aux roles et permissions deja charges). - 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`). - 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). - 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). - Auto-refresh de l'auth store apres save si `isSelfEdit` (deja present, conserver).
- `/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/Coltura/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/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/Starseed/frontend/i18n/locales/fr.json` : cles - `/home/m-tristan/workspace/Coltura/frontend/i18n/locales/fr.json` : cles
- `sidebar.core.sites` = "Sites". - `sidebar.core.sites` = "Sites".
- `admin.sites.title`, `admin.sites.newSite`, `admin.sites.editSite`, `admin.sites.createSite`, `admin.sites.noSites`. - `admin.sites.title`, `admin.sites.newSite`, `admin.sites.editSite`, `admin.sites.createSite`, `admin.sites.noSites`.
- `admin.sites.table.{name, city, postalCode, color, fullAddress}`. - `admin.sites.table.{name, city, postalCode, color, fullAddress}`.
@@ -228,7 +228,7 @@ final class CurrentSiteResource
## 6. Plan de migration Doctrine ## 6. Plan de migration Doctrine
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`). 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`).
### `up()` — ordre des instructions ### `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) ### Frontend — Module Sites (layer deja cree au ticket 2)
- `/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/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/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. - `/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.
### Frontend — Shared ### Frontend — Shared
- `/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/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/Starseed/frontend/shared/utils/color.ts` : fonctions utilitaires de couleur, au minimum : - `/home/m-tristan/workspace/Coltura/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`. - `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. - `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). - `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 ### Frontend — Tests
- `/home/m-tristan/workspace/Starseed/frontend/modules/sites/composables/__tests__/useCurrentSite.spec.ts` : Vitest. Tests : - `/home/m-tristan/workspace/Coltura/frontend/modules/sites/composables/__tests__/useCurrentSite.spec.ts` : Vitest. Tests :
- `switchSite` met a jour l'etat localement avant la requete (optimistic). - `switchSite` met a jour l'etat localement avant la requete (optimistic).
- Si la requete reussit, l'etat reste aligne. - Si la requete reussit, l'etat reste aligne.
- Si la requete echoue, l'etat rollback a l'ancien `currentSite`. - Si la requete echoue, l'etat rollback a l'ancien `currentSite`.
- `resetCurrentSite` vide l'etat. - `resetCurrentSite` vide l'etat.
- `/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/Coltura/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/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/Starseed/frontend/modules/sites/components/__tests__/SiteSelector.spec.ts` : smoke test Vitest. - `/home/m-tristan/workspace/Coltura/frontend/modules/sites/components/__tests__/SiteSelector.spec.ts` : smoke test Vitest.
## 4. Fichiers à modifier ## 4. Fichiers à modifier
- `/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/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/Starseed/frontend/shared/types/user-data.ts` : ajouter les champs - `/home/m-tristan/workspace/Coltura/frontend/shared/types/user-data.ts` : ajouter les champs
```ts ```ts
sites: Site[] sites: Site[]
currentSite: Site | null 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`. 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/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/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/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/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/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/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/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/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/Starseed/frontend/i18n/locales/fr.json` : ajouter les cles - `/home/m-tristan/workspace/Coltura/frontend/i18n/locales/fr.json` : ajouter les cles
```json ```json
"sites": { "sites": {
"selector": { "selector": {

View File

@@ -34,50 +34,50 @@ Le ticket livre aussi une documentation developpeur (`docs/modules/site-aware.md
### Shared — Contrat ### Shared — Contrat
- `/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. - `/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.
### Module Sites — Application ### Module Sites — Application
- `/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. - `/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.
### Module Sites — Infrastructure ### Module Sites — Infrastructure
- `/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/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/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`. - `/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`.
### Documentation ### Documentation
- `/home/m-tristan/workspace/Starseed/docs/modules/site-aware.md` : guide developpeur (cf. contenu section 10). - `/home/m-tristan/workspace/Coltura/docs/modules/site-aware.md` : guide developpeur (cf. contenu section 10).
### Tests ### Tests
- `/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 : - `/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 :
- Le filtre s'applique sur une resource `SiteAware` quand le provider retourne un site. - 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 `SiteAware` mais provider null.
- Le filtre est no-op si resource non `SiteAware`. - Le filtre est no-op si resource non `SiteAware`.
- Le filtre est no-op si user a `sites.bypass_scope`. - Le filtre est no-op si user a `sites.bypass_scope`.
- `totalItems` Hydra reflete bien le filtrage. - `totalItems` Hydra reflete bien le filtrage.
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Infrastructure/ApiPlatform/State/Processor/SiteAwareInjectionProcessorTest.php` : tests unitaires (`TestCase` pur) avec mocks. Verifie : - `/home/m-tristan/workspace/Coltura/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 sans site → injection du site courant.
- `$data` SiteAware avec site deja positionne → pas d'overwrite. - `$data` SiteAware avec site deja positionne → pas d'overwrite.
- `$data` non-SiteAware → delegation directe sans modification. - `$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". - Provider retourne null (module off ou user sans site) ET `$data` SiteAware sans site → BadRequestHttpException (400) "aucun site selectionne".
- `/home/m-tristan/workspace/Starseed/tests/Module/Sites/Application/Service/CurrentSiteProviderTest.php` : tests unitaires `TestCase`. Couvre : - `/home/m-tristan/workspace/Coltura/tests/Module/Sites/Application/Service/CurrentSiteProviderTest.php` : tests unitaires `TestCase`. Couvre :
- User authentifie avec currentSite → retourne le Site. - User authentifie avec currentSite → retourne le Site.
- User authentifie sans currentSite → null. - User authentifie sans currentSite → null.
- Pas d'user → null. - Pas d'user → null.
- Module desactive dans config/modules.php de test → null meme si user.currentSite existe. - Module desactive dans config/modules.php de test → null meme si user.currentSite existe.
- `/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. - `/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.
## 4. Fichiers à modifier ## 4. Fichiers à modifier
- `/home/m-tristan/workspace/Starseed/src/Module/Sites/SitesModule.php` : ajouter la permission `sites.bypass_scope` dans `permissions()` : - `/home/m-tristan/workspace/Coltura/src/Module/Sites/SitesModule.php` : ajouter la permission `sites.bypass_scope` dans `permissions()` :
```php ```php
['code' => 'sites.bypass_scope', 'label' => 'Voir les donnees site-scoped de tous les sites (bypass du filtrage)'], ['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. **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/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/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/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). - `/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).
## 5. Contrat `SiteAwareInterface` ## 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 ### Risque 8 — Doc developpeur en francais vs anglais
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. 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.
## 12. Plan de tests ## 12. Plan de tests

View File

@@ -62,6 +62,6 @@ watch(() => route.path, () => {
}) })
useHead({ useHead({
titleTemplate: (title) => title || 'Starseed', titleTemplate: (title) => title || 'Coltura',
}) })
</script> </script>

View File

@@ -1 +1 @@
/* Starseed - Custom styles */ /* Coltura - Custom styles */

View File

@@ -14,7 +14,7 @@ export default await nuxt(
}, },
}, },
{ {
name: 'starseed/custom-overrides', name: 'coltura/custom-overrides',
rules: { rules: {
// Indentation 4 espaces (convention CLAUDE.md) // Indentation 4 espaces (convention CLAUDE.md)
'vue/html-indent': ['error', 4], 'vue/html-indent': ['error', 4],

View File

@@ -36,7 +36,7 @@
}, },
"dashboard": { "dashboard": {
"title": "Tableau de bord", "title": "Tableau de bord",
"welcome": "Bienvenue sur Starseed" "welcome": "Bienvenue sur Coltura"
}, },
"commercial": { "commercial": {
"title": "Commercial", "title": "Commercial",

View File

@@ -9,8 +9,7 @@
<!-- Filtres --> <!-- Filtres -->
<section class="mt-4 rounded border border-gray-200 bg-white p-4"> <section class="mt-4 rounded border border-gray-200 bg-white p-4">
<!-- Labels uniformes au-dessus : les composants Malio sont utilises sans <!-- Labels uniformes au-dessus : les composants Malio sont utilises sans
leur `label` flottant interne pour ne pas mixer deux patterns de label. leur `label` flottant interne pour ne pas mixer deux patterns de label. -->
A revoir une fois le composant calendar Malio développé -->
<div class="grid grid-cols-1 items-start gap-3 md:grid-cols-5"> <div class="grid grid-cols-1 items-start gap-3 md:grid-cols-5">
<!-- TODO(malio-ui): remplacer par un composant Malio quand la lib <!-- TODO(malio-ui): remplacer par un composant Malio quand la lib
exposera un datetime picker. Cf. exception documentee dans exposera un datetime picker. Cf. exception documentee dans
@@ -60,20 +59,26 @@
v-model="performedByInput" v-model="performedByInput"
icon-name="mdi:account-search" icon-name="mdi:account-search"
input-class="text-sm" input-class="text-sm"
group-class="h-10"
/> />
</div> </div>
<!-- TODO(malio-ui): remplacer par MalioSelect quand la lib
supportera de maniere fiable des options a valeur string
(cf. note Lesstime CLAUDE.md). Exception documentee dans
CLAUDE.md (section "Composants formulaires"). -->
<div> <div>
<label class="mb-1 block text-xs font-medium text-gray-600"> <label class="mb-1 block text-xs font-medium text-gray-600">
{{ t('audit.filters.action') }} {{ t('audit.filters.action') }}
</label> </label>
<div class="[&>div>div]:!mt-0"> <select
<MalioSelect
v-model="actionValue" v-model="actionValue"
:options="actionOptions" class="h-[40px] w-full rounded-md border border-m-muted bg-white px-3 text-sm outline-none focus-visible:border-2 focus-visible:border-m-primary"
text-field="text-sm" >
text-value="text-sm" <option value="">{{ t('audit.filters.all_actions') }}</option>
/> <option v-for="opt in actionOptions" :key="opt.value" :value="opt.value">
</div> {{ opt.label }}
</option>
</select>
</div> </div>
</div> </div>
@@ -199,14 +204,11 @@ const entityTypeOptions = computed(() =>
// pas binder directement un `string | undefined` reactive. // pas binder directement un `string | undefined` reactive.
const performedByInput = ref<string>('') const performedByInput = ref<string>('')
// Action : '' = "toutes les actions". On declare l'option dans `actionOptions` // Action : MalioSelect ne gere pas fiablement des options a valeur string (cf.
// plutot que via `emptyOptionLabel` (qui n'inclut pas l'option vide dans // note Lesstime CLAUDE.md). On utilise un `<select>` natif stylise comme les
// `props.options`, donc `selectedLabel` reste vide). On evite aussi `value: null` // inputs dates pour garder un look coherent. '' = "toutes les actions".
// car MalioSelect grise visuellement les options dont la valeur est `null`
// (Select.vue:137) — on utilise donc une chaine vide comme sentinelle.
const actionValue = ref<string>('') const actionValue = ref<string>('')
const actionOptions = [ const actionOptions = [
{ value: '', label: t('audit.filters.all_actions') },
{ value: 'create', label: t('audit.action.create') }, { value: 'create', label: t('audit.action.create') },
{ value: 'update', label: t('audit.action.update') }, { value: 'update', label: t('audit.action.update') },
{ value: 'delete', label: t('audit.action.delete') }, { value: 'delete', label: t('audit.action.delete') },
@@ -376,7 +378,7 @@ watch(selectedEntityTypes, values => {
loadEntries() loadEntries()
}) })
// Sync MalioSelect action -> filters.action. // Sync select action natif -> filters.action.
watch(actionValue, value => { watch(actionValue, value => {
if (watchersSuspended) return if (watchersSuspended) return
filters.action = value === '' ? undefined : value filters.action = value === '' ? undefined : value

View File

@@ -8,7 +8,7 @@
<MalioButton <MalioButton
v-if="can('core.roles.manage')" v-if="can('core.roles.manage')"
:label="t('admin.roles.newRole')" :label="t('admin.roles.newRole')"
icon-name="mdi:add-bold" icon-name="mdi:plus"
icon-position="left" icon-position="left"
@click="openCreateDrawer" @click="openCreateDrawer"
/> />

View File

@@ -27,8 +27,8 @@
<MalioButton <MalioButton
label="Se connecter" label="Se connecter"
button-class="w-full" button-class="w-full"
type="submit"
:disabled="isSubmitting" :disabled="isSubmitting"
@click="handleSubmit"
/> />
<p class="font-bold">v{{ version }}</p> <p class="font-bold">v{{ version }}</p>
</form> </form>

View File

@@ -78,7 +78,7 @@ async function onChange(site: { id: string; name: string; color: string }): Prom
// intentionnellement supprimee pour garantir qu'un clic sur le tile // intentionnellement supprimee pour garantir qu'un clic sur le tile
// "actif selon cet onglet" envoie quand meme le PATCH et re-synchronise // "actif selon cet onglet" envoie quand meme le PATCH et re-synchronise
// l'etat. Amelioration future : ecouter l'evenement `storage` sur la // l'etat. Amelioration future : ecouter l'evenement `storage` sur la
// cle `starseed:site-switch` pour mettre a jour les onglets inactifs // cle `coltura:site-switch` pour mettre a jour les onglets inactifs
// sans clic via auth.fetchUser() / auth.refreshUser(). // sans clic via auth.fetchUser() / auth.refreshUser().
try { try {

View File

@@ -8,7 +8,7 @@
<MalioButton <MalioButton
v-if="can('sites.manage')" v-if="can('sites.manage')"
:label="t('admin.sites.newSite')" :label="t('admin.sites.newSite')"
icon-name="mdi:add-bold" icon-name="mdi:plus"
icon-position="left" icon-position="left"
@click="openCreateDrawer" @click="openCreateDrawer"
/> />

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{ {
"name": "starseed-frontend", "name": "coltura-frontend",
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -17,7 +17,7 @@
"test:e2e:ui": "playwright test --ui" "test:e2e:ui": "playwright test --ui"
}, },
"dependencies": { "dependencies": {
"@malio/layer-ui": "^1.5.0", "@malio/layer-ui": "^1.4.2",
"@nuxt/icon": "^2.2.1", "@nuxt/icon": "^2.2.1",
"@nuxtjs/i18n": "^10.2.3", "@nuxtjs/i18n": "^10.2.3",
"@nuxtjs/tailwindcss": "^6.14.0", "@nuxtjs/tailwindcss": "^6.14.0",

View File

@@ -1,7 +1,7 @@
import { defineConfig, devices } from '@playwright/test' import { defineConfig, devices } from '@playwright/test'
/** /**
* Config Playwright pour les tests E2E de Starseed. * Config Playwright pour les tests E2E de Coltura.
* *
* Pre-requis avant de lancer : * Pre-requis avant de lancer :
* 1. Les containers Docker tournent (`make start`) * 1. Les containers Docker tournent (`make start`)

View File

@@ -1,7 +1,7 @@
import type {Config} from 'tailwindcss' import type {Config} from 'tailwindcss'
/** /**
* Config Tailwind du projet Starseed. * Config Tailwind du projet Coltura.
* *
* @nuxtjs/tailwindcss merge automatiquement les configs de chaque layer * @nuxtjs/tailwindcss merge automatiquement les configs de chaque layer
* Nuxt declare dans `nuxt.config.ts:extends`. Le layer `@malio/layer-ui` * Nuxt declare dans `nuxt.config.ts:extends`. Le layer `@malio/layer-ui`
@@ -11,7 +11,7 @@ import type {Config} from 'tailwindcss'
* success,btn-*,site-blue,site-yellow,site-green} * success,btn-*,site-blue,site-yellow,site-green}
* - fontFamily.sans (Helvetica Neue) * - fontFamily.sans (Helvetica Neue)
* *
* Cette config locale ne redeclare QUE ce qui est specifique a Starseed * Cette config locale ne redeclare QUE ce qui est specifique a Coltura
* ou absent de la config Malio — evite la duplication et les derives. * ou absent de la config Malio — evite la duplication et les derives.
*/ */
export default <Partial<Config>>{ export default <Partial<Config>>{
@@ -19,7 +19,7 @@ export default <Partial<Config>>{
theme: { theme: {
extend: { extend: {
colors: { colors: {
// Couleurs applicatives Starseed (hors namespace `m` reserve // Couleurs applicatives Coltura (hors namespace `m` reserve
// au design system Malio partage). // au design system Malio partage).
primary: { primary: {
500: '#222783', 500: '#222783',

View File

@@ -1,8 +1,8 @@
DOCKER_APP_NAME=starseed DOCKER_APP_NAME=coltura
DOCKER_PHP_VERSION=8.4.6 DOCKER_PHP_VERSION=8.4.6
DOCKER_NODE_VERSION=24.12.0 DOCKER_NODE_VERSION=24.12.0
APP_USER=www-data APP_USER=www-data
POSTGRES_DB=starseed POSTGRES_DB=coltura
POSTGRES_USER=root POSTGRES_USER=root
POSTGRES_PASSWORD=root POSTGRES_PASSWORD=root
POSTGRES_PORT=5437 POSTGRES_PORT=5437

View File

@@ -2,12 +2,11 @@ APP_ENV=prod
APP_DEBUG=0 APP_DEBUG=0
APP_SECRET=CHANGE_ME_IN_PRODUCTION APP_SECRET=CHANGE_ME_IN_PRODUCTION
DATABASE_URL="postgresql://starseed:CHANGE_ME@host.docker.internal:5432/starseed_prod?serverVersion=16&charset=utf8" DATABASE_URL="postgresql://coltura:CHANGE_ME@host.docker.internal:5432/coltura?serverVersion=16&charset=utf8"
JWT_PASSPHRASE=CHANGE_ME_IN_PRODUCTION JWT_PASSPHRASE=CHANGE_ME_IN_PRODUCTION
# HTTP en reseau local => cookie non secure JWT_COOKIE_SECURE=1
JWT_COOKIE_SECURE=0
JWT_TOKEN_TTL=86400 JWT_TOKEN_TTL=86400
JWT_COOKIE_TTL=86400 JWT_COOKIE_TTL=86400
CORS_ALLOW_ORIGIN='^http://starseed\.malio-dev\.fr$' CORS_ALLOW_ORIGIN='^https://coltura\.malio-dev\.fr$'

View File

@@ -60,7 +60,7 @@ RUN rm -f /etc/nginx/sites-enabled/default
# Configs # Configs
COPY infra/prod/supervisord.conf /etc/supervisor/conf.d/app.conf COPY infra/prod/supervisord.conf /etc/supervisor/conf.d/app.conf
COPY infra/prod/nginx.conf /etc/nginx/sites-enabled/starseed.conf COPY infra/prod/nginx.conf /etc/nginx/sites-enabled/coltura.conf
# Backend from stage 1 # Backend from stage 1
COPY --from=backend-build /app /var/www/html COPY --from=backend-build /app /var/www/html

View File

@@ -4,9 +4,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
TAG="${1:-latest}" TAG="${1:-latest}"
export STARSEED_IMAGE_TAG="$TAG" export COLTURA_IMAGE_TAG="$TAG"
echo "==> Deploying starseed:${TAG}..." echo "==> Deploying coltura:${TAG}..."
echo "==> Enabling maintenance mode..." echo "==> Enabling maintenance mode..."
touch maintenance.on touch maintenance.on

View File

@@ -1,16 +1,16 @@
services: services:
app: app:
image: gitea.malio.fr/malio-dev/starseed:${STARSEED_IMAGE_TAG:-latest} image: gitea.malio.fr/malio-dev/coltura:${COLTURA_IMAGE_TAG:-latest}
container_name: starseed-app container_name: coltura-app
env_file: .env env_file: .env
ports: ports:
- "8086:80" - "8086:80"
volumes: volumes:
- ./config/jwt:/var/www/html/config/jwt:ro - ./config/jwt:/var/www/html/config/jwt:ro
- starseed_logs:/var/www/html/var/log - coltura_logs:/var/www/html/var/log
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
restart: unless-stopped restart: unless-stopped
volumes: volumes:
starseed_logs: coltura_logs:

View File

@@ -1,12 +1,12 @@
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
server_name starseed.malio-dev.fr; server_name coltura.malio-dev.fr;
root /var/www/starseed/public; root /var/www/coltura/public;
# Maintenance mode # Maintenance mode
if (-f /var/www/starseed/maintenance.on) { if (-f /var/www/coltura/maintenance.on) {
return 503; return 503;
} }

View File

@@ -9,12 +9,6 @@ ENV_FILE := $(if $(wildcard $(ENV_LOCAL)),$(ENV_LOCAL),$(ENV_DEFAULT))
include $(ENV_DEFAULT) include $(ENV_DEFAULT)
-include $(ENV_LOCAL) -include $(ENV_LOCAL)
# Export du UID/GID host pour que docker compose les voie dans toutes les targets
# (sinon le build du Dockerfile dev fail sur `usermod -u ${CURRENT_UID}` quand l'image
# n'est pas en cache, ex: apres renommage du compose project).
export CURRENT_UID := $(shell id -u)
export CURRENT_GID := $(shell id -g)
PHP_CONTAINER = php-$(DOCKER_APP_NAME)-fpm PHP_CONTAINER = php-$(DOCKER_APP_NAME)-fpm
SYMFONY_CONSOLE = $(EXEC_PHP) php bin/console SYMFONY_CONSOLE = $(EXEC_PHP) php bin/console
@@ -32,7 +26,7 @@ FILES =
# Affiche l'aide — cible par defaut (make ou make help) # Affiche l'aide — cible par defaut (make ou make help)
help: help:
@printf "\n \033[1mStarseed — Commandes make\033[0m\n\n" @printf "\n \033[1mColtura — Commandes make\033[0m\n\n"
@printf " \033[1;33mContainers\033[0m\n" @printf " \033[1;33mContainers\033[0m\n"
@printf " \033[36m%-28s\033[0m %s\n" "start" "Demarrer les containers Docker" @printf " \033[36m%-28s\033[0m %s\n" "start" "Demarrer les containers Docker"
@printf " \033[36m%-28s\033[0m %s\n" "stop" "Arreter les containers" @printf " \033[36m%-28s\033[0m %s\n" "stop" "Arreter les containers"

View File

@@ -11,7 +11,7 @@ use Doctrine\Migrations\AbstractMigration;
* Module Sites - Ticket 1/4 : brique fondatrice de donnees. * Module Sites - Ticket 1/4 : brique fondatrice de donnees.
* *
* Cree la table `site` qui porte les etablissements physiques de l'instance * Cree la table `site` qui porte les etablissements physiques de l'instance
* Starseed. La table est creee inconditionnellement : meme si SitesModule est * Coltura. La table est creee inconditionnellement : meme si SitesModule est
* desactive dans `config/modules.php`, la structure DB existe (pas de * desactive dans `config/modules.php`, la structure DB existe (pas de
* dependance dure depuis Core, mais pas de coin d'ombre schema non plus). * dependance dure depuis Core, mais pas de coin d'ombre schema non plus).
* *

View File

@@ -24,7 +24,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
/** /**
* Site physique (usine / etablissement) appartenant a l'instance Starseed. * Site physique (usine / etablissement) appartenant a l'instance Coltura.
* *
* Adresse decomposee en champs structures (rue, complement, CP, ville) pour * Adresse decomposee en champs structures (rue, complement, CP, ville) pour
* permettre des recherches/tris fins ulterieurs et eviter les divergences * permettre des recherches/tris fins ulterieurs et eviter les divergences

View File

@@ -36,7 +36,7 @@ class SitesFixtures extends Fixture
public function load(ObjectManager $manager): void public function load(ObjectManager $manager): void
{ {
// Chatellerault : bleu Starseed. // Chatellerault : bleu Coltura.
$this->ensureSite( $this->ensureSite(
$manager, $manager,
name: 'Chatellerault', name: 'Chatellerault',