Aller au contenu principal

Audit des consommateurs de l'API des alertes mairie

MissionAudit des consommateurs de l'API des alertes mairie
Rattaché àENG-001.5 — Extraction de Municipal Management §8.1, §13.A
TypeAudit uniquement — aucune implémentation, aucune décision d'architecture
Dépôts inspectésdmv-workspace, dmv-public, dmv-backoffice, api (dépôt dmv_api)
StatutAudit produit

1. Objectif

Déterminer avec certitude laquelle des deux API de gestion des alertes mairie — l'API Actor (/api/v1/acteurs/{acteurId}/alertes) ou l'API Mairie (/api/v1/mairie/...alertes...) — est réellement utilisée en production aujourd'hui, par quels consommateurs, pour quels usages (lecture, écriture), afin de permettre une décision ultérieure (non prise ici) sur l'API canonique à conserver.

Ce document ne tranche rien. Il répond exclusivement à la question factuelle : qui appelle quoi, aujourd'hui, dans le code réel.


2. Cartographie API Actor

Module backendActor
ContrôleurActor/Controllers/ActeurModulesController.php
Modèle utiliséMairie\Models\MairieAlerte (import direct, hors module — c'est la violation V-Actor-Mairie déjà documentée par ENG-001-5-municipal-management.md §8.1)
Service dédiéAucun — logique inline dans le contrôleur

Routes (Actor/Routes/api.php)

MéthodeRouteActionMiddleware
GET/acteurs/{acteurId}/alertesindexAlertes()identify.app (public, aucune auth)
POST/acteurs/{acteurId}/alertesstoreAlerte()identify.app + auth:sanctum
PATCH/acteurs/{acteurId}/alertes/{alerteId}updateAlerte()identify.app + auth:sanctum
PATCH/acteurs/{acteurId}/alertes/{alerteId}/desactiverdesactiverAlerte()identify.app + auth:sanctum

Aucune route de suppression physique — seule la désactivation logique existe (is_active=false).

Autorisation

  • Écriture : $this->authorize('update', $acteur)ActeurPolicy::update()ActorAccessService::hasPermission($user, $acteurId, 'manage_actor') (Actor/Policies/ActeurPolicy.php:25-28) — permission générique de gestion de l'acteur.
  • Restriction de type : findMairieActeurOrFail() (ActeurModulesController.php:19-28) vérifie kind === 'mairie', 403 sinon.
  • Ne vérifie ni la permission manage_alertes, ni l'activation du module alertes pour l'acteur.

Validation (storeAlerte, ActeurModulesController.php:241-247)

'titre' => ['required', 'string', 'max:255'],
'contenu' => ['nullable', 'string'],
'niveau' => ['required', 'string', 'in:info,warning,danger'],
'is_active' => ['sometimes', 'boolean'],
'expires_at' => ['nullable', 'date'],

Réponse

Retourne le modèle Eloquent MairieAlerte sérialisé brut (pas de DTO) : tous les attributs de colonne, y compris created_at et updated_at (timestamps Eloquent standard).


3. Cartographie API Mairie

Module backendMairie
ContrôleurMairie/Controllers/MairieAlerteController.php
ServiceMairie/Services/MairieWriteService.php (écriture), MairieReadService.php (lecture)
DTOMairie/DTOs/MairieAlerteDTO.php

Routes (Mairie/Routes/api.php)

MéthodeRouteActionMiddleware
GET/mairie/communes/{communeId}/alertesindex()identify.app (public, aucune auth)
POST/mairie/acteurs/{acteurId}/alertesstore()identify.app + auth:sanctum + mairie.access
PATCH/mairie/alertes/{alerteId}update()idem
DELETE/mairie/alertes/{alerteId}/desactiverdesactiver()idem

Contrairement à l'API Actor, la lecture publique se fait par commune, pas par acteur.

Autorisation (mairie.accessEnsureMairieAccess)

  • Vérifie acteur.kind === 'mairie' (EnsureMairieAccess.php:53-56).
  • ActorAccessService::canAccess() (ownership/collaborateur).
  • Permission spécifique manage_alertes (EnsureMairieAccess::resolveRequiredCapability(), route /alertes['manage_alertes', 'alertes']).
  • Module alertes activé pour l'acteur (hasModule($acteurId, 'alertes')).

Validation (CreateAlerteRequest.php, UpdateAlerteRequest.php)

Création :

'titre' => ['required', 'string', 'max:255'],
'contenu' => ['required', 'string'], // requis, contrairement à Actor
'niveau' => ['required', 'in:info,warning,danger'],
'expires_at' => ['nullable', 'date', 'after:now'], // doit être future, contrainte absente côté Actor

is_active n'est pas dans les règles de création — même envoyé, il est ignoré (filtré par FormRequest::validated()), puis MairieWriteService::createAlerte() force is_active = true inconditionnellement (MairieWriteService.php:47).

Mise à jour :

'titre' => ['sometimes', 'string', 'max:255'],
'contenu' => ['sometimes', 'string'],
'niveau' => ['sometimes', 'in:info,warning,danger'],
'expires_at' => ['nullable', 'date'],
'is_active' => ['sometimes', 'boolean'],

Réponse

MairieAlerteDTO::toArray() : id, commune_id, acteur_id, titre, contenu, niveau, is_active, expires_at, created_at — ensemble fixe, dates ISO8601, pas de updated_at.


4. Consommateurs frontend

Recherche exhaustive dans les trois dépôts (grep récursif sur les patterns d'URL des deux API, sur *.ts/*.tsx/*.js/*.jsx, répertoires node_modules, out, .next, dist, build exclus ; recherche complémentaire sur fichiers de test et de mock).

dmv-workspace

Fichier:ligneFonctionRoute appeléeÉcranUsage
services/acteur/acteur-modules.service.ts:123-127getActeurAlertes()GET /acteurs/{acteurId}/alertesPage « Alertes » de l'espace mairieLecture, actif
services/acteur/acteur-modules.service.ts:130-139createActeurAlerte()POST /acteurs/{acteurId}/alertesidemCréation, actif
services/acteur/acteur-modules.service.ts:141-151updateActeurAlerte()PATCH /acteurs/{acteurId}/alertes/{alerteId}idemModification, actif
services/acteur/acteur-modules.service.ts:153-158desactiverActeurAlerte()PATCH /acteurs/{acteurId}/alertes/{alerteId}/desactiveridemDésactivation, actif
app/(workspace)/acteur/[slug]/alertes/page.tsxPage complète (formulaire, liste, dialogues)consomme les 4 fonctions ci-dessusÉcran de gestion des alertesActif, seul écran de gestion trouvé

Aucun import ni appel vers /mairie/acteurs/.../alertes, /mairie/alertes/..., ou /mairie/communes/.../alertes trouvé dans dmv-workspace, malgré la présence d'un dossier services/mairie/ dédié à d'autres ressources (voir §4 note ci-dessous). Recherche confirmée par deux passes indépendantes (recherche par motif de route, puis recherche du terme alerte dans tout l'arbre app/).

Note — autres ressources Mairie utilisées par Workspace, hors périmètre alertes mais pertinentes pour §10 : services/mairie/commune.ts, elus.ts, collectes.ts, infos.ts existent et sont bien consommés (app/(workspace)/acteur/[slug]/{commune,elus,collectes}/page.tsx) pour GET/PATCH /mairie/communes/{communeId} et le CRUD élus/collectes/infos. Aucun services/mairie/services.ts n'existe, et aucun appel à /mairie/services/... ou /mairie/communes/{id}/services... n'a été trouvé nulle part dans les trois dépôts — la gestion des mairie_services (services municipaux) semble sans consommateur frontend actif dans le périmètre audité.

dmv-public

Fichier:ligneRoute appeléeÉcranUsage
app/[commune]/acteur/[slug]/ActorsClient.tsx:283GET /acteurs/{actor.id}/alertes (via apiGetPublic)Fiche publique d'un acteur mairie, section alertes activesLecture, actif
app/components/wall/hooks/useWallUserSignals.ts:87GET /acteurs/{acteur.id}/alertesSignaux utilisateur du Mur (Wall)Lecture, actif
app/components/wall/hooks/useWallUserSignals.ts:133GET /acteurs/{id}/alertesidem, variante (liste de mairies suivies)Lecture, actif

app/components/mon-espace/MonEspaceTodaySummary.tsx:228 affiche un libellé « Alertes mairie » mais consomme les données produites par useWallUserSignals.ts (ci-dessus), pas d'appel HTTP propre.

Aucun appel vers /mairie/communes/{communeId}/alertes (l'endpoint public Mairie, pourtant public et donc a priori le plus naturel pour un site public) n'a été trouvé dans dmv-public.

Sur la même page (ActorsClient.tsx), les données commune (élus, collectes, infos) sont lues via GET /communes/{communeId}/elus|collectes|infos — l'API Territory publique, ni Actor ni Mairie — confirmant que la fiche publique d'un acteur mairie compose déjà des lectures issues de plusieurs Bounded Contexts.

dmv-backoffice

Aucun consommateur trouvé. Les trois occurrences du terme « alerte » dans ce dépôt (src/components/Layout.jsx:107, src/pages/CriticiteConfig.jsx, src/pages/PublicationScoringConfig.jsx:154) concernent la configuration du score de criticité des publications (un mécanisme de modération de contenu, type_publications de type « alerte »), un concept homonyme mais fonctionnellement sans rapport avec mairie_alertes. Confirmé par lecture du contenu de chaque fichier.

Tests et mocks

Une seule occurrence du terme « alerte » dans les suites de tests des trois dépôts : dmv-public/lib/feedEngine/__tests__/pipeline.test.ts — teste le classement d'une publication de type_publications.nom === 'alerte' dans le moteur de flux (Wall), sans rapport avec mairie_alertes non plus. Aucun test frontend, dans aucun des trois dépôts, n'exerce l'une ou l'autre des deux API d'alertes mairie.


5. Consommateurs backend

  • ExpireAlertes Job (Mairie/Jobs/ExpireAlertes.php) : n'appelle aucune des deux API HTTP. Il invoque directement MairieWriteService::expireAlertes() en mémoire (job horaire), qui exécute une mise à jour de masse sur le modèle Eloquent MairieAlerte — indépendant du chemin HTTP utilisé pour créer les alertes, donc indépendant du résultat de cet audit.
  • Commandes Artisan : aucune commande ne référence mairie_alertes ou MairieAlerte (vérifié — seules RefreshMairieSireneCommand et ResolveMairiesCommand existent dans Console/Commands, aucune des deux ne touche aux alertes).
  • Listeners : aucun listener trouvé dans le dépôt référençant MairieAlerte ou mairie_alertes (api/app/Modules/*/Listeners — répertoires absents ou vides pour ce sujet, cohérent avec le constat déjà établi par ENG-001-1-dmv-core-audit-report.md : aucune classe d'événement métier n'existe encore dans le projet).
  • Import : confirmé lors de la rédaction d'ENG-001-5-municipal-management.md §6.5 — aucune table municipale, y compris mairie_alertes, n'est touchée par le module Import. Revérifié ici, inchangé.
  • Admin : aucune route ni aucun contrôleur Admin ne gère mairie_alertes (vérifié par lecture de Admin/Routes/api.php — zéro occurrence du terme « alerte »). Le backoffice n'offre aucune gestion des alertes mairie, ni via l'une ni via l'autre API.

Conclusion. Les deux API HTTP sont exclusivement consommées par les frontends. Aucun usage interne au backend ne dépend de l'une ou l'autre route — un remplacement ou une suppression de route n'affecterait que des clients HTTP externes (les trois frontends audités, et tout client non audité, voir §11).


6. Comparaison fonctionnelle

AspectAPI ActorAPI MairieImpact
Lecture publiquePar acteur (/acteurs/{id}/alertes)Par commune (/mairie/communes/{id}/alertes)Clé de recherche différente — une migration de lecture nécessiterait de reprojeter la requête
Permission d'écrituremanage_actor (générique)manage_alertes (spécifique)Un collaborateur avec un rôle restreint pourrait avoir des droits différents selon l'API utilisée
Vérification du module alertesAbsentePrésente (hasModule)L'API Actor permet de créer une alerte même si le module « alertes » est désactivé pour l'acteur — l'API Mairie l'interdit. Différence de comportement fonctionnel réelle, pas seulement cosmétique.
contenu à la créationOptionnel (nullable)Obligatoire (required)Un payload valide pour Actor serait rejeté (422) par Mairie
expires_at à la créationAucune contrainteDoit être future (after:now)Idem — une date passée est acceptée par Actor, rejetée par Mairie
is_active à la créationRespecté si fourni par le clientToujours forcé à true, valeur cliente ignoréeUne alerte créée « inactive » est possible via Actor, impossible via Mairie
Suppression physiqueAbsente (aucune des deux)Absente (aucune des deux)Identique — pas de différence
Forme de la réponseModèle Eloquent brut (tous les attributs, dont updated_at)DTO figé (MairieAlerteDTO, sans updated_at)Un client qui dépendrait de champs non exposés par le DTO (ex. updated_at) casserait en migrant vers Mairie
Résolution de commune_id à la créationLecture de l'attribut Eloquent déjà chargé ($acteur->commune_id)Requête dédiée (getCommuneIdForActeur())Résultat identique en pratique, chemin de code différent

Ce qui empêcherait un remplacement immédiat, sans changement de comportement observable :

  1. La vérification du module alertes (présente côté Mairie, absente côté Actor) — basculer tous les clients vers Mairie sans adaptation changerait le comportement pour tout acteur ayant ce module désactivé (blocage nouveau) ; basculer vers Actor de façon définitive maintiendrait un contournement de ce contrôle qui existe déjà aujourd'hui côté Actor.
  2. La contrainte contenu obligatoire et expires_at future à la création côté Mairie — un payload aujourd'hui accepté par Actor (contenu vide, date passée) serait rejeté si le trafic basculait vers Mairie sans adapter le frontend.
  3. La forme de la réponse (DTO vs modèle brut) — tout code frontend qui lirait un champ absent du DTO Mairie (aucun trouvé dans le code actuel, mais non garanti pour des consommateurs non audités, voir §11) casserait silencieusement.
  4. La clé de lecture publique (par acteur vs par commune) — la migration de la lecture publique nécessiterait une adaptation du frontend, pas seulement un changement d'URL.

7. Compatibilité

Cas constaté : Cas D, avec asymétrie marquée. Les deux API sont utilisées simultanément, mais pas pour les mêmes ressources :

  • mairie_alertes : uniquement l'API Actor est utilisée par les trois frontends audités, en lecture (dmv-public, deux écrans) et en écriture (dmv-workspace, un écran complet). L'API Mairie (MairieAlerteController) n'a aucun consommateur trouvé dans les trois dépôts.
  • commune_elus/commune_collectes/commune_infos/communes (commune) : l'API Mairie est utilisée par dmv-workspace en écriture ; la lecture publique passe par l'API Territory, ni Actor ni Mairie.
  • mairie_services : aucune des deux API pertinentes (il n'existe qu'une API Mairie pour cette ressource) n'a de consommateur trouvé dans les trois dépôts.

Ce n'est donc ni le Cas A, ni le Cas B, ni le Cas C tels que formulés par la mission : c'est un Cas D où, pour la ressource alertes spécifiquement, l'API qui semble « legacy » au vu de sa localisation dans le code (Actor, hors du module Mairie qui porte le nom de la fonctionnalité) est en réalité celle qui est réellement utilisée, et l'API qui semble « canonique » de par son emplacement (module Mairie) est celle qui n'a aucun consommateur actif trouvé.

Impacts et risques par hypothèse de suite :

  • Si l'API Mairie (MairieAlerteController) était supprimée aujourd'hui : aucun impact détecté sur les trois frontends audités. Risque résiduel : tout consommateur non audité (voir §11).
  • Si l'API Actor (ActeurModulesController, endpoints alertes) était supprimée aujourd'hui : rupture immédiate de l'écran de gestion des alertes dans dmv-workspace (4 fonctions) et de l'affichage des alertes actives dans dmv-public (3 emplacements). Impact majeur, à éviter sans migration préalable du frontend.
  • Migration nécessaire dans les deux sens : quelle que soit l'API retenue comme canonique, au moins un consommateur frontend actif devra être adapté — sauf si l'API Actor est retenue telle quelle (aucune adaptation frontend nécessaire, uniquement un changement d'implémentation côté backend, voir Option B §8).

8. Options

Présentées sans décision, conformément à la mission.

Option A — Conserver l'API Mairie comme canonique

  • Avantages. Cohérent avec l'ownership cible (RFC-001 : alertes municipales appartiennent à Municipal Management, dont Mairie est l'ancêtre direct) ; autorisation plus fine déjà en place (manage_alertes, vérification du module) ; réponse déjà sous forme de DTO stable.
  • Inconvénients. Nécessite d'adapter trois écrans actifs (un dans dmv-workspace, deux dans dmv-public) vers la nouvelle clé de lecture (par commune plutôt que par acteur pour la lecture publique) et vers des règles de validation plus strictes (contenu requis, expires_at future) — un changement fonctionnel non trivial, potentiellement perceptible par les utilisateurs actuels de Workspace (formulaire différent) si le frontend n'est pas adapté en amont.
  • Risques. Migration frontend à coordonner sur deux dépôts avant toute suppression de l'API Actor ; fenêtre de coexistence obligatoire.
  • Dette technique. Aucune nouvelle dette — résorbe la duplication.
  • Impact Municipal Management. Aligné nativement — Mairie devient directement le socle du futur module, cohérent avec RFC-001.
  • Impact frontend. Élevé — trois écrans à adapter avant toute suppression côté backend.

Option B — Conserver l'API Actor comme canonique

  • Avantages. Aucune adaptation frontend nécessaire — c'est déjà l'API réellement utilisée partout. Migration purement backend : réimplémenter ActeurModulesController pour déléguer à un futur service Municipal Management au lieu d'accéder directement au modèle, sans changer les routes ni les contrats de payload/réponse déjà consommés.
  • Inconvénients. Contredit la logique de nommage et l'intuition d'ownership (une route /acteurs/... gérée par un contexte qui n'est pas censé posséder cette donnée) ; nécessite de corriger consciemment les deux lacunes fonctionnelles identifiées (permission manage_alertes absente, vérification de module absente) pour ne pas figer un comportement moins strict que prévu comme comportement définitif.
  • Risques. Si les deux lacunes de contrôle d'accès ne sont pas corrigées au passage, elles deviennent une dette permanente plutôt qu'un accident de trajectoire.
  • Dette technique. Nécessite de documenter formellement pourquoi l'API publique porte un nom (/acteurs/...) qui ne reflète pas son Bounded Context propriétaire réel (Municipal Management) — écart nom/ownership à assumer explicitement plutôt qu'à corriger.
  • Impact Municipal Management. Le futur module devrait exposer ses contrats via des routes historiquement logées dans Actor — inhabituel mais pas bloquant (ADR-014 §12 : les routes sont des adaptateurs, pas l'implémentation elle-même).
  • Impact frontend. Nul à court terme.

Option C — Façade de compatibilité temporaire

  • Avantages. Permet de basculer l'implémentation backend (vers Municipal Management à terme) sans attendre la coordination des trois dépôts frontend ; les deux routes actuelles restent disponibles, l'une (Mairie) authentique, l'autre (Actor) en façade de compatibilité qui délègue au même service sous-jacent.
  • Inconvénients. Maintient deux surfaces API pour la même ressource plus longtemps ; un garde-fou architectural (comme celui introduit par ENG-001.4 pour Territory → Actor) devient plus difficile à écrire puisque la façade elle-même doit légitimement appeler le service interne.
  • Risques. Si la façade n'est jamais retirée faute de suivi, la duplication redevient permanente — nécessite un engagement explicite de dépréciation avec échéance.
  • Dette technique. Dette explicitement temporaire et documentée, la moins mauvaise des trois formes de dette si un calendrier de retrait est fixé et respecté.
  • Impact Municipal Management. Neutre — le module peut être créé et devenir propriétaire exclusif de la donnée sans attendre la clarification de la façade externe.
  • Impact frontend. Nul à court terme ; nécessite malgré tout, à terme, la même migration que l'Option A pour permettre le retrait de la façade.

9. Recommandation

Cette section formule une recommandation argumentée, conformément à la mission — elle ne constitue pas une décision actée.

Les faits constatés (§4-§7) pointent vers l'Option B comme la moins risquée à court terme : l'API Actor est déjà, de facto, l'unique API réellement utilisée pour mairie_alertes par les trois frontends audités ; l'API Mairie n'a aucun consommateur actif détecté. Choisir l'Option A imposerait une migration frontend sur trois écrans avant de pouvoir supprimer quoi que ce soit côté backend, pour un gain d'alignement architectural qui peut être obtenu autrement (faire pointer les routes /acteurs/.../alertes vers un service Municipal Management interne, sans changer les routes elles-mêmes — cohérent avec ADR-014 §12, « DMV Core n'est pas l'API »). L'Option C n'apporte pas d'avantage suffisant ici puisque l'API « à déprécier » (Mairie) n'a déjà aucun trafic à faire coexister.

Cette recommandation ne dispense pas de trancher explicitement les deux lacunes fonctionnelles identifiées en §6 (permission manage_alertes, vérification du module alertes) — quelle que soit l'option retenue, ces deux points doivent être une décision consciente, pas un oubli hérité.

Recommandation : Option B, sous réserve de vérification qu'aucun consommateur non audité n'utilise l'API Mairie (§11).


10. Impact sur les futures PR

Réévaluation du découpage Municipal-A à Municipal-F proposé par ENG-001-5-municipal-management.md §12, à la lumière de cet audit. Aucun document de roadmap n'est modifié ici — recommandation uniquement.

  • Le découpage en six PR reste globalement pertinent. Aucune PR ne doit être supprimée.
  • PR Municipal-B (« résolution de la duplication Actor/Mairie ») devrait être précisée, pas déplacée : son option recommandée n'était pas encore connue au moment de sa rédaction (elle demandait explicitement une vérification frontend avant arbitrage, §13.A de la spec principale) — cet audit fournit cette vérification. Le risque documenté dans la spec (« rupture d'un client existant... à vérifier côté frontend avant implémentation ») est désormais levé dans un sens précis : c'est l'API Mairie qui peut être retirée sans impact frontend détecté, pas l'inverse.
  • PR Municipal-C (« déplacement d'Alertes et Services municipaux ») doit intégrer une clarification : les alertes ont un consommateur actif à préserver (API Actor) tandis que les services municipaux (mairie_services) n'en ont aucun dans le périmètre audité — ces deux ressources, actuellement regroupées dans la même PR, ont des profils de risque différents. Ne pas fusionner ni scinder sans arbitrage explicite, mais signaler l'asymétrie à l'architecte.
  • Aucune nouvelle PR n'apparaît nécessaire du fait de cet audit — les corrections des deux lacunes fonctionnelles (§6) peuvent être absorbées par PR Municipal-B, qui touche déjà ce périmètre.

11. Décisions restant ouvertes

Cet audit ne tranche rien. Les points suivants restent à arbitrer par l'architecte :

  1. Choix entre Option A, B et C (§8) — recommandation formulée (§9), décision non prise.
  2. Correction des deux lacunes fonctionnelles de l'API Actor (permission manage_alertes absente, vérification du module alertes absente) — à corriger quelle que soit l'option retenue, mais le mécanisme exact (ajout des contrôles manquants dans ActeurModulesController, ou délégation complète à un futur service partagé qui les porterait nativement) n'est pas arbitré ici.
  3. Consommateurs non audités. Cet audit couvre exclusivement dmv-workspace, dmv-public, dmv-backoffice et le backend api, conformément au périmètre de la mission. Il ne couvre pas : une éventuelle application mobile Capacitor, dmv_backoffice (l'autre backoffice, à accès Supabase direct, distinct de dmv-backoffice, non mentionné par la mission), ni tout consommateur externe (partenaire, script, intégration tierce). L'affirmation « aucun consommateur actif de l'API Mairie » est valable dans le périmètre audité uniquement — à confirmer avant toute suppression de route.
  4. Calendrier et modalités d'une éventuelle façade de compatibilité (Option C), si elle devait finalement être retenue malgré la recommandation en sens contraire.
  5. Précision du découpage Municipal-C (§10) : scinder Alertes et Services municipaux en deux PR distinctes, ou les garder groupées avec un risque documenté comme asymétrique — non tranché ici.

Références