API
Routes HTTP du module Rétroaction sous /za/api/feedback, l'intégration feedback lue par le module Projets, et les fonctions importables par le site.
Les routes sont servies sous /za/api/feedback. Le routeur du core applique la session, la permission, la limite de débit et la vérification d'origine. Un refus que la personne peut corriger répond un message en français : 400 pour une saisie invalide, 404 pour un objet introuvable, 409 avec un code pour un conflit.
Le projet d'une requête
Une route qui liste ou crée lit le projet en paramètre : ?project=<id> pour l'admin, ?projet=<slug> pour le public. Sans lui, c'est le projet principal. Côté public, quand le projet principal est privé, c'est le premier projet public et actif, dans l'ordre. Le paramètre est toujours lu, même avec un seul projet. Un projet inconnu répond 404, comme un projet privé ou archivé côté public.
Les routes qui visent un rapport ou une étiquette par son :id n'en ont pas besoin : l'objet connaît son projet.
Admin : les rapports
| Méthode | Chemin | Permission | Rôle |
|---|---|---|---|
| GET | /admin/reports | feedback.view | La boîte de réception d'un projet (project). Filtres status, type, tag, search (dans le résumé et les détails ; un nombre, 42 ou #42, trouve aussi le rapport de ce numéro). sort vaut created (par défaut, le plus récent reçu d'abord) ou updated (la dernière activité d'abord) ; une autre valeur répond 400. Paginée par page et limit (50 par défaut, 200 au plus). Chaque rapport porte duplicateOf, attachmentCount et item : { id, title, column: { name, isDone } } avec le module Projets, null sinon. |
| GET | /admin/reports/:id | feedback.view | Un rapport, son compte (account), ses échanges, ses captures et sa fiche (item). original est le rapport dont il est le doublon, duplicates ses doublons, chacun { id, title, status, closeReason, project: { id, name, isDefault }, createdAt }. Chaque échange porte kind : internal, public ou author. projectsModule vaut { available, linked } : le module Projets est là, et le projet du rapport nomme un de ses projets. |
| PATCH | /admin/reports/:id | feedback.manage | type, tagId, sensitive. La marque de sécurité est refusée sur un rapport lié à une fiche publique (code item_public). Ou { projectId } seul : déplace le rapport vers ce projet, sans étiquette, hors de sa fiche. |
| DELETE | /admin/reports/:id | feedback.manage | Supprime le rapport, ses échanges et ses captures. |
| POST | /admin/reports/:id/link | feedback.manage | Lie le rapport à une fiche du module Projets. Voir Lier un rapport. |
| POST | /admin/reports/:id/unlink | feedback.manage | Retire le rapport de sa fiche. Un rapport linked redevient new. Un rapport fermé le reste, avec sa raison et sa date de fermeture. |
| POST | /admin/reports/:id/close | feedback.manage | { reason, duplicateOf }. Voir Fermer un rapport. |
| POST | /admin/reports/:id/reopen | feedback.manage | Rouvre un rapport fermé : linked s'il a une fiche, new sinon. Il quitte son original s'il était un doublon. 409 s'il n'est pas fermé. L'auteur reçoit « Message rouvert », sauf pour un rapport fermé comme spam. |
| POST | /admin/reports/:id/messages | feedback.manage | { kind: 'internal' | 'public', body }. Une réponse publique part par courriel ; emailed le dit. author est refusé : seul l'auteur écrit un message de l'auteur. |
| GET | /admin/reports/:id/attachments/:attachmentId | feedback.view | Une capture, servie par le core. |
Fermer un rapport
POST /admin/reports/:id/close prend { reason, duplicateOf }. reason vaut resolved, duplicate, declined ou spam. Fermer un rapport déjà fermé ne change que sa raison : closedAt garde la première fermeture, d'où part la conservation.
duplicateOf, facultatif, est l'id du rapport original, seulement avec duplicate. Il peut être d'un autre projet. S'il est lui-même un doublon, le lien va à son original. Les doublons du rapport fermé suivent vers le même original. Avec une autre raison, le lien vers un original est retiré.
| Refus | Statut | Quand |
|---|---|---|
| « Seul un doublon nomme un rapport original. » | 400 | duplicateOf avec une autre raison que duplicate. |
| « Un rapport ne peut pas être le doublon de lui-même. » | 400 | duplicateOf est le rapport fermé. |
| « Le rapport original est introuvable. » | 400 | duplicateOf ne désigne aucun rapport. |
| « Le rapport choisi est déjà un doublon de celui-ci… » | 400 | L'original désigné est un doublon du rapport fermé : la boucle est refusée. |
Un rapport qui n'était pas fermé vaut à son auteur un courriel « Message fermé », mis en file une fois la réponse envoyée. Jamais avec spam, ni pour un rapport effacé par la conservation, ni quand seule la raison change. Voir Les courriels.
Agir sur plusieurs rapports
Chaque route prend { ids }, de 1 à 200 ids de rapports, et s'applique dans une seule transaction : un refus n'en change aucun. Un id qui ne désigne aucun rapport est ignoré. La réponse est { success, count }, le nombre de rapports touchés. Des ids absents, vides, au-delà de 200 ou illisibles répondent 400.
| Méthode | Chemin | Permission | Rôle |
|---|---|---|---|
| POST | /admin/reports/bulk/close | feedback.manage | { ids, reason, duplicateOf }, avec les règles de Fermer un rapport. L'original ne peut pas faire partie de ids. Un courriel par rapport qui n'était pas fermé. |
| POST | /admin/reports/bulk/tag | feedback.manage | { ids, tagId }. L'étiquette doit appartenir au projet de chaque rapport, sinon 400. tagId: null retire l'étiquette. |
| POST | /admin/reports/bulk/move | feedback.manage | { ids, projectId }. Comme PATCH /admin/reports/:id avec { projectId } : chaque rapport perd son étiquette et quitte sa fiche. count compte les rapports déplacés, pas ceux déjà dans ce projet. Un projet inconnu répond 400. |
| POST | /admin/reports/bulk/delete | feedback.manage | { ids }. Comme DELETE /admin/reports/:id : les dossiers de leurs captures sont mis en file et vidés une fois la réponse envoyée. |
Lier un rapport
POST /admin/reports/:id/link prend { itemId }, une fiche existante, ou { newItem: { title, isPublic } }, une fiche à créer dans la même transaction, au nom du compte connecté. La nouvelle fiche prend le type du rapport : bug pour un bug, idea pour une idée, task pour une question. La fiche appartient toujours au projet de Projets que nomme le projet du rapport. Un rapport fermé se rouvre, à l'état linked.
| Code | Statut | Quand |
|---|---|---|
projects_missing | 409 | Le module Projets n'est pas installé ou pas activé. |
projects_unset | 409 | Le projet du rapport ne nomme aucun projet de Projets : « Ce projet ne correspond à aucun projet du module Projets : déplacer le rapport vers un projet qui en a un. » |
item_other_project | 409 | La fiche appartient à un autre projet de Projets. |
item_public | 409 | Un rapport de sécurité vers une fiche publique. Une nouvelle fiche créée depuis lui est interne, quel que soit isPublic. |
Admin : le module Projets
Ces routes répondent même sans le module Projets : available vaut alors false et les listes sont vides.
| Méthode | Chemin | Permission | Rôle |
|---|---|---|---|
| GET | /admin/projects-module/items | feedback.view | { available, linked, items } : les fiches ouvertes (hors des colonnes terminées) du projet de Projets que nomme le projet project de la rétroaction. Filtre search. linked vaut false quand ce projet n'en nomme aucun. Un projet inconnu répond 404. |
| GET | /admin/projects-module/items/:itemId/reports | feedback.view | Les rapports liés à une fiche, les plus récents d'abord, chacun { id, type, title, status, closeReason, sensitive, createdAt }. Lu par la page d'une fiche dans Projets. |
Admin : les projets et les étiquettes
| Méthode | Chemin | Permission | Rôle |
|---|---|---|---|
| GET | /admin/projects | feedback.view | { enabled, managedBy, projects }. projects : chacun { id, slug, name, position, isPublic, isDefault, projectsProjectId, isArchived, reportCount }, le principal d'abord. Un projet archivé n'y est que s'il garde des rapports. enabled vaut true dès qu'il y a plus d'un projet : l'admin montre alors le sélecteur. managedBy vaut 'projects' avec le module Projets, null sinon. |
| POST | /admin/projects | feedback.options | { name, isPublic }. Le projet s'ajoute à la fin, sans étiquette. |
| PATCH | /admin/projects/:id | feedback.options | name, isPublic. Le slug ne change pas. Un projectsProjectId envoyé est ignoré : seul le reflet des projets de Projets pose ce lien. |
| PUT | /admin/projects/order | feedback.options | { ids } : chaque projet, une fois. |
| DELETE | /admin/projects/:id | feedback.options | Un projet sans rapport qui n'est pas le principal (codes project_not_empty, project_default). Ses étiquettes partent avec lui. |
| GET | /admin/tags | feedback.view | Les étiquettes d'un projet (project), dans leur ordre : chacune { id, name, position, isPublic, reportCount }. |
| POST | /admin/tags | feedback.options | { name, isPublic }, dans le projet project, à la fin. isPublic vaut false par défaut. Code tag_taken pour un nom déjà pris. |
| PUT | /admin/tags/order | feedback.options | { ids } : chaque étiquette du projet project, une fois. L'ordre de l'admin et du menu du formulaire. Rend tags. |
| PATCH | /admin/tags/:id | feedback.options | name, isPublic (proposée dans le formulaire), l'un ou l'autre. Un champ absent garde sa valeur. |
| DELETE | /admin/tags/:id | feedback.options | Supprime l'étiquette. |
Le nom d'un projet ou d'une étiquette se lit sur une ligne : sauts de ligne, tabulations et caractères de contrôle deviennent une espace.
Sans le module Projets, les routes d'écriture des projets répondent toujours, même avec un seul projet. Deux écritures simultanées peuvent se heurter : la seconde répond 409 avec le code project_taken, « Un autre projet vient d'être enregistré en même temps : réessayer. »
Avec le module Projets
Les projets de la rétroaction reflètent alors ceux de Projets, qui les gère. Voir Avec le module Projets.
GET /admin/projectsrépondmanagedBy: 'projects'. Quand aucun projet de la rétroaction ne reprend encore un projet de Projets, juste après l'installation de Projets, il relit d'abord la liste de Projets. Ensuite, la rétroaction suit les signaux de Projets et la tâchefeedback-retention.POST /admin/projectsetPUT /admin/projects/orderrépondent 409 avec le codeprojects_managed: « Les projets se créent dans le module Projets. »PATCH /admin/projects/:idetDELETE /admin/projects/:idrépondent 409 avec le même code pour un projet qui reprend un projet de Projets : « Ce projet se gère dans le module Projets. » Un projet de la rétroaction qui n'en reprend aucun reste modifiable et supprimable ici : les Options le listent dans la carte « Projets sans équivalent dans Projets ».
Public
| Méthode | Chemin | Limite | Rôle |
|---|---|---|---|
| GET | /public/form | api | Ce dont le formulaire a besoin à l'ouverture : project ({ slug, name, isDefault }), le jeton du délai minimum (token, null sans FRAZA_ENCRYPTION_KEY), attachments, signedIn, vrai quand un compte client est connecté : un compte d'équipe, ou tout compte sur un site sans comptes clients, compte comme un visiteur, similar, vrai quand /public/similar peut répondre pour ce projet, et tags, [{ id, name }], les étiquettes que le formulaire propose sous « Ça concerne… », vide quand l'équipe n'en propose aucune. Jamais en cache. |
| GET | /public/similar | feedback_similar | ?q=<phrase> : les fiches publiques de la feuille de route qui partagent des mots avec la phrase, trois au plus, la plus proche d'abord : items, [{ title, path, voteCount, column: { name, isDone } }]. path est l'adresse de la fiche sur le site. Vide sans le module Projets, ou quand le projet ne lit pas ses rapports dans une feuille de route. Soixante appels par minute et par adresse : le formulaire le demande pendant la saisie. Jamais en cache. |
| POST | /public/reports | public_form | Envoie un rapport, en multipart/form-data : project (le slug, vide pour le projet principal), type, title, body, email, tag (facultatif : l'id d'une des tags du formulaire ; toute autre valeur est ignorée, et le rapport arrive sans étiquette), context (JSON), attachments (fichiers), _t, _hp. title se lit sur une ligne : sauts de ligne, tabulations et caractères de contrôle deviennent une espace. Le type enregistré d'une capture vient de son extension, jamais du type que déclare le navigateur. Répond followUrl pour un rapport envoyé sans compte client, signedIn quand le rapport est lié à un compte client, et accountHref, la section de l'espace client où le suivre, ou null. Code form_expired : redemander un jeton. 413 au-delà de trois captures de 5 Mo et du texte. Un projet inconnu ou privé est refusé. |
| POST | /public/follow/:token/messages | feedback_author_message | { body }, 5000 caractères au plus : un message de l'auteur sur le rapport que le jeton de suivi ouvre. Répond message : { fromAuthor: true, body, createdAt }. Voir Message de l'auteur. |
GET /public/form et GET /public/similar lisent ?projet=<slug>. L'envoi lit le champ project.
Espace client
| Méthode | Chemin | Limite | Rôle |
|---|---|---|---|
| POST | /account/reports/:id/messages | feedback_author_message | Session requise. { body } : un message de l'auteur sur un rapport que le compte client connecté a envoyé. Le rapport d'un autre compte, ou la session d'un compte d'équipe, répond 404. Même réponse que la route publique. |
Message de l'auteur
Les deux routes ajoutent un message author, puis notifient les personnes qui ont feedback.manage. Le préréglage feedback_author_message compte dix messages par adresse réseau toutes les dix minutes, même pour un compte client.
| Code | Statut | Quand |
|---|---|---|
| — | 404 | Un jeton faux ou effacé : « Ce lien de suivi n'existe pas ou n'est plus valide. » Ou un rapport d'un autre compte. |
report_closed | 409 | Le rapport est fermé : « Ce message est fermé : il ne reçoit plus de réponse. Écrire un nouveau message à l'équipe. » |
report_anonymized | 409 | La conservation a effacé les données du rapport. Seule la route de l'espace client le rencontre : l'effacement coupe aussi le lien de suivi, qui répond alors 404. |
Un body vide ou au-delà de 5000 caractères répond 400.
L'intégration feedback
Le module Projets lit ce module par le registre @fraza/core/integrations, sans importer son paquet. Voir Intégrations entre modules. getIntegration('feedback') vaut null quand ce module est absent ou désactivé.
La forme de cette intégration est un contrat avec le module Projets : une fonction ne change de forme qu'avec lui. L'objet porte contract, le numéro de ce contrat. Chaque module vérifie celui de l'autre : un module Projets qui déclare un autre contrat est ignoré, comme s'il était absent, avec un avertissement unique dans le journal. Mettre alors les deux modules à jour.
Côté serveur
Publiée par register-server.js, quand le module est activé.
| Fonction | Rôle |
|---|---|
contract | Pas une fonction : le numéro du contrat, 1. Projets ignore une intégration feedback qui en déclare un autre. |
announceRecipients(itemId) | Les adresses des auteurs des rapports liés à une fiche, chacune une fois. Un rapport de sécurité ou effacé par la conservation est laissé de côté. L'adresse d'un compte est celle du compte, lue maintenant. |
checkItemChange(itemId, change) | Un refus en français, ou null. Avec change.isPublic, refuse une fiche liée à un rapport de sécurité : elle reste interne. |
countReports(itemIds) | { [itemId]: nombre de rapports }, pour les fiches qui en ont. |
onItemMoved(itemId, projectId) | La fiche a changé de projet dans Projets. Ses rapports suivent vers le projet de la rétroaction qui nomme le nouveau, et perdent leur étiquette s'ils changent de projet. Si aucun ne le nomme, les rapports restent où ils sont et quittent la fiche : linked redevient new. |
onItemDeleted(itemId) | Les rapports de la fiche retournent dans la boîte de réception. |
onItemsMerged(fromId, intoId) | Une fiche fusionnée dans une autre du même projet : ses rapports passent sur la fiche gardée, leur état inchangé. |
onProjectSaved(project) | Un projet de Projets a été créé ou modifié. Prend { id, slug, name, isPublic, isFeatured, isArchived }. Son projet dans la rétroaction le suit : créé la première fois, puis même nom, même visibilité, même archivage. Mis en avant, il devient le projet principal. Le projet principal n'est jamais archivé : il reste ouvert jusqu'à ce qu'un autre projet soit mis en avant. |
onProjectDeleted(projectId) | Un projet de Projets a été supprimé. Son projet dans la rétroaction est supprimé s'il n'a aucun rapport. Sinon, il reste, archivé, et ne nomme plus aucun projet de Projets. Le projet principal n'est jamais supprimé ni archivé ainsi. |
formPath(project) | Le formulaire à proposer sur la feuille de route d'un projet de Projets ({ id }), ou null. Celui du projet de la rétroaction qui le nomme, seulement s'il est public et pas archivé. |
Côté navigateur
Publiée par client.js sous le même nom.
| Composant | Rôle |
|---|---|
ItemReportsSection | Les rapports liés à une fiche, avec leur type, leur statut et un lien vers leur page dans l'admin. Prend { itemId }. La page d'une fiche de Projets le montre dans sa section « Rapports liés ». Lit GET /admin/projects-module/items/:itemId/reports : sans feedback.view, il affiche « Rapports indisponibles ». |
Ce que ce module lit de Projets
Ce module lit l'intégration projects au moment de s'en servir : listProjects, getProject, listItems, getItem, getItems, createItem, itemsState, roadmapPath et, s'il existe, similarItems. Il vérifie d'abord son contract : une intégration projects d'un autre contrat est traitée comme absente, avec un avertissement unique dans le journal. Sa forme est documentée dans API de Projets.
Fonctions serveur et composants
@fraza-pro/module-feedback/server lit le suivi d'un rapport, les rapports du compte client connecté, les projets publics et les adresses des pages. @fraza-pro/module-feedback/public donne les appels du navigateur et les composants prêts à l'emploi. Les deux sont décrits, avec des exemples, dans Intégrer au site.