Skip to content

Como Fazer: Publicação Agendada de Artigos ​

Referência FT: FT330 (NENE2-FT/pubschedulelog) — Ciclo de vida de rascunho/agendamento/publicação/arquivamento de artigos, acesso a rascunho somente para proprietário, artigos publicados públicos, gatilho de publicação agendada, 34 testes / 95 assertivas PASS.

Este guia mostra como construir um sistema de gerenciamento de artigos com publicação diferida: autores escrevem rascunhos, os agendam para um horário futuro, e um job em background (ou chamada de API) os faz transitar para publicado.

Schema ​

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',   -- draft | scheduled | published | archived
    publish_at TEXT,                               -- ISO-8601, NULL exceto quando agendado
    created_at TEXT    NOT NULL,
    updated_at TEXT    NOT NULL
);

Transições de Status ​

draft ──publish──► published ──archive──► archived
  │
  └──schedule──► scheduled ──(tempo passa)──► published
  │                  │
  │               unschedule
  │                  │
  └──────────────────┘

Apenas transições permitidas — transições inválidas retornam 409.

Endpoints ​

MétodoCaminhoDescrição
POST/articlesCriar rascunho (X-User-Id obrigatório)
GET/articles/{id}Obter (rascunho: somente proprietário; publicado: público)
PUT/articles/{id}Atualizar rascunho (X-User-Id obrigatório)
POST/articles/{id}/publishPublicar imediatamente
POST/articles/{id}/scheduleAgendar para horário futuro
POST/articles/{id}/unscheduleRetornar para rascunho
POST/articles/{id}/archiveArquivar artigo publicado
GET/articlesListar (com filtro ?status=)
POST/publish-dueDisparar artigos agendados com publish_at passado

Criar Rascunho ​

php
POST /articles  X-User-Id: 1
{"title": "Hello", "body": "World"}
→ 201  {"id": 1, "status": "draft", "author_id": 1}

// Sem autenticação → 401

Regras de Visibilidade ​

php
// Rascunho: somente proprietário
GET /articles/1  X-User-Id: 1  → 200   // autor vê próprio rascunho
GET /articles/1  X-User-Id: 2  → 404   // outro usuário não consegue ver rascunho
GET /articles/1               → 404   // sem auth, rascunho oculto

// Publicado: qualquer um
GET /articles/1               → 200   // público

Publicar e Arquivar ​

php
POST /articles/1/publish  X-User-Id: 1  → 200  {"status": "published"}
POST /articles/1/archive  X-User-Id: 1  → 200  {"status": "archived"}

// Não é possível arquivar um rascunho
POST /articles/1/archive  X-User-Id: 1  → 409

Agendar ​

php
// Agendar para 1 hora a partir de agora
POST /articles/1/schedule  X-User-Id: 1
{"publish_at": "2026-05-27T15:00:00+09:00"}
→ 200  {"status": "scheduled", "publish_at": "2026-05-27T15:00:00+09:00"}

// Horário passado → 422
POST /articles/1/schedule  X-User-Id: 1
{"publish_at": "2020-01-01T00:00:00Z"}
→ 422

// Cancelar agendamento → volta para rascunho
POST /articles/1/unschedule  X-User-Id: 1
→ 200  {"status": "draft", "publish_at": null}

Disparar Artigos Agendados ​

Um cron job ou endpoint admin faz a transição de todos os artigos agendados com publish_at <= now:

php
POST /publish-due
→ 200  {"published_count": 3}

Listar Artigos ​

php
GET /articles?status=published      → 200  // público, sem auth necessária
GET /articles?status=draft  X-User-Id: 1  → 200  // apenas próprios rascunhos

O Que NÃO Fazer ​

AntipadrãoRisco
Mostrar rascunho para usuário não autenticadoVaza conteúdo não publicado
Permitir agendamento no passadoArtigo publicaria "imediatamente" via job de disparo, contornando revisão
Usar now() do relógio de parede no teste para o gatilho de agendamentoTestes se tornam dependentes do tempo; use force-insert com publish_at passado nos testes
Hard-delete no arquivamentoPerde trilha de auditoria; use campo de status
Permitir transição de archived → publishedTraz de volta conteúdo removido; exigir re-publicação explícita

Publicado sob a licença MIT.