Skip to content

How-to : Workflow Brouillon → Publication → Archive

Référence FT : FT305 (NENE2-FT/draftlog) — Machine à états du cycle de vie d'article : transitions one-way draft→published→archived, accès en écriture auteur uniquement, les non-auteurs voient uniquement les articles publiés (les brouillons retournent 404), impossible de modifier les articles publiés, la liste publiée exclut les brouillons et archives, 20 tests / 28 assertions PASS.

Ce guide montre comment implémenter un cycle de vie de contenu où les articles démarrent comme brouillons, sont publiés pour devenir visibles, et peuvent être archivés pour les retirer des listes publiques.

Schéma

sql
CREATE TABLE articles (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    author_id    INTEGER NOT NULL,
    title        TEXT    NOT NULL,
    body         TEXT    NOT NULL DEFAULT '',
    status       TEXT    NOT NULL DEFAULT 'draft',
    published_at TEXT,
    archived_at  TEXT,
    created_at   TEXT    NOT NULL,
    updated_at   TEXT    NOT NULL,
    CHECK (status IN ('draft', 'published', 'archived')),
    FOREIGN KEY (author_id) REFERENCES users(id)
);

CHECK (status IN (...)) garantit que seuls les états connus sont stockés. Les horodatages published_at et archived_at enregistrent quand les transitions ont eu lieu.

Machine à états

draft ──(POST /publish)──▶ published ──(POST /archive)──▶ archived
TransitionPréconditionErreur si violée
draft → publishedle statut doit être 'draft'422
published → archivedle statut doit être 'published'422
published → draft❌ non autorisé
archived → quoi que ce soit❌ non autorisé
php
// Gestionnaire de publication
if ($article['status'] !== 'draft') {
    return $this->responseFactory->create(['error' => 'only draft articles can be published'], 422);
}

// Gestionnaire d'archivage
if ($article['status'] !== 'published') {
    return $this->responseFactory->create(['error' => 'only published articles can be archived'], 422);
}

Endpoints

MéthodeCheminAuthDescription
POST/articlesX-User-IdCréer un article (démarre comme brouillon)
GET/articlesLister les articles publiés uniquement
GET/articles/{id}X-User-IdObtenir un article (vérification de visibilité)
PUT/articles/{id}X-User-Id (auteur)Mettre à jour le brouillon (uniquement si brouillon)
POST/articles/{id}/publishX-User-Id (auteur)Publier
POST/articles/{id}/archiveX-User-Id (auteur)Archiver

Les nouveaux articles démarrent comme brouillons

php
$id = $this->repo->create($actorId, $title, $body);
return $this->responseFactory->create(['id' => $id, 'status' => 'draft'], 201);

Le status est toujours 'draft' à la création quels que soient les champs du corps. Le client ne peut pas choisir le statut initial.

Visibilité — Les non-auteurs voient uniquement les publiés

php
// Les non-auteurs peuvent uniquement voir les articles publiés
if ($article['status'] !== 'published' && (int) $article['author_id'] !== $actorId) {
    return $this->responseFactory->create(['error' => 'not found'], 404);
}

Les articles non publiés (brouillon ou archivé) retournent 404 aux non-auteurs. Cela empêche :

  • D'autres utilisateurs de lire des brouillons non publiés
  • De révéler si un article a été archivé

Impossible de modifier les articles publiés

php
// Gestionnaire de mise à jour — seuls les brouillons sont modifiables
if ($article['status'] !== 'draft') {
    return $this->responseFactory->create(['error' => 'only draft articles can be edited'], 422);
}
if ((int) $article['author_id'] !== $actorId) {
    return $this->responseFactory->create(['error' => 'forbidden'], 403);
}

Une fois publié, le contenu de l'article est figé. L'auteur doit dépublier (non pris en charge ici) pour modifier — dans cette conception, la publication est une porte à sens unique.

Endpoint de liste — Publiés uniquement

php
// Repository : SELECT WHERE status = 'published' ORDER BY published_at DESC
$articles = $this->repo->listPublished();

L'endpoint de liste filtre sur status = 'published' uniquement. Les brouillons et les articles archivés n'apparaissent jamais dans la liste publique.

Actions auteur uniquement

Toutes les opérations d'écriture (update, publish, archive) vérifient que l'acteur est l'auteur de l'article :

php
if ((int) $article['author_id'] !== $actorId) {
    return $this->responseFactory->create(['error' => 'forbidden'], 403);
}

Ce qu'il ne faut PAS faire

Anti-patternRisque
Autoriser le statut dans le corps de créationLe client démarre l'article comme 'published' en contournant le workflow de révision
Retourner 403 pour GET brouillon par non-auteurRévèle que l'article existe ; utiliser 404 pour cacher le contenu non publié
Autoriser la modification des articles publiésModifie rétroactivement le contenu en direct ; viole la confiance des lecteurs
Autoriser la transition archive → publishedLes articles archivés réapparaissent de façon inattendue
Lister les brouillons dans la liste publiqueDu contenu non publié est exposé avant d'être prêt
Pas de CHECK (status IN (...))Les insertions directes en DB peuvent définir des chaînes de statut arbitraires
Les articles archivés retournent 200 aux non-auteursIndique aux non-auteurs que le contenu existait et a été archivé

Publié sous licence MIT.