Aller au contenu

API

Routes HTTP du module Projets sous /za/api/projects, et l'intégration projects que lisent les autres modules.

Les routes sont servies sous /za/api/projects. Le routeur du core applique la session, la permission, la limite de débit et la vérification d'origine. Seule la route de désabonnement se passe de cette dernière : un client de messagerie la sollicite sans en-tête d'origine, et la signature de son lien en tient lieu. Un refus que la personne peut corriger répond un message en français, et parfois un code pour les cas qu'un appelant traite à part.

Le projet d'une requête

Une route d'admin qui travaille dans un projet le nomme dans son chemin : /admin/projects/:project/items. :project se lit d'abord comme un slug, puis comme un identifiant : un projet nommé « 2026 », de slug 2026, s'ouvre par son slug. Un projet inconnu répond 404.

Les routes qui visent une fiche, une mise à jour, une colonne, une étiquette ou un jalon par son :id n'en ont pas besoin : l'objet connaît son projet.

Une route publique lit ?projet=<slug>. Sans lui, c'est le projet mis en avant, ou le premier projet public et actif, dans l'ordre, quand il est interne. Un projet inconnu, interne ou archivé répond 404.

Admin : projets

MéthodeCheminPermissionRôle
GET/admin/projectsprojects.view{ projects }, le projet mis en avant d'abord, puis dans l'ordre. Chacun { id, slug, name, description, template, position, isPublic, isFeatured, archivedAt, isArchived, createdAt }, avec itemCount, doneCount, openCount, overdueCount et lastActivityAt. ?archived=1 : les archivés seulement ; ?archived=0 : les actifs.
POST/admin/projectsprojects.options{ name, description, template, isPublic }. template vaut roadmap (par défaut), development ou support, et donne les colonnes de départ. Le premier projet actif du site est mis en avant. Code project_taken quand un autre projet est enregistré au même instant : « Un autre projet vient d'être enregistré en même temps : réessayer. »
PUT/admin/projects/orderprojects.options{ ids } : chaque projet, une fois, dans le nouvel ordre.
GET/admin/projects/:projectprojects.view{ project }.
PATCH/admin/projects/:projectprojects.optionsname, description, isPublic, isFeatured. Le slug ne change pas. isFeatured: true met le projet en avant et retire la mention à l'autre ; code project_archived pour un projet archivé. isFeatured: false sur le projet mis en avant répond 409 avec le code project_featured : « Un projet reste toujours mis en avant : mettre un autre projet en avant à sa place. »
DELETE/admin/projects/:projectprojects.optionsUn projet sans fiche (code project_not_empty). Ses colonnes, ses étiquettes et ses jalons partent avec lui. S'il était mis en avant, le projet actif suivant l'est, les publics d'abord, puis dans l'ordre.
POST/admin/projects/:project/archiveprojects.optionsArchive le projet et lui retire la mise en avant : le projet actif suivant l'est, les publics d'abord, puis dans l'ordre. Ses fiches restent.
POST/admin/projects/:project/restoreprojects.optionsLe rend actif. Il est mis en avant si aucun projet ne l'est.
GET/admin/projects/:project/overviewprojects.view{ project, summary, activity }. summary : total, done, columns ({ id, name, isDone, itemCount }), overdue (les dix premières fiches en retard, l'échéance la plus ancienne d'abord), overdueCount (toutes les fiches en retard) et nextMilestone (le jalon ouvert à l'échéance la plus proche, { id, name, dueDate, itemCount, doneCount }, ou null). activity : les quinze derniers évènements des fiches du projet, chacun avec item: { id, title }.

Admin : fiches

MéthodeCheminPermissionRôle
GET/admin/projects/:project/itemsprojects.view{ project, items, columns, tags, milestones, assignees, truncated }. assignees : les comptes assignés à au moins une fiche du projet, par nom, ce que proposent les filtres. Voir plus bas pour les filtres et la forme d'une fiche. 1000 fiches au plus : truncated vaut true quand la liste s'est arrêtée à cette limite.
POST/admin/projects/:project/itemsprojects.managetitle, description (Markdown), columnId (la colonne par défaut sans lui), isPublic, tagId, shippedNote, kind, priority, dueDate (AAAA-MM-JJ), milestoneId. Répond { item }. Code no_column quand le projet n'a aucune colonne, project_archived dans un projet archivé.
GET/admin/my-itemsprojects.view{ items } : les fiches ouvertes assignées à la personne connectée, dans tous les projets actifs, l'échéance la plus proche d'abord. 500 au plus.
GET/admin/items/:idprojects.view{ item, updates, events } : la fiche, toutes ses mises à jour (la plus récente d'abord) et son historique (le plus ancien d'abord).
PATCH/admin/items/:idprojects.manageLes mêmes champs. L'entrée dans une colonne qui prévient envoie les courriels ; announced les compte. Code item_refused quand un autre module refuse le changement, project_archived pour une fiche d'un projet archivé. projectId déplace la fiche vers la colonne par défaut de ce projet, sans étiquette, jalon ni place dans le tableau, avec ses votes, ses abonnés, ses mises à jour, ses responsables et son historique, sans courriel. Avec d'autres champs, la fiche est déplacée d'abord, puis les autres champs s'appliquent, lus dans le nouveau projet.
DELETE/admin/items/:idprojects.manageSupprime la fiche, ses votes, ses abonnés, ses mises à jour, ses responsables et son historique.
POST/admin/items/:id/mergeprojects.manage{ intoId } : fusionne la fiche, un doublon, dans intoId, puis la supprime. Répond { item }, la fiche gardée, son voteCount recompté. Voir Fusionner. 400 quand intoId manque ou vaut :id, 404 pour une fiche inconnue. Codes merge_other_project pour deux projets différents, project_archived, item_refused quand le module Rétroaction refuse.
GET/admin/items/:id/votesprojects.view{ voters, anonymousCount, followerCount }. voters : les comptes qui ont voté, le plus récent d'abord, chacun { id, name, image, votedAt }, sans adresse, « Compte supprimé » pour un compte disparu. anonymousCount : les votes sans compte, jamais listés. followerCount : les comptes qui suivent la fiche.
DELETE/admin/items/:id/votes/:userIdprojects.manageRetire le vote de ce compte, recompte vote_count, écrit l'évènement vote_removed. Répond { voteCount }. 404 « Ce vote n'existe plus. » ; code project_archived.
PUT/admin/items/:id/assigneesprojects.manage{ userIds } : tous les responsables de la fiche, dix au plus. Une personne ajoutée doit avoir un compte avec projects.view, sinon 400 « Une des personnes choisies n'existe plus ou n'a pas accès aux projets. » Une personne déjà assignée reste, même si elle a perdu cet accès. Notifie les personnes ajoutées. Répond { item, events }.
GET/admin/accountsprojects.manage{ accounts } : les comptes qui ont projects.view et dont le nom ou l'adresse contient ?search=, vingt au plus, chacun { id, name, email, image }.
POST/admin/items/:id/updatesprojects.manage{ kind: 'public' | 'internal', body, notify }. notify envoie une mise à jour publique par courriel ; announced les compte.
PATCH/admin/updates/:idprojects.managebody, kind.
DELETE/admin/updates/:idprojects.manageSupprime la mise à jour.
POST/admin/imagesprojects.manageUne image de description, en multipart/form-data (champ file) : JPG, PNG, GIF ou WebP, 5 Mo au plus. Répond son adresse publique, url. 503 sans stockage configuré.

Un projet archivé

Les fiches d'un projet archivé sont en lecture. Créer une fiche, la modifier, changer ses responsables, publier, modifier ou supprimer une de ses mises à jour, ou déplacer une fiche vers ce projet répond 409 avec le code project_archived : « Ce projet est archivé : le restaurer pour modifier ses fiches. » Déplacer une fiche hors d'un projet archivé reste possible, comme la supprimer.

Les courriels comptés

announced compte les courriels mis dans la file d'envoi. Jusqu'à cinquante destinataires, ils y entrent avant la réponse, et le compte est exact : une adresse déjà prévenue ne compte pas. Au-delà, ils y entrent après la réponse, et announced est le nombre de destinataires, avant que la file écarte ceux déjà prévenus.

Filtrer et trier les fiches

ParamètreEffet
column, tagL'identifiant d'une colonne, d'une étiquette.
milestoneL'identifiant d'un jalon, ou none pour les fiches sans jalon.
assigneeL'identifiant d'un compte : les fiches qu'il porte.
kindtask, bug, idea ou improvement.
prioritylow, normal, high ou urgent.
open=1Hors des colonnes terminées.
overdue=1En retard : échéance passée, hors des colonnes terminées.
searchDans le titre et la description, sans tenir compte de la casse.
sortvotes (par défaut), board (l'ordre du tableau : les fiches placées par position, puis les autres par votes), updated, due (sans échéance à la fin), priority (de urgente à basse), created ou title.

Une valeur inconnue répond 400 « Filtre invalide. ».

Une fiche

Chaque fiche de l'admin porte id, slug, title, description, isPublic, kind, priority, dueDate, tasks ({ total, done }), project ({ id, slug, name, isPublic, isArchived }), column ({ id, key, name, isPublic, isDone, announce }), milestone ({ id, name, dueDate, isClosed } ou null), tag ({ id, name } ou null), shippedNote, voteCount, position (sa place au tableau, ou null), statusChangedAt, createdAt, updatedAt, createdBy, assignees (des comptes { id, name, email, image }), visible (la page publique la montre) et isOverdue. Dans la liste d'un projet, elle porte aussi reportCount (0 sans le module Rétroaction).

Un évènement de l'historique : { id, itemId, kind, actor, data, createdAt }, actor étant un compte ou null. Dans data, added et removed sont des comptes. Les types d'évènements sont listés dans Modèle de données.

Fusionner deux fiches

POST /admin/items/:id/merge fait tout dans une transaction :

  1. les votes de :id sont copiés vers intoId ; la copie d'un votant, compte ou navigateur, qui avait voté pour les deux est écartée ;
  2. ses abonnés sont copiés de même ;
  3. ses mises à jour passent à intoId, à leur date ;
  4. vote_count de intoId est recompté, son updated_at avancé, et l'évènement merged ({ from, votes, updates }) écrit dans son historique ;
  5. :id est supprimée, avec son historique, ses responsables et ce qui n'a pas été copié.

Quand intoId est publique, le module Rétroaction est d'abord interrogé par checkItemChange(:id, { isPublic: true }) : ses rapports vont se retrouver sur une fiche publique. Après la transaction, il entend onItemsMerged(:id, intoId), ou onItemDeleted(:id) s'il ne connaît pas ce crochet. Aucun courriel ne part.

Admin : jalons, colonnes, étiquettes

MéthodeCheminPermissionRôle
GET/admin/projects/:project/milestonesprojects.view{ milestones }, les ouverts d'abord par échéance, puis les fermés. Chacun { id, projectId, name, description, dueDate, position, isClosed, closedAt, itemCount, doneCount }.
POST/admin/projects/:project/milestonesprojects.manage{ name, description, dueDate }. Le jalon s'ajoute à la fin.
PATCH/admin/milestones/:idprojects.managename, description, dueDate, et closed (true ferme, false rouvre).
DELETE/admin/milestones/:idprojects.manageSupprime le jalon. Ses fiches restent, sans jalon.
GET/admin/milestones/:id/notesprojects.view{ milestone, markdown, internalCount } : les notes de version du jalon, faites des fiches publiques terminées. internalCount compte les fiches internes terminées laissées de côté. Voir Administration.
GET/admin/projects/:project/columnsprojects.view{ columns }, dans leur ordre, avec leurs réglages et leur nombre de fiches (itemCount).
POST/admin/projects/:project/columnsprojects.optionsname, isPublic, isDefault, isDone, announce, announceSubject, announceMessage. La colonne s'ajoute à la fin.
PUT/admin/projects/:project/columns/orderprojects.options{ ids } : chaque colonne du projet, une fois, dans le nouvel ordre.
PUT/admin/columns/:id/items/orderprojects.manage{ ids } : les fiches de la colonne dans leur ordre au tableau, la première en haut, chacune une fois, 1000 au plus. Elles prennent les places 1, 2, 3… ; les fiches de la colonne absentes de la liste perdent leur place. 400 pour une liste vide ou en double, 409 avec le code board_changed quand une fiche n'est plus dans la colonne, project_archived. Ne touche ni updated_at ni l'historique.
PATCH/admin/columns/:idprojects.optionsLes mêmes champs. Code column_default pour retirer « par défaut » à la colonne qui l'est.
DELETE/admin/columns/:idprojects.optionsUne colonne vide qui n'est pas celle par défaut (codes column_not_empty, column_default).
GET/admin/projects/:project/tagsprojects.view{ tags }.
POST/admin/projects/:project/tagsprojects.options{ name }. Code tag_taken pour un nom déjà pris.
PATCH/admin/tags/:idprojects.options{ name }.
DELETE/admin/tags/:idprojects.optionsSupprime l'étiquette.

Public

MéthodeCheminLimiteRôle
GET/public/projectsapiLes projets publics et actifs, le projet mis en avant d'abord : { projects }, chacun { slug, name, description, isFeatured }.
GET/public/roadmapapiLe projet (project, { slug, name, description, isFeatured }), ses colonnes publiques dans leur ordre (columns, chacune { key, name, done, total, items }), ses étiquettes (tags), l'état du vote (vote) et le formulaire du module Rétroaction pour ce projet (formPath, ou null). Filtre etiquette. Chaque fiche porte voted. total compte les fiches publiques de la colonne, au-delà de celles montrées.
GET/public/columns/:keyapiUne colonne publique entière, 50 fiches par page : { column: { key, name, done }, items, total, page, pageCount }. ?page=, ?etiquette=. Une colonne terminée met les dernières livrées d'abord, une autre les plus votées. 404 pour une colonne inconnue ou interne.
GET/public/changelogprojects_feedLe journal : { project, milestones, total, page, pageCount }, 20 jalons par page, ?page=. Chaque jalon : { name, anchor, description, dueDate, closedAt, sections: [{ title, items }] }. Voir Le journal.
GET/public/changelog.rssprojects_feedLe même journal en RSS 2.0 (application/rss+xml), les 20 derniers jalons, mis en cache dix minutes (Cache-Control: public, max-age=600). Chaque <item> : le nom du jalon, un lien vers son ancre dans le journal, sa date de fermeture, sa description et ses fiches en HTML échappé, et une <category> par type de fiche. Les liens suivent FRAZA_MODULE_PROJECTS_PUBLIC_PATH.
GET/public/items/:slugapiUne fiche publique (item, avec voted, ses mises à jour publiques, updates, et following : le compte client connecté la suit, null sans compte client) et l'état du vote.
GET/public/images/:nomapiUne image de description.
POST/public/items/:slug/voteprojects_voteVote. Répond voted et voteCount. Code login_required quand il faut un compte client, vote_off quand le vote est fermé. Seul un compte client vote en son nom : une autre session vote comme un visiteur. Sans compte, en mode anyone, dix votes par fiche et par adresse réseau chaque jour : au-delà, 429 avec le code vote_limit, « Trop de votes pour cette fiche depuis ce réseau aujourd'hui. »
DELETE/public/items/:slug/voteprojects_voteRetire le vote.
POST/public/items/:slug/followprojects_followLe compte client connecté suit la fiche, et son adresse se réinscrit si elle s'était désabonnée. Répond { following: true }. Sans session, 401. Un compte d'équipe, ou tout compte sur un site sans comptes clients : 403, code client_account_required, « Le suivi d'une fiche demande un compte client. »
DELETE/public/items/:slug/followprojects_followLe compte client ne la suit plus : { following: false }. Mêmes refus.
POST/public/unsubscribeprojects_unsubscribeDésabonne l'adresse d'un lien de courriel. c et t se lisent dans l'adresse de la requête, ou dans un corps application/x-www-form-urlencoded ou JSON, 4 Ko au plus. Accepte la requête « un clic » de la RFC 8058 (le lien dans l'adresse, List-Unsubscribe=One-Click en corps). Répond { success: true }, même pour une adresse déjà désabonnée ; 400 avec le code unsubscribe_invalid pour un lien modifié ou forgé. Sans vérification d'origine.

La feuille de route, la colonne, le journal, la fiche, le vote et le suivi lisent ?projet=<slug>. Les budgets des limites sont dans Configuration. Une fiche publique n'expose ni ses responsables, ni son échéance, ni sa priorité. Les fonctions serveur et les composants pour le site sont décrits dans Intégrer au site.

L'intégration projects

Le module publie, par @fraza/core/integrations, un objet de fonctions serveur sous le nom projects. Un autre module le lit par getIntegration('projects'), sans importer ce paquet, et reçoit null quand Projets est absent ou désactivé. Voir Intégrations entre modules. Le module Rétroaction s'en sert pour reprendre les projets et lier ses rapports aux fiches.

C'est un contrat : sa forme ne change qu'avec les modules qui l'appellent. L'objet porte contract, le numéro de ce contrat. Un module qui en parle un autre ignore l'intégration, comme si Projets était absent, avec un avertissement unique dans le journal.

FonctionRend
contractPas une fonction : le numéro du contrat, 1.
listProjects()Tous les projets, archivés compris, le projet mis en avant d'abord : [{ id, slug, name, isPublic, isFeatured, isArchived }].
getProject(id)Un projet sous la même forme, ou null.
listItems({ projectId, search, open })Les fiches d'un projet, la dernière modifiée d'abord, sous la forme de getItem. search cherche dans le titre et la description ; open écarte les colonnes terminées. Sans projectId, le projet mis en avant.
getItem(id){ id, slug, title, isPublic, visible, kind, project: { id, slug, name }, column: { id, name, isDone, isPublic }, adminPath, publicPath }, ou null. adminPath vaut /admin/projects/<projet>/fiches/<id>. publicPath est la page du module (/za/projects/<projet>/<fiche>), qui redirige vers celle du site quand elle existe, ou null quand le site ne montre pas la fiche.
getItems(ids)La même forme, pour les identifiants qui existent.
createItem(input, { projectId, author, client })Crée une fiche dans la colonne par défaut du projet. input : { title, description, isPublic, kind }. Avec client, dans la transaction de l'appelant : la fiche et ce qui la crée sont enregistrés ensemble, ou pas du tout. L'historique note sa création. Rend la fiche sous la forme de getItem.
itemsState(ids)[{ id, isDone, statusChangedAt, visible }] : de quoi savoir si une fiche est terminée, et depuis quand. visible : la fiche, sa colonne et son projet sont publics, et le projet n'est pas archivé.
roadmapPath(projectId)L'adresse de la feuille de route publique d'un projet (/za/projects/<projet>), ou null pour un projet interne ou archivé.
similarItems({ projectId, text, limit })Les fiches publiques d'un projet qui partagent des mots avec text, limit au plus (3 par défaut, 5 au plus) : [{ id, slug, title, voteCount, column: { name, isDone }, path }]. Recherche plein texte en français dans le titre et la description, le titre comptant davantage ; la plus proche d'abord, puis la plus votée. path est l'adresse de la fiche sur le site (FRAZA_MODULE_PROJECTS_PUBLIC_PATH). Venue après le contrat 1 et facultative : l'appelant vérifie qu'elle existe. Le formulaire de Rétroaction s'en sert pour « Déjà sur la feuille de route ? ».

Aucune de ces fonctions ne rend la description, les responsables, l'échéance ni la priorité d'une fiche. Seule similarItems rend le nombre de votes.

Ce que le module lit de l'intégration feedback

Dans l'autre sens, Projets appelle les fonctions que le module Rétroaction publie sous feedback, quand elles existent. Il vérifie d'abord son contract : une intégration feedback qui ne déclare pas le contrat 1 est ignorée, avec un avertissement unique dans le journal. Chaque fonction est facultative : absente, Projets fait sans. Une erreur dans l'une d'elles est journalisée, et le changement, déjà enregistré, reste.

FonctionAppeléeEffet attendu
contractPas une fonction : lu avant toute autre.1. Un autre numéro, ou aucun, et Projets fait comme si Rétroaction était absente.
announceRecipients(itemId)Avant un courriel de colonne ou de mise à jour.Des adresses de plus. Chacune ne reçoit qu'un courriel.
checkItemChange(itemId, { isPublic: true })Avant de rendre une fiche publique, et avant de fusionner itemId dans une fiche publique.Un refus en français, ou null. Un refus répond le code item_refused.
countReports(itemIds)En listant les fiches d'un projet.{ [itemId]: nombre }, lu comme reportCount.
onItemMoved(itemId, projectId)Après le déplacement d'une fiche vers un autre projet.
onItemsMerged(fromId, intoId)Après la fusion de fromId dans intoId, une fois fromId supprimée.Les rapports liés à fromId le sont désormais à intoId. Sans ce crochet, Projets appelle onItemDeleted(fromId) à la place.
onItemDeleted(itemId)Après la suppression d'une fiche.
onProjectSaved(project)Après la création d'un projet, sa modification, son archivage ou sa restauration. Aussi pour le projet qui devient mis en avant après un archivage, une suppression ou un changement de mise en avant : le nouveau projet mis en avant d'abord. project : { id, slug, name, description, template, position, isPublic, isFeatured, archivedAt, isArchived, createdAt }.Rétroaction tient à jour son projet miroir.
onProjectDeleted(projectId)Après la suppression d'un projet.Rétroaction retire son projet miroir, ou le garde archivé s'il contient des rapports.
formPath(project)En rendant la feuille de route publique.L'adresse d'un formulaire pour ce projet, ou null.
ItemReportsSectionSur la page d'une fiche, côté navigateur.Un composant, rendu avec { itemId, Section } : Section est le cadre d'un bloc de réglages, que le composant utilise pour s'afficher, ou rend null. Publié par le client.js de l'autre module.

onItemsMerged est venu après le contrat 1 : facultatif, il ne change pas son numéro.

Ce que fait Rétroaction de chacune est décrit dans Avec Rétroaction.

Sur cette page