Aller au contenu principal

API Admin

Objectif

Documenter les endpoints sensibles du backoffice DMV réservés aux administrateurs globaux ou opérateurs autorisés.

Périmètre

Gestion dashboard, utilisateurs, rôles, acteurs, publications, claims, abonnements, messages et communes.

Applications consommatrices

  • Backoffice DMV uniquement.
  • Support/administration autorisés.

Authentification requise

Toutes les routes visibles utilisent :

  • identify.app ;
  • auth:sanctum ;
  • ensure.admin.

Le commentaire du code mentionne /api/admin/, mais les providers montent les modules sous api/v1 et le route group utilise prefix('admin'). Le chemin documenté est donc /api/v1/admin/... à confirmer par route:list.

Permissions/RBAC

  • Endpoints sensibles.
  • Réservés admin global ou opérateurs explicitement autorisés.
  • ensure.admin vérifie isAdmin() via rôle admin selon commentaire code.
  • Aucun accès service_role côté client.

Endpoints confirmés visibles

MéthodeRouteDomaine
GET/api/v1/admin/usersUtilisateurs.
GET/api/v1/admin/dashboardDashboard agrégé backoffice.
GET/api/v1/admin/users/{userId}Utilisateurs.
PATCH/api/v1/admin/users/{userId}Utilisateurs.
POST/api/v1/admin/users/{userId}/rolesRôles.
DELETE/api/v1/admin/users/{userId}/roles/{role}Rôles.
GET/api/v1/admin/acteursActeurs.
PATCH/api/v1/admin/acteurs/{id}/validateValidation acteur.
PATCH/api/v1/admin/acteurs/{id}/badgeBadge acteur.
PATCH/api/v1/admin/acteurs/{id}/activeActivation/désactivation acteur.
DELETE/api/v1/admin/acteurs/{id}/restoreRestauration acteur.
POST/api/v1/admin/acteurs/{id}/can-publishDroit publication acteur.
GET/api/v1/admin/claimsClaims.
PATCH/api/v1/admin/claims/{id}Claims.
GET/api/v1/admin/publicationsListe paginée des publications.
PATCH/api/v1/admin/publications/bulk/moderateModération en lot de plusieurs publications.
PATCH/api/v1/admin/publications/{id}/moderateModération publication.
PATCH/api/v1/admin/publications/{id}/valideBascule visibilité/validation.
PATCH/api/v1/admin/publications/{id}/featureMise en avant (en_avant, featured_until).
DELETE/api/v1/admin/publications/{id}Soft-delete publication.
PATCH/api/v1/admin/publications/{id}/restoreRestauration publication.
GET/api/v1/admin/subscriptionsAbonnements.
POST/api/v1/admin/communesCommunes.
PATCH/api/v1/admin/communes/{id}Communes.
GET/api/v1/admin/communes/{id}/fullSnapshot complet d'une commune pour le backoffice.
GET/api/v1/admin/communes/{id}/defibrillateursListe admin des défibrillateurs de la commune.
POST/api/v1/admin/communes/{id}/defibrillateurs/syncSynchronisation GEODAE pour une commune.
PATCH/api/v1/admin/defibrillateurs/{id}Édition du statut admin d'un défibrillateur communal.
GET/api/v1/admin/defibrillateurs-franceCatalogue national paginé des défibrillateurs.
POST/api/v1/admin/defibrillateurs-france/syncSynchronisation nationale GEODAE.
PATCH/api/v1/admin/defibrillateurs-france/{id}Édition du statut admin d'un défibrillateur national.
GET/api/v1/admin/messages/threadsFils de messages support.
GET/api/v1/admin/messages/threads/{id}/messagesMessages d'un fil.
POST/api/v1/admin/messages/threads/{id}/replyRépondre (envoie un email via Resend).
PATCH/api/v1/admin/messages/threads/{id}/readMarquer comme lu.
PATCH/api/v1/admin/messages/threads/{id}/unreadMarquer comme non lu.
PATCH/api/v1/admin/messages/threads/{id}/flagChanger la couleur de flag.
PATCH/api/v1/admin/messages/threads/{id}/closeOuvrir/fermer le fil.
GET/api/v1/admin/import-sourcesListe des sources d'import automatique.
POST/api/v1/admin/import-sourcesCréer une source d'import.
GET/api/v1/admin/import-sources/{id}Lire une source d'import.
PATCH/api/v1/admin/import-sources/{id}Modifier une source d'import.
DELETE/api/v1/admin/import-sources/{id}Supprimer une source d'import.
POST/api/v1/admin/import-sources/{id}/runDéclencher un import manuel (?dry_run=true pour simulation).
GET/api/v1/admin/import-sources/{id}/runsHistorique des runs d'une source.
GET/api/v1/admin/import-sources/{id}/logs/{date}Log d'un run spécifique (format YYYY-MM-DD).

