refac: article reads, story delete, no leaks, stored SEO status, rate limit, cleanup (%site%, article repo split, old lang repos, one default per site rule to domain)

This commit is contained in:
Florian Sylvain committed 2026-11-03 16:01:52 +01:00
1 parent 9d284e65a9
commit 7c15206170
85 files changed
+1788 -593

No files matched your search

+81 -3
View File
@@ -1,5 +1,5 @@
Feature: Capabilities, translations, publish rules, delete and story move
An article read tells the editor what the caller may do with the version and which readable sibling versions exist. Publishing respects disabled languages. Deleting the last version removes its story. A story moves to another site as a whole, atomically, never split.
An article read tells the editor what the caller may do with the version and which readable sibling versions exist. Publishing respects disabled languages. Deleting the last version removes its story, and a whole article can be deleted at once, atomically. A story moves to another site as a whole, atomically, never split. Nothing reveals a version the caller cannot read.
Background:
Given error responses are plain text, with extra data in headers
@@ -9,6 +9,8 @@ Feature: Capabilities, translations, publish rules, delete and story move
And "owner" holds every content permission at scope all
And "writer1" (user 1) holds content.create, content.read, content.update, content.publish and content.delete at scope own, and content.read at scope all for online articles only
And "reader" holds content.read at scope all only
And "restricted" holds content.create at scope all and content.read at scope own
And "deleter" holds content.delete at scope all and content.read at scope own
Scenario: Capabilities of the owner
When the owner sends GET /v1/article/12
@@ -30,8 +32,8 @@ Feature: Capabilities, translations, publish rules, delete and story move
Scenario: Translations list readable versions only
When writer1 sends GET /v1/article/10
Then "translations" has entries for 10, 12 and 13 and none for 11
And each entry has id, lang, title, is_online, language_enabled and capabilities
And no count, placeholder or total names the hidden version
And each entry has id, lang, title, is_online, language_enabled, excluded_from_public, source_article_id and capabilities
And no entry, count, placeholder or total names the hidden version
Scenario: Translations are ordered default language first, then by tag
When the owner sends GET /v1/article/12
@@ -52,6 +54,7 @@ Feature: Capabilities, translations, publish rules, delete and story move
When an anonymous client sends GET /v1/article/10
Then "translations" is []
And "capabilities" is ["read"]
And the response has no "missing_langs", "excluded_from_public", "source_unavailable" or "partial_view"
And the public shape is otherwise unchanged
Scenario: The source on the read
@@ -62,6 +65,47 @@ Feature: Capabilities, translations, publish rules, delete and story move
Then "source" is null
And a version created empty has "source": null
Scenario Outline: Missing languages are the enabled languages without a readable version
When <caller> sends GET /v1/article/10
Then "missing_langs" is <missing>
Examples:
| caller | missing |
| owner | [] |
| restricted | ["de","fr"] |
| reader | [] |
Scenario: A version in a disabled language or without language is left out of public surfaces
Given article 14 (es, offline, created by restricted) is in story 5 and es is disabled on journal
And article 30 has lang "" and its own story
When the owner sends GET /v1/article/14
Then "excluded_from_public" is true
And the translations entry of 14 has "excluded_from_public": true and the entry of 10 has false
And GET /v1/article/30 has "excluded_from_public": true
And GET /v1/article/10 has "excluded_from_public": false
Scenario: A deleted source and an unreadable source read the same
Given article 14 tracks article 12 at content revision 2
When the owner sends GET /v1/article/14
Then "source" is {"article_id":12,"revision":2} and "source_unavailable" is false
When the restricted user, who cannot read 12, sends GET /v1/article/14
Then "source" is null and "source_unavailable" is true
When article 12 is deleted and the owner sends GET /v1/article/14
Then "source" is null and "source_unavailable" is true
Scenario: Translations name the source they track only when it is readable
Given article 14 tracks article 12
When the owner sends GET /v1/article/10
Then the translations entry of 14 has "source_article_id": 12 and the entry of 10 has null
When the restricted user sends GET /v1/article/14
Then the translations entry of 14 has "source_article_id": null
Scenario: Partial view follows the read scope of the caller only
When the owner sends GET /v1/article/10
Then "partial_view" is false
When the restricted user sends GET /v1/article/13
Then "partial_view" is true, whether or not the story holds a version the restricted user cannot read
Scenario Outline: Publishing respects the language configuration
Given a valid jwt cookie for a user holding content.publish
When the client sends PATCH /v1/article/<id>/publish
@@ -136,6 +180,40 @@ Feature: Capabilities, translations, publish rules, delete and story move
Then the story remains
And nothing in the response mentions the other version
Scenario: Deleting an article removes every version and the article
When the owner sends DELETE /v1/story/5
Then the response status is 204 with an empty body
And articles 10, 11, 12 and 13 and story 5 are gone
And one article.delete event per version is recorded with params.lang and params.story_id 5
Scenario: Versions the caller cannot read are deleted with the article
When the deleter, who reads 10 and 13 only, sends DELETE /v1/story/5
Then the response status is 204
And articles 11 and 12 are deleted too
Scenario: Deleting an article needs delete permission on every version
When writer1, whose content.delete is at scope own, sends DELETE /v1/story/5
Then the response status is 403, the body is "missing_permission" and X-Missing-Permission is "content.delete"
And every version remains
And the answer is the same whether or not the story holds a version writer1 cannot read
Scenario: A failed delete removes nothing
Given the database fails while the versions are deleted
When the owner sends DELETE /v1/story/5
Then the response status is 500 and the body is "Failed to delete article"
And every version and story 5 remain
Scenario: An unknown or unreadable article answers the same not found
When the owner sends DELETE /v1/story/9999
Then the response status is 404 and the body is "Article not found"
When the deleter sends DELETE /v1/story/77, whose only version is a draft by another user
Then the response status is 404 and the body is "Article not found"
And that version remains
Scenario: An invalid id is refused
When the owner sends DELETE /v1/story/abc
Then the response status is 400 with the uint32 id message
Scenario: Move a story
When the owner sends POST /v1/story/5/move with {"site":"atelier"}
Then the response status is 200