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

+13
View File
@@ -1,4 +1,5 @@
Feature: Paginated article listing
Superseded in part by article_list_localization: the flat envelope below is unchanged, and the optional parameters lang and author are added to it (see the Background); the story-grouped shape is a separate opt-in.
Superseded in part by roles_permissions_content_scope: the article routes are gated by the content.* permissions of the caller's role (content.create, content.update, content.delete and content.publish; a refusal is 403 "missing_permission" with the header X-Missing-Permission), and content.read decides which drafts show: every draft at all, only the caller's own drafts at own, none otherwise. A draft the caller may not read answers 404 "Article not found", and a role that cannot be resolved is treated as holding no permission.
As an API consumer or the RenewCMS back-office
@@ -15,6 +16,10 @@ Feature: Paginated article listing
And q (optional, max 200 characters; case-insensitive, accent-insensitive, matches anywhere in the title) and status (optional, online or draft) filter the items
And total is the number of visible articles matching q and status, before paging; page and size echo the values applied
And counts ignores q and status, so the tab counts do not move while the user searches; it respects visibility: for a public caller all equals online and draft is 0
And this counts statement is true for the flat shape and is explicitly NOT true for the grouped shape, where counts follow lang, q, author, seo and site (features/article_list_localization.feature)
And the flat counts.suggestions rule is unchanged
And the flat envelope above is unchanged; a client opts in to the story-grouped editorial shape with group=story (features/article_list_localization.feature); public and unverified callers cannot
And lang (flat: narrows items, total and counts.all, counts.online and counts.draft to that language) and author (a byline id, flat: narrows by byline) are additive, optional, validated parameters; an invalid value answers 400 with apperr.BodyErrorMessage
And status=draft requested by a public caller matches nothing: items [], total 0
And sort (optional) is one of title:asc, title:desc, status:asc, status:desc, created_at:asc, created_at:desc, updated_at:asc, updated_at:desc; the default is updated_at:desc; status orders by is_online (asc puts drafts first); every ordering ends with id descending as the tie-break, so pages are stable and never overlap
And sort is mapped to a column and direction through a fixed whitelist in the repository, so request text never reaches the SQL; the same ordering applies on the search path (q) before slicing
@@ -176,6 +181,14 @@ Feature: Paginated article listing
| sort=updated_at |
| sort=title:asc;DROP TABLE |
| sort=TITLE:ASC |
| lang=english |
| lang=xx |
| lang= |
| author=0 |
| author=abc |
| author= |
| group=stories |
| tab=missing |
Scenario: Invalid format is reported first
When a client sends GET /v1/article/?page=0&format=pdf