feat: SEO backend output (2/4)
This commit is contained in:
1 parent
0eb46b0d74
commit
eb9876fde3
32 files changed
+3859
-65
No files matched your search
+20
-5
@@ -10,8 +10,9 @@ The origin that serves the app, for example `https://cms.example`. Every path st
|
||||
for example `https://cms.example/v1/article/`.
|
||||
|
||||
## Authentication
|
||||
- Public routes need nothing: `GET /v1/article/` and `GET /v1/article/{id}`. They return online
|
||||
articles only.
|
||||
- Public routes need nothing: `GET /v1/article/`, `GET /v1/article/{id}`, `GET /v1/article/{id}/seo`
|
||||
and `GET /v1/site/{key}/sitemap`. They return online articles only; the list and the resolved SEO
|
||||
object also answer drafts to a verified session or API key.
|
||||
- Everything else needs a signed-in browser session (the `jwt` cookie) or an API key:
|
||||
`Authorization: Bearer rcms_...`. Create keys at `/integration/api-keys`. A key acts as its
|
||||
creator, with that user's role, and shows in full once.
|
||||
@@ -21,14 +22,14 @@ for example `https://cms.example/v1/article/`.
|
||||
under `/v1/site`) and the author writes (`POST`, `PATCH` and `DELETE` under `/v1/author`, `POST /v1/author/me` included) are session only too.
|
||||
- Each key can make 120 requests per minute. Beyond that: 429 with a `Retry-After` header.
|
||||
|
||||
## Routes a key can call (26)
|
||||
## Routes a key can call (29)
|
||||
| Group | Routes |
|
||||
| --- | --- |
|
||||
| Articles | GET /v1/article/, GET /v1/article/{id}, POST /v1/article/, PATCH /v1/article/{id}, PATCH /v1/article/{id}/publish, PATCH /v1/article/{id}/unpublish, DELETE /v1/article/{id} |
|
||||
| Articles | GET /v1/article/, GET /v1/article/{id}, GET /v1/article/{id}/seo, POST /v1/article/{id}/seo/check, POST /v1/article/, PATCH /v1/article/{id}, PATCH /v1/article/{id}/publish, PATCH /v1/article/{id}/unpublish, DELETE /v1/article/{id} |
|
||||
| Media | GET /v1/media/, POST /v1/media/, PATCH /v1/media/{id}, DELETE /v1/media/{id}, POST /v1/media/merge, GET /v1/media/{id}/usage, GET /v1/media/purge-settings (owner or admin), PATCH /v1/media/purge-settings (owner or admin), GET /v1/media/purge-preview (owner or admin), POST /v1/media/purge (owner or admin), GET /v1/media/{id}/variants (owner or admin, session only), POST /v1/media/{id}/variants (owner or admin, session only), POST /v1/media/{id}/variants/{name} (owner or admin, session only), DELETE /v1/media/{id}/variants/{name} (owner or admin, session only) |
|
||||
| Users and invites | GET /v1/user/, PATCH /v1/user/me, POST /v1/user/invite (owner only), DELETE /v1/user/invite/{id} (owner only), POST /v1/user/invite/{id}/renew (owner only), GET /v1/auth/me (with the linked `author`) |
|
||||
| Settings | GET /v1/settings |
|
||||
| Sites | GET /v1/site/ |
|
||||
| Sites | GET /v1/site/, GET /v1/site/{key}/sitemap |
|
||||
| Authors | GET /v1/author/ |
|
||||
|
||||
## Activity log
|
||||
@@ -155,6 +156,20 @@ those fields, a `null` value (except on `cover_image_id` and `social_image_id`)
|
||||
400 with the validator text and an unknown site is 404 `site_not_found`. Moving an article never
|
||||
changes `is_online`. `{"body": ""}` still sets an empty body, but `{}` and `{"body": null}` no longer do: they are 400.
|
||||
|
||||
### Resolved SEO, check, list status and sitemap
|
||||
|
||||
RenewCMS renders no page and injects no meta tag: it returns the data a frontend needs.
|
||||
|
||||
| Route | Who | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `GET /v1/article/{id}/seo` | public for online articles; a verified session or API key also reads drafts | The resolved object `{ title, description, canonical_url, robots, og, twitter, image, authors, published_at, modified_at, fallbacks, json_ld }`. It never carries the body, the id or `seo_dismissed`, ignores `format`, and writes nothing. An offline article for any other caller, and a missing one, is 404 `Article not found`. |
|
||||
| `POST /v1/article/{id}/seo/check` | verified session or API key (401 without credentials, 403 unverified) | Evaluates the article without saving. The body is `{}` or any of the twelve SEO fields of `PATCH` plus `body`; each replaces the saved value for this check only. `site_id`, `author_ids`, unknown fields and `null` (except on `cover_image_id` and `social_image_id`) are 400 with the body-mismatch message. The other validation is the one of `PATCH` (`invalid_slug`, `invalid_url` as 422, `image_not_found`, validator text). Answers `{ status, suggestions, slug }` where `suggestions` is `[{ code, field, params }]` and `slug` is `{ available, suggestion }`. A taken slug is a 200 with `available: false` and the first free `<slug>-N` in `suggestion`, never a 409. |
|
||||
| `GET /v1/site/{key}/sitemap` | public | A JSON array `[{ url, modified_at, noindex }]` of the online articles of the site with that key, by id ascending. `url` is `base_url/slug` (never the canonical override). `page` (default 1) and `size` (25, 50, 100 or 500, default 100); an invalid value is 400 with the body-mismatch message. The total is in the `X-Total-Count` header. An unknown key, an id included, is 404 `site_not_found`. |
|
||||
|
||||
`GET /v1/article/` also takes `seo=ready|suggestions|hidden` (any other value, an empty one included, is 400 with the body-mismatch message, after the `format` check). It is applied before paging and composes with `q`, `status`, `site`, `sort`, `page` and `size`; `total` follows it and `counts` ignore it. Every item carries `seo_status` (`ready`, `suggestions` or `hidden`) and `seo_suggestions` (the number of suggestions that are not dismissed). The filter is evaluated in memory over every matching article, so its cost grows with the number of articles on the site; without it only the returned page is assessed.
|
||||
|
||||
`X-Total-Count` is added to `Access-Control-Expose-Headers` next to `Link`, so a browser on an allowed origin can read it.
|
||||
|
||||
### Article SEO fields
|
||||
|
||||
An article carries twelve search and sharing fields, all edited through `PATCH /v1/article/{id}` and returned by
|
||||
|
||||
Reference in new issue
Block a user