feat: translation flows, revision conflicts, delete scopes, article moves and a target site for new articles
This commit is contained in:
1 parent
339e646eb8
commit
45fbbc33b1
93 files changed
+7630
-294
No files matched your search
@@ -28,25 +28,25 @@ The headers are set on those responses only. Same-origin SPA code reads them dir
|
||||
| --- | --- | --- |
|
||||
| 400 | `The request cannot be processed due to a mismatch in the format of the body.` | undecodable or invalid body, missing `copy`, `copy: false` with a source, `if_revision` invalid, an unknown or empty update |
|
||||
| 400 | `The server expects the ID to be in the format of an unsigned 32-bit integer (uint32).` | invalid id in the path |
|
||||
| 400 | the validator text (for example `Key: 'PostTranslation.Title' Error:Field validation for 'Title' failed on the 'min' tag`) | `lang` or `title` missing, title shorter than 3 or longer than 200 characters on a create |
|
||||
| 400 | the validator text (for example `Key: 'PostTranslation.Title' Error:Field validation for 'Title' failed on the 'min' tag`) | `lang` or `title` missing, title shorter than 3 or longer than 200 characters on a create, `site` that is `""` or not a site key on `POST /v1/article/` |
|
||||
| 400 | `Article title must be between 3 and 200 characters.` | a `PATCH` title outside 3 to 200 runes |
|
||||
| 400 | `invalid_slug` | a `PATCH` slug that is not a slug |
|
||||
| 401 | `You must be logged in to access this resource.` | no valid session or key |
|
||||
| 403 | `missing_permission` | the caller lacks the permission (`X-Missing-Permission`) |
|
||||
| 404 | `Article not found` | the version, or the story, does not exist or the caller cannot read it |
|
||||
| 404 | `source_unavailable` | the chosen source does not exist or is not readable; the same answer for both |
|
||||
| 404 | `site_not_found` | `POST /v1/story/{story_id}/move` with an unknown site key |
|
||||
| 404 | `site_not_found` | `POST /v1/story/{story_id}/move`, or `POST /v1/article/` with `site`, naming an unknown site key |
|
||||
| 409 | `revision_conflict` | `if_revision` is not the current `revision` (`X-Current-Revision`) |
|
||||
| 409 | `language_exists` | the language already has a readable version (`X-Existing-Article-Id`) |
|
||||
| 409 | `language_unavailable` | one neutral answer: the language is disabled on the site, or its version exists but is not readable by the caller, or a concurrent request created it first |
|
||||
| 409 | `setup_required` | the version has no language yet (`lang` `""`): the site default is not confirmed |
|
||||
| 409 | `language_unavailable` | one neutral answer: the language is disabled on the site (the chosen site on `POST /v1/article/` with `site`), or its version exists but is not readable by the caller, or a concurrent request created it first |
|
||||
| 409 | `setup_required` | the version has no language yet (`lang` `""`): the site default is not confirmed (the chosen site on `POST /v1/article/` with `site`) |
|
||||
| 409 | `slug_taken` | a `PATCH` slug used by another article of the same site and language (`X-Slug-Suggestion`) |
|
||||
| 409 | `slug_conflict` | a move would put an address that already exists at the destination (`X-Conflicting-Articles`) |
|
||||
| 409 | `story_move_required` | `PATCH site_id` on a version whose story holds several versions |
|
||||
| 409 | `story_busy` | the database could not lock the rows in time (lock timeout, deadlock or serialization failure); retry |
|
||||
| 422 | `invalid_language` | the tag is not a language tag |
|
||||
| 422 | `unsupported_language` | a valid tag outside the supported list |
|
||||
| 422 | `language_not_enabled` | the site has no language row for the tag (creation), or a destination site lacks or has disabled a language of the story (move, `X-Missing-Languages`) |
|
||||
| 422 | `language_not_enabled` | the site (the chosen one on `POST /v1/article/` with `site`) has no language row for the tag (creation), or a destination site lacks or has disabled a language of the story (move, `X-Missing-Languages`) |
|
||||
| 422 | `language_disabled` | publishing an offline version whose language is disabled on its site |
|
||||
| 422 | `source_other_story` | the source belongs to another story |
|
||||
|
||||
@@ -138,7 +138,18 @@ Answers `201` with the created article object. Order of checks: permission, path
|
||||
|
||||
### `POST /v1/article/` without `story_id`
|
||||
|
||||
Unchanged: it creates a new story with one offline version. `lang` is now optional: absent, the version gets the starting language of the site as before; present, it is normalized and must be an enabled language of the default site (`422 invalid_language`, `unsupported_language`, `language_not_enabled`; `409 language_unavailable` when disabled; `409 setup_required` before the site default is confirmed).
|
||||
It creates a new story with one offline version, on the default site unless the body names a site.
|
||||
|
||||
```json
|
||||
{"title": "Neuf", "lang": "fr", "site": "atelier"}
|
||||
```
|
||||
|
||||
- `site` is optional: the site key. Absent or `null`, the default site is used exactly as before. A present `site` is validated like any site key (`400` with the plain-text body `invalid_site_key` for `""` or `Bad Key!`), then looked up (`404 site_not_found` for an unknown key, a plain text body, nothing stored). Every caller who may create articles may create on every site: `content.create` is not scoped to a site.
|
||||
- `lang` is optional. Absent, the version gets the starting language of the chosen site; present, it is normalized and must be an enabled language of the chosen site (`422 invalid_language`, `unsupported_language`, `language_not_enabled`; `409 language_unavailable` when disabled on that site; `409 setup_required` before that site's default language is confirmed). The same tag can therefore be accepted on one site and refused on another.
|
||||
- The slug is generated from the title and made free within the chosen site and language only.
|
||||
- `site` with `story_id`, `source_article_id` or `copy` answers `400` with the body mismatch message and stores nothing. `POST /v1/article/{id}/translations` has no `site` field: the translation keeps the site of its source and an unknown `site` key in its body is ignored.
|
||||
- The response is the created article object (`201`) with the same keys as before; `site_id` is the id of the chosen site. The article is offline, so it is invisible to anonymous reads and public lists, and `GET /v1/article/?site=<key>` lists it for readers.
|
||||
- `article.create` records `params.site` (the site key) on success; a refusal records one failed event with a plain reason (refusals by validation, `401` and `403` happen before the use case and record nothing).
|
||||
|
||||
## Deleting
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ Breaking change: the default used to be HTML. Clients that need HTML must send `
|
||||
}
|
||||
```
|
||||
|
||||
`lang` is the full BCP 47 language tag of the article (`""` while the site default language is not confirmed) and `story_id` is the story the article belongs to. Both are present on every article object (list items, reads, create and update responses, public reads) as additive keys, and every other key is unchanged. They are read-only on `PATCH`, where `lang` and `story_id` are ignored. `POST /v1/article/` takes `lang`, `story_id`, `source_article_id` and `copy` only as described in [article-localization.md](article-localization.md), which also documents `revision`, `capabilities`, `translations` and `source`, the additive keys of every article read. Supported tags: `en`, `en-GB`, `en-US`, `fr`, `fr-CA`, `pt`, `pt-BR`, `es`, `de`, `ar`. Existing articles each get their own story and `lang` `""` until the site default is confirmed.
|
||||
`lang` is the full BCP 47 language tag of the article (`""` while the site default language is not confirmed) and `story_id` is the story the article belongs to. Both are present on every article object (list items, reads, create and update responses, public reads) as additive keys, and every other key is unchanged. They are read-only on `PATCH`, where `lang` and `story_id` are ignored. `POST /v1/article/` takes `title`, `body`, and optionally `lang`, `site` (a site key; the default site when absent or `null`), `story_id`, `source_article_id` and `copy` only as described in [article-localization.md](article-localization.md), which also documents `revision`, `capabilities`, `translations` and `source`, the additive keys of every article read. Supported tags: `en`, `en-GB`, `en-US`, `fr`, `fr-CA`, `pt`, `pt-BR`, `es`, `de`, `ar`. Existing articles each get their own story and `lang` `""` until the site default is confirmed.
|
||||
|
||||
`created_by` is the id of the user who created the article (`0` when it is attributed to nobody). It is present on every article object (list, read, create and update responses) only when the caller is signed in with a verified session or authenticates with an API key; the key acts as its creator. Anonymous callers, and unverified or stale sessions, get the same object without the field. The SPA reads it to decide which rows a role limited to `own` can edit, delete or publish.
|
||||
|
||||
@@ -220,6 +220,7 @@ Example for `<h2>Plan</h2><p>Replace the <strong>old</strong> deck.</p>`:
|
||||
| --- | --- |
|
||||
| `400` | `The server expects the ID to be in the format of an unsigned 32-bit integer (uint32).` for an invalid id |
|
||||
| `400` | `The format must be one of: markdown, html, json.` for an unsupported format |
|
||||
| `400` | `invalid_site_key` for a `site` of `POST /v1/article/` that is not a valid site key |
|
||||
| `400` | `invalid_slug`, `image_not_found` or the validator text for an over-long SEO field, `cover_image_id: 0` or an unknown `seo_dismissed` code |
|
||||
| `404` | `Article not found` |
|
||||
| `409` | `slug_taken`, with the header `X-Slug-Suggestion: <first free slug>` (this response only) |
|
||||
|
||||
Reference in new issue
Block a user