5 API en direct
Corentin JOGUET edited this page 2026-09-30 15:09:06 +02:00

API en direct

Deux surfaces distinctes, et la distinction compte.

Surface Qui appelle Authentification
/api/... (hote borne, relaye aussi sur l'hote back-office) la borne, un client anonyme aucune
/admin/api/... sur l'hote back-office un equipier, un outil de test session + jeton anti-falsification (CSRF)

L'hote de la borne ne relaie que /api vers le serveur d'application : le back-office est hors d'atteinte depuis l'origine borne, par construction.

Essayer sans rien installer

curl -s https://corentin-wakdo-admin.stark.a3n.fr/api/health
curl -s https://corentin-wakdo-admin.stark.a3n.fr/api/categories
curl -s https://corentin-wakdo-admin.stark.a3n.fr/api/products

Ces trois appels repondent en JSON sans compte.

Le contrat

docs/api/conventions.md — les conventions : enveloppe {data, error}, codes de statut, compteur total sur les listes (pas de pagination), gestion des conflits. C'est le document de reference.

docs/adr/0017-api-admin-json.md — pourquoi une API JSON d'administration a ete ajoutee a cote du back-office rendu cote serveur, et ce qu'elle garantit.

Demonstration avec Postman, Bruno ou Insomnia

La collection couvre les 57 routes de l'API d'administration (/admin/api/…), dont, depuis le 2026-09-28, l'historique des mouvements d'un ingredient (GET /admin/api/ingredients/{id}/movements), le modele d'import, l'apercu d'import sans ecriture (?dry_run=1) et l'etat de sante detaille (GET /admin/api/health).

Les deux collections sont livrees, generees depuis un script pour rester alignees sur le code :

Les deux collections portent 91 requetes, chacune avec une verification de sa reponse. Pour Insomnia, la collection se construit a la main en suivant le meme scenario (docs/api/demo-api.md, section 8) : les collections Postman et Bruno servent de reference, pas de copie.

La session s'ouvre en JSON : POST /admin/api/auth/login avec un corps JSON pose le cookie de session et rend le jeton anti-falsification (CSRF) a placer dans l'en-tete des ecritures. Plus besoin de passer par le formulaire HTML pour tester. GET /admin/api/auth/me dit qui est connecte, POST /admin/api/auth/logout ferme.

Les identifiants des comptes de demonstration : docs/demo/comptes-demo.md

Depuis le back-office : la page Sante

Connecte, /admin/health rassemble au meme endroit :

  • l'etat de l'application et sept appels reels, dont cinq refus attendus (401, 403, 415...) arretes avant toute ecriture, avec le detail de chaque reponse (statut, en-tetes utiles, corps JSON indente) ;
  • la carte des 158 routes, lue dans le routeur et non recopiee, rangee par action : la page qui affiche le formulaire (GET), l'envoi du formulaire, la meme action dans l'API JSON. Une action a souvent deux ou trois routes : afficher puis envoyer sont deux requetes, un formulaire HTML ne connait que GET et POST (d'ou POST …/delete cote back-office et DELETE cote API), et l'API refait les memes actions en JSON, avec les memes permissions et le meme code personnel ;
  • le trajet d'un appel, couche par couche, avec des reponses reelles : une lecture est envoyee au serveur quand on lance l'appel ; une ecriture n'est jamais envoyee depuis la page, et le trajet montre la reponse obtenue pour de vrai sur une pile de test jetable (date et commit de la capture affiches). Les 158 routes y sont capturees en succes, et 670 refus obtenus sur 689 tentes (les autres sont affiches « non reproduit » avec le code observe), par tests/e2e/health-capture.spec.js ;
  • une console de lecture : toute route GET, un champ par parametre, appelee avec la session de la page. Aucune ecriture n'est possible depuis la page, et c'est le code qui le refuse, pas seulement la liste deroulante ;
  • une connexion de demonstration qui montre la vraie reponse de POST /admin/api/auth/login et son jeton, sans remplacer la session de qui regarde la page, puis donne la sequence curl a rejouer dans Postman, Bruno ou Insomnia.

docs/journal/2026-09-27--console-page-sante.md — les garde-fous et comment ils sont verifies en vrai navigateur.

Construire une requete soi-meme

Cinq questions, dans l'ordre ; chaque reponse remplit une case de Postman, Bruno ou Insomnia.

  1. Sur quoi j'agis ? Le chemin. Une collection (/admin/api/ingredients) ou un element (/admin/api/ingredients/12) : l'identifiant va dans le chemin, jamais apres un ?. Les parametres apres ? sont des options (?dry_run=1).
  2. Quel geste ? La methode. Lire : GET. Creer : POST sur la collection, sans identifiant. Modifier : PUT sur l'element, qui remplace l'element entier (lire d'abord l'element, copier son data, changer ce qu'il faut). Supprimer ou desactiver : DELETE. Une action metier : POST …/{id}/action.
  3. Qui suis-je ? Le cookie de session, pose par POST /admin/api/auth/login et renvoye tout seul par l'outil. Ne rien mettre dans l'onglet d'authentification.
  4. Est-ce une ecriture ? Alors l'en-tete X-CSRF-Token, avec le csrf_token renvoye par la connexion (il change a chaque connexion).
  5. Quelles donnees ? Le corps, en JSON (Content-Type: application/json). Pour une action sensible, ajouter pin_email et pin dans le corps, meme pour un DELETE.

Lire la reponse

Le code dit a quelle etape la requete a ete arretee :

Code Etape Ce qu'on corrige
404 routeur le chemin, ou un identifiant qui n'existe pas (exception : un numero de commande inconnu sur /admin/api/orders/... rend 403, pas 404, pour ne pas distinguer un numero inconnu d'un canal non visible)
405 routeur la methode : le chemin existe, pas pour ce geste
401 session se reconnecter
403 FORBIDDEN permission ce compte n'a pas le droit
403 CSRF_INVALID jeton en-tete X-CSRF-Token absent ou d'une ancienne connexion
400 INVALID_JSON corps JSON mal ecrit, ou racine qui n'est pas un objet
415 corps l'en-tete Content-Type n'est pas application/json
422 VALIDATION_ERROR validation lire error.fields : chaque ligne dit quoi mettre
422 PIN_INVALID code personnel pin_email ou pin faux
409 base l'etat ne le permet pas (ingredient qui a un historique, commande annulee ou deja livree, emplacement de menu deja commande)

Cote back-office, une adresse inconnue affiche une page « Page introuvable » ; l'API, elle, repond toujours dans son enveloppe JSON.

Sur l'API de la borne (POST /api/orders), le refus d'une commande porte un code precis : EMPTY_ORDER, INVALID_QUANTITY (1 a 20 par ligne), TOO_MANY_ITEMS (50 lignes), ORDER_TOO_LARGE (50 articles), OPTION_UNAVAILABLE (option de menu indisponible pour le format choisi). Sur /admin/api/orders, les memes refus sortent en 422 VALIDATION_ERROR, le message dans error.fields.items.

Le cycle complet en ligne de commande

scripts/curl-e2e-json-login.sh — connexion, lecture, creation, modification, suppression, deconnexion, avec une verification a chaque etape. Les identifiants se passent par l'environnement, ils ne sont pas ecrits dans le script.

Ce que l'API refuse, et pourquoi

Le controle d'acces verifie une permission, pas un nom de role : un role personnalise dote des bonnes permissions ouvre les memes fonctions sans changement de code. La matrice complete : docs/demo/matrice-rbac.md

Les actions sensibles demandent en plus un code personnel (4 a 12 chiffres ; ceux des comptes de demonstration en ont 4), verifie independamment de la session : annulation d'une commande, suppression d'un produit ou d'un menu, changement de prix ou de TVA d'un produit, confirmation d'un import quand il change un prix, inventaire et ajustement de stock, creation, modification, desactivation, effacement et remise a zero du code personnel d'un compte, creation et modification d'un role. La liste qui fait foi est la colonne PIN de src/app/Health/RouteSecurity.php. La personne qui agit est inscrite dans la meme transaction que l'action : au journal d'audit, ou, pour l'inventaire et l'ajustement, dans le mouvement de stock.

Import de produits par fichier

docs/api/import-produits.md — le format du fichier CSV attendu, le modele telechargeable depuis le back-office, et ce que l'apercu montre avant d'appliquer quoi que ce soit.

Voir aussi

Architecture · Modele de donnees