Endpoints cibles si nécessaire

  • Journal d’audit admin à exposer en lecture contrôlée, non confirmé.
  • Gestion opérateurs/support différenciée à confirmer.

Exemples génériques de requête/réponse

POST /api/v1/admin/users/user-id/roles
Authorization: Bearer <admin-token>
X-App-Source: backoffice
Content-Type: application/json

{
"role": "admin"
}
{
"data": {
"user_id": "user-id",
"roles": ["admin"]
}
}

Exemple dashboard :

GET /api/v1/admin/dashboard
Authorization: Bearer <admin-token>
X-App-Source: backoffice
{
"publications": 1284,
"publications_pending": 12,
"publications_reported": 3,
"acteurs": 542,
"users": 1880
}

Exemple liste publications admin :

GET /api/v1/admin/publications?page=1&per_page=10&moderation_status=pending&sort=created_at_desc
Authorization: Bearer <admin-token>
X-App-Source: backoffice

Erreurs principales

{
"error": "admin_required",
"message": "Droits administrateur requis."
}

Audit/logs si applicable

Obligatoire pour toutes les mutations admin :

  • rôles utilisateurs ;
  • validation acteur ;
  • badge ;
  • restauration ;
  • droit de publication ;
  • décision claim ;
  • création/mise à jour commune.

Sécurité

  • Endpoints très sensibles.
  • Pas d’usage depuis un frontend public.
  • Pas de clé service exposée côté client.
  • Limiter les données personnelles affichées.
  • L’IA ne doit pas appeler ces endpoints directement sans backend contrôlé et validation humaine.

État actuel vs cible

SujetÉtat actuelCible
Routes adminConfirmées, avec dashboard et publications backoffice.Audit complet et rôles opérateurs détaillés.
ensure.adminConfirmé.Matrice admin/support plus fine si besoin.
URL finale/api/v1/admin/... confirmée dans le route group.Contrat route/payload documenté plus finement.

Notes d'implémentation observées

  • L'endpoint GET /api/v1/admin/dashboard doit rester tolérant aux tables optionnelles ou absentes afin d'éviter un 500 global du dashboard.
  • Le listing admin des publications supporte per_page et le backoffice l'utilise actuellement à 10 éléments par page.
  • Le listing admin des publications charge page par page ; il ne doit plus charger l'ensemble du stock au premier affichage.
  • Les filtres supportés par GET /api/v1/admin/publications sont page, per_page, sort, q, commune_id, acteur_id, type_id, moderation_status, valide, deleted et duplicates.
  • Le backoffice ouvre désormais la vue avec moderation_status=pending, sort=created_at_desc et per_page=10.

Doublons publications

Le filtre duplicates=true active un groupement serveur des publications suspectes. La détection commence par source_url normalisée, puis bascule sur une signature de contenu normalisée basée sur acteur_id, type_id, titre, date_debut et le début du contenu.

Le backend renvoie alors duplicate_key, duplicate_reason et duplicate_count pour permettre au backoffice d'afficher les groupes plutôt que des lignes isolées.

Bug corrigé : dashboard 500 (2026-06-26)

La jointure subscription_logs → subscription_plans comparait l.plan_id (UUID) avec p.slug (varchar) — PostgreSQL rejetait l'opérateur. Correction : jointure sur p.id = l.plan_id. Les blocs subscription_plans et subscription_logs sont désormais enveloppés dans des blocs try/catch pour éviter un 500 global si le schéma évolue.

Bug corrigé : publications invisibles après validation manuelle (2026-06-26)

Les méthodes moderate(), bulkModerate() et toggleValide() de AdminPublicationService mettaient valide = true et moderation_status = 'published' sans remplir publie_le. Or tous les filtres du frontend exigent publie_le IS NOT NULL. Correction : lorsqu'une publication passe à published, publie_le est fixé à now() si la valeur est encore NULL.

Impact : les publications importées puis approuvées manuellement dans le backoffice étaient entièrement invisibles sur le front, même si leur statut apparaissait correct. 82 publications ont été rétroactivement corrigées le 2026-06-22.

Points à confirmer

  • Différence admin global vs opérateur support.
  • Payloads exacts.
  • Audit log exposé ou interne.
  • Confirmation route réelle via commande Laravel.