Modèle de données
Les tables fraza_projects_* du module Projets, ses migrations, les votes, les abonnés, les désabonnements, et les images des descriptions dans le stockage.
Toutes les tables sont préfixées fraza_projects_. Un compte est désigné par l'identifiant du compte du core, en texte, sans clé étrangère : un compte supprimé laisse son identifiant, et l'admin l'affiche « Compte supprimé ».
Chaque fiche, colonne, étiquette et jalon appartient à un projet. Les slugs des fiches, les clés des colonnes et les noms des étiquettes sont uniques dans leur projet.
fraza_projects_project
Un projet, avec son tableau, ses colonnes, ses étiquettes et ses jalons.
| Colonne | Contenu |
|---|---|
slug | Son adresse (/za/projects/<slug>, /admin/projects/<slug>), fixée à la création, unique. Jamais nouveau, suivi, mes-fiches, desabonnement ni journal. |
name | Son nom, 60 caractères au plus. |
description | En Markdown, 20 000 caractères au plus. |
template | Le modèle de ses colonnes de départ : roadmap, development ou support. |
position | Son rang dans la liste. |
is_public | Ses pages publiques existent. |
is_featured | Le projet mis en avant : celui que lit un site qui n'en nomme aucun. Un seul au plus, garanti par un index unique. Le module en garde toujours un parmi les projets actifs, tant qu'il en existe. |
archived_at | Le moment de son archivage, ou NULL pour un projet actif. Un projet archivé n'a plus de pages publiques ni de mise en avant. |
created_at | Sa création. |
createTables() crée le projet « Principal » (principal), mis en avant, avec les colonnes du modèle Feuille de route, seulement quand il crée la table fraza_projects_project. Un site qui a supprimé tous ses projets ne le retrouve pas à un fraza-db init suivant. Un projet ne se supprime que sans fiche : project_id est non nul, sans suppression en cascade, dans fraza_projects_item, _column et _tag. Ses jalons, eux, partent avec lui.
fraza_projects_column
Les colonnes d'un projet : project_id, key (tirée du nom à la création, lue par les sites, unique dans son projet), name, position, et leurs réglages is_public, is_default (une seule colonne par projet, garantie par un index unique), is_done, announce, announce_subject, announce_message. Une colonne qui contient des fiches ne se supprime pas.
fraza_projects_tag
Les étiquettes d'un projet : project_id, name, unique dans son projet sans tenir compte de la casse, et position. Supprimer une étiquette la retire des fiches (ON DELETE SET NULL).
fraza_projects_milestone
Les jalons d'un projet.
| Colonne | Contenu |
|---|---|
project_id | Son projet. Supprimé avec lui. |
name | Son nom, 80 caractères au plus. |
description | En texte simple, 2000 caractères au plus. Reprise en tête des notes de version. |
due_date | Son échéance, une date sans heure, ou NULL. |
position | Son rang, après l'échéance, dans la liste. |
closed_at | Le moment de sa fermeture, ou NULL pour un jalon ouvert. |
L'avancement d'un jalon n'est pas stocké : il se compte à la lecture, sur les fiches qui le nomment et celles d'entre elles dans une colonne terminée. Supprimer un jalon le retire des fiches (ON DELETE SET NULL).
fraza_projects_item
Une fiche.
| Colonne | Contenu |
|---|---|
project_id | Son projet. |
slug | Son adresse publique, fixée à la création, unique dans son projet. Jamais nouveau, suivi, mes-fiches, desabonnement ni journal. Un déplacement vers un projet où il est pris ajoute -2, -3… |
title, description | Ce que montre la page publique. La description est en Markdown, 20 000 caractères au plus. |
column_id | Sa colonne, du même projet. |
is_public | Visible sur la page publique, quand sa colonne et son projet le sont aussi, et que le projet n'est pas archivé. |
tag_id | Son étiquette, du même projet. |
milestone_id | Son jalon, du même projet, ou NULL. |
kind | Son type : task, bug, idea ou improvement. task par défaut. |
priority | Sa priorité : low, normal, high ou urgent. normal par défaut. |
due_date | Son échéance, une date sans heure, ou NULL. Dépassée hors d'une colonne terminée, la fiche est en retard. |
task_total, task_done | Les sous-tâches de la description (- [ ], - [x]), comptées à chaque enregistrement de la description : une carte affiche « 3/5 » sans relire le texte. |
shipped_note | La note de livraison, 200 caractères au plus. |
vote_count | Le nombre de votes, tenu à jour dans la même transaction que chaque vote : la feuille de route se trie sans compter. Une fusion et le retrait d'un vote par l'équipe le recomptent. |
position | Sa place dans sa colonne du tableau, de 1 en haut, posée par l'équipe. NULL pour une fiche qui vient d'entrer dans sa colonne : elle se range après les fiches placées, par votes. Remis à NULL à chaque changement de colonne ou de projet. Index (column_id, position). La feuille de route publique ne le lit pas. |
created_by | Le compte de l'équipe qui l'a créée, ou NULL. |
status_changed_at | Le dernier changement de colonne ou de projet. Sert à l'ordre d'une colonne terminée et des notes de version. |
created_at, updated_at | Sa création et sa dernière modification, responsables compris. |
fraza_projects_item_assignee
Les responsables d'une fiche : item_id, user_id, assigned_at. Une ligne par compte et par fiche, dix au plus par fiche. Supprimées avec leur fiche.
fraza_projects_item_event
L'historique d'une fiche, écrit dans la transaction de chaque changement : item_id, kind, actor_id (le compte de l'équipe qui a fait le changement), data en JSON, created_at. Supprimés avec leur fiche.
kind | data |
|---|---|
created | { column }, le nom de la colonne |
column | { from, to }, les noms des colonnes |
project | { from, to }, les noms des projets |
assigned | { added, removed }, les identifiants des comptes |
due | { from, to }, des dates AAAA-MM-JJ ou null |
priority | { from, to } |
kind | { from, to } |
milestone | { from, to }, les noms des jalons, ou null |
visibility | { public } |
title | { from, to } |
merged | { from, votes, updates }, le titre de la fiche fusionnée dans celle-ci, les votes et les mises à jour repris |
vote_removed | { removed }, l'identifiant du compte dont l'équipe a retiré le vote |
Les noms sont copiés au moment du changement : renommer une colonne ou un jalon ne réécrit pas l'historique.
fraza_projects_item_update
Les mises à jour d'une fiche : item_id, kind (public ou internal), body en Markdown (20 000 caractères au plus), author_id (le compte de l'équipe), created_at, updated_at. Supprimées avec leur fiche.
fraza_projects_vote
Un vote : item_id, et soit user_id (un compte), soit voter_hash (l'empreinte SHA-256 du cookie d'un navigateur), jamais les deux, et created_at. Un seul vote par votant et par fiche, garanti par deux index uniques partiels. Supprimés avec leur fiche. Une fusion copie les votes du doublon vers l'autre fiche : la copie d'un votant qui avait voté pour les deux est écartée par ces index.
fraza_projects_follower
Un compte qui suit une fiche sans forcément voter : item_id, user_id, created_at. Clé primaire (item_id, user_id), index sur user_id. Supprimés avec leur fiche. Ajoutée par la migration 002-followers-and-optouts.
fraza_projects_email_optout
Une adresse qui ne veut plus les courriels du module : email_hash, l'empreinte SHA-256 en hexadécimal de l'adresse en minuscules et sans espaces autour, clé primaire, et created_at. L'adresse elle-même n'est jamais gardée. Une ligne disparaît quand le compte de cette adresse suit une fiche. Ajoutée par la migration 002-followers-and-optouts. Voir Se désabonner.
Les images des descriptions
Rangées dans le stockage sous projects/images/, avec un nom tiré au hasard, et servies à tous par GET /za/api/projects/public/images/<nom>. Cette route ne sert que ce dossier, et seulement un nom de la forme que le module écrit. Aucune table ne suit les images : le texte des descriptions et des mises à jour dit lesquelles servent.
fraza_projects_storage_deletion
La file des objets à retirer du stockage. Chaque heure, la tâche projects-images y inscrit les images du dossier qu'aucun texte ne nomme et qui y sont depuis plus d'une journée, puis vide la file. Avant, elle retire de la file une image qu'un texte nomme de nouveau, recollée par exemple : elle reste dans le stockage. Un objet que le stockage refuse reste dans la file avec son erreur, et est retenté plus tard, avec un délai qui double jusqu'à une journée.
dropTables() supprime les tables, pas les images du stockage.
Migrations
createTables() crée le schéma courant, et fraza-db init pose une ligne 000-baseline après lui. fraza-db migrate applique ensuite les migrations dans l'ordre, chacune une fois. Sur une base neuve, elles trouvent leur changement déjà fait par createTables() et passent sans rien toucher. Voir Écrire une migration.
| Migration | Effet |
|---|---|
001-item-position | Ajoute position à fraza_projects_item, à NULL pour toutes les fiches, et son index. Chaque colonne garde son ordre par votes tant que l'équipe n'y place aucune carte. |
002-followers-and-optouts | Crée fraza_projects_follower et fraza_projects_email_optout. |
003-reserve-new-segments | Renomme une fiche dont le slug est journal ou desabonnement en <slug>-fiche, ou <slug>-fiche-<id> quand ce slug est déjà pris dans son projet, et un projet desabonnement en desabonnement-projet, ou desabonnement-projet-<id>. Ces adresses publiques changent : les pages du journal et du désabonnement les prennent. |
Une base déployée avant ces migrations les reçoit à son prochain npx fraza-db migrate.