feat: editorial article list

This commit is contained in:
Florian Sylvain committed 2026-11-02 07:28:44 +01:00
1 parent a0b3c58c52
commit a671b81f80
103 files changed
+6493 -1805

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
The full, searchable reference is the Integration page of the running app (`/integration`). It
renders `web/src/data/apiCatalogue.ts`, which is the single source for this page. This file is for
readers without the UI. Reading articles (pagination, `format`, body schemas) has its own page,
[api/articles.md](api/articles.md), versions, translations and the story move have [api/article-localization.md](api/article-localization.md), site languages and the one-time language setup are in [api/site-languages.md](api/site-languages.md), and API keys are covered in [api/api-keys.md](api/api-keys.md).
[api/articles.md](api/articles.md), versions, translations, the story move and the story-grouped editorial list (`GET /v1/article/?group=story`) have [api/article-localization.md](api/article-localization.md), site languages and the one-time language setup are in [api/site-languages.md](api/site-languages.md), and API keys are covered in [api/api-keys.md](api/api-keys.md).
## Base URL
The origin that serves the app, for example `https://cms.example`. Every path starts with `/v1`,
+32
View File
@@ -160,6 +160,38 @@ Verified session or API key. The caller needs `content.update` on every version
Everything is checked inside the transaction, under the locks of both sites, and nothing moves when something fails: every language of every version (readable or not) must be enabled at the destination (`422 language_not_enabled`, `X-Missing-Languages` limited to languages of readable versions, a language disabled at the destination counts as missing, a language disabled at the source does not matter), and no address may exist at the destination in the same language (`409 slug_conflict`, `X-Conflicting-Articles` limited to readable versions). A conflict that appears between the check and the write answers `409 slug_conflict` without the header.
## Editorial list by story: `GET /v1/article/?group=story`
An opt-in shape of the list for a signed-in, verified caller whose role resolves. Without `group` the response keeps the flat envelope `{ items, total, page, size, counts }` of the paginated list, whose `counts` are `all`, `online`, `draft` and `suggestions`. A caller without a valid session, or with an unverified user, who sends `group` gets 400 with the body-mismatch message and nothing is read.
| Parameter | Meaning |
| --- | --- |
| `group` | Only `story`. `group=`, any other value, or `group` from a public caller is 400. |
| `lang` | A supported language tag, normalized like the language of a version (`fr`, `pt-br` becomes `pt-BR`). Empty, malformed or unsupported is 400. Also narrows the flat list (`items`, `total`, `counts.all`, `counts.online`, `counts.draft`). Disabled languages can be selected. |
| `tab` | `all` (default) or `missing`. `review`, an empty value, any other value, and `missing` without `group=story` are 400. |
| `author` | A byline id, an integer of at least 1; anything else is 400. Also narrows the flat list. |
| `page`, `size`, `q`, `status`, `site`, `seo`, `sort` | Same values, defaults and validation as the flat list. |
Only versions the caller can read count: they are titled, credited, listed in `coverage`, searched and counted. A hidden version never appears, and a story whose versions are all hidden has no row.
**Rows.** With no `lang` a row is a story: `total` counts stories. With `lang` a row is the version of that language (`tab=all`), and the whole story's `coverage`, `story_authors` and `missing_langs` still ride on the row. With `tab=missing` rows are stories again: `lang=fr` lists the stories on a site that enables `fr` and that have a readable version but none in `fr`; without `lang` it lists the stories that lack at least one enabled language, and a caller who cannot create (no `content.create`) gets `total` 0. Articles without language (`lang` `""`) are one story each and are never listed as missing a language. A version of the story must satisfy `status`, `author`, `q` and `seo` together to make the story match; `q` ignores case and accents.
**Item.** The article list item of the representative version (the one in `lang`, otherwise the site default language version, otherwise the first readable one by language tag), plus:
| Field | Type | Meaning |
| --- | --- | --- |
| `story_updated_at` | RFC 3339 | The latest `updated_at` among the readable versions of the story |
| `story_authors` | array of bylines | The union of the bylines of the readable versions, without duplicates |
| `coverage` | array | One entry per readable version, in the order of `translations` (default language, other enabled tags, disabled tags): `{ id, lang, title, is_online, language_enabled, capabilities }` |
| `missing_langs` | array of tags | Enabled languages without a readable version, default first then by tag; `[]` when the caller cannot create |
| `capabilities` | array | The capabilities of the representative version |
**Sort.** The whitelist is the flat one. `updated_at` uses the latest readable version of the story, `created_at` the earliest, `title` and `status` the representative; with `lang` the version itself. Ties end with `story_id` descending.
**Counts.** `{ all, online, draft, suggestions, missing }`. They follow `lang`, `q`, `author`, `seo` and `site` but never `status` or `tab`, and count the unit of the mode (stories, or versions of the selected language). `online` and `draft` count the units with a readable online or draft version. `missing` is the `total` the not-in view would give under the same filters (0 without `lang` for a caller who cannot create). `suggestions` is the number of stories with a readable version whose `seo_status` is `suggestions`, ignoring every parameter.
A repository failure at any step, counts included, answers 500 `Failed to retrieve articles`.
## Mock-ready examples
Statuses of the new and changed calls. Bodies are plain text unless shown as JSON; headers listed are the only extra ones.