Commit Graph

111 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
gitea-actions
dce189d982 chore: bump version to v0.1.33
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 9s
v0.1.33
2026-04-20 17:56:54 +00:00
140dca9061 fix : resolve var/cache permission issue in Docker
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Create var/cache and var/log directories in Dockerfile and ensure
correct ownership in Makefile before running composer install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 19:56:30 +02:00
gitea-actions
93f47e9111 chore: bump version to v0.1.32
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 8s
v0.1.32
2026-04-20 15:32:05 +00:00
6cf5ef4cfc Module sites (#8)
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [x] Pas de régression
- [x] TU/TI/TF rédigée
- [x] TU/TI/TF OK
- [ ] CHANGELOG modifié

Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #8
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-04-20 15:31:58 +00:00
gitea-actions
6b4868b261 chore: bump version to v0.1.31
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 58s
v0.1.31
2026-04-17 12:34:44 +00:00
e8c2789435 RBAC - Système complet de permissions (Backend + Frontend) (#7)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
## Résumé

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

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

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

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

## Tickets Lesstime

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

## Test plan

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

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

Co-authored-by: Matthieu <mtholot19@gmail.com>
Co-authored-by: tristan <tristan@yuno.malio.fr>
Reviewed-on: #7
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-04-17 12:34:38 +00:00
gitea-actions
b59d0f8a44 chore: bump version to v0.1.29
Some checks failed
Build & Push Docker Image / build (push) Failing after 16s
Auto Tag Develop / tag (push) Successful in 5s
v0.1.29
2026-04-14 13:12:49 +00:00
Matthieu
5cb8cff4ce Merge branch 'feature/ERP-7-mise-en-place-du-modular-monolith' into develop
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
# Conflicts:
#	docker-compose.yml
2026-04-14 15:11:59 +02:00
gitea-actions
c62f054da1 chore: bump version to v0.1.28
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 53s
v0.1.28
2026-04-14 13:07:45 +00:00
Matthieu
168dad4657 feat(infra) : add logs volume to prod docker-compose
Persist var/log/ via named volume coltura_logs so logs survive
container restarts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:28:09 +02:00
Matthieu
68bdb6ff72 docs : add code review report for PR #1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:16:33 +02:00
Matthieu
7045debc66 feat : add ESLint linter to frontend with pre-commit hook
Add ESLint with @nuxt/eslint-config enforcing 4-space indentation.
Add make nuxt-lint and nuxt-lint-fix targets.
Add ESLint check to pre-commit hook (lint only, no auto-fix).
Fix auth.vue indentation from 2 to 4 spaces.

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