feat: image variants per image

This commit is contained in:
fsylvain committed 2026-10-08 19:57:51 +02:00
1 parent f567866161
commit 951fef18ba
92 files changed
+8695 -413

No files matched your search

+34 -3
View File
@@ -23,7 +23,7 @@ for example `https://cms.example/v1/article/`.
| 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} |
| 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) |
| 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 |
| Settings | GET /v1/settings |
@@ -62,7 +62,7 @@ never changes the response or the change itself.
| Category | Actions |
| --- | --- |
| `articles` | `article.create`, `article.edit`, `article.publish`, `article.unpublish`, `article.delete` |
| `media` | `media.upload`, `media.rename`, `media.delete`, `media.merge`, `media.purge`, `media.variant_create`, `media.variant_update`, `media.variant_delete`, `media.variant_enable`, `media.variant_disable`, `media.variant_purge` |
| `media` | `media.upload`, `media.rename`, `media.delete`, `media.merge`, `media.purge`, `media.variant_create`, `media.variant_update`, `media.variant_delete`, `media.variant_enable`, `media.variant_disable`, `media.variant_purge`, `media.variant_generate_all`, `media.variant_purge_image` |
| `users` | `user.register`, `user.verify`, `user.update_profile`, `invite.create`, `invite.cancel`, `invite.renew`, `invite.accept` |
| `system` | `system.media_purge` (scheduled), `system.update_checked`, `system.update_downloaded`, `system.restart_requested`, `system.activity_cleanup`, `system.activity_retention_changed` |
@@ -73,6 +73,10 @@ kinds `text` and `body`; a body edit carries a signed character count delta and
change that leaves a value as it was (same body, same publish state, same file name, same display
name) records nothing. Params: `media.merge` and `media.purge` and `system.media_purge` carry
`merged` and `articles_updated`, plus `failed_groups` when a group failed (still outcome `success`);
`media.variant_generate_all` carries `generated`, `failed` and `causes` (a list of `too_large`,
`unsupported_format` or `failed`) and is `failed` only when nothing was generated and something failed;
`media.variant_purge_image` carries `purged_files` and `purged_bytes`; both have an `image` target
and are recorded only when files were generated or removed;
`system.update_checked` carries `update_available`; `system.restart_requested` carries `kind` and,
for an update, `tag` (it has no target). `system.update_downloaded` is recorded when the job reaches
`ready` or `failed`, with the origin captured at request time; a refusal at the start is recorded
@@ -88,7 +92,10 @@ try again."; the text of the error never reaches the event, the API or the CSV.
codes, article bodies, file bytes, storage paths and asset urls are never recorded.
Article routes accept `?format=markdown|html|json` (default `markdown`). `GET /v1/media/` accepts
`q`, `hash`, `cursor`, `filter`, `sort` and `limit`.
`q`, `hash`, `cursor`, `filter`, `sort` (`newest`, `oldest`, `name`, `largest` or `total_size`, the
original plus its generated variants), `variants` (`with` or `without` generated variant files) and
`limit`. Every item carries `variant_count` and `variant_size` (the index rows of the image and
their bytes, 0 when none); `counts` stays library-wide. Any other `variants` or `sort` value is 400.
## Image variants
Owners and admins manage named variants of the stored images. These routes are session only (no API
@@ -115,6 +122,30 @@ Errors are plain text: 400 for a body that is not valid JSON, 404 "Variant not f
named “thumb” already exists. Choose another name." and 422 with one sentence for the first invalid
field (name, width and height, fit, format, quality).
### Variants of one image
Owners and admins only, session only: a request with an API key is 401. All ids are image ids.
| Route | Purpose |
| --- | --- |
| `GET /v1/media/{id}/variants` | `{ "unsupported": null, "items": [...] }` for the enabled variants in name order. `unsupported` is `"svg"` (with `items: []`) for an SVG. |
| `POST /v1/media/{id}/variants` | Generates every enabled variant that is missing. `{ "generated", "failed", "failures": [{ "name", "cause" }], "items": [...] }`, 200 even when some fail. |
| `POST /v1/media/{id}/variants/{name}` | Generates one enabled variant. 200 with the item. |
| `DELETE /v1/media/{id}/variants/{name}` | Removes the generated file. `{ "purged_files", "purged_bytes" }`, also for a disabled variant. |
An item is `{ name, width, height, fit, format, output_width, output_height, output_format,
generated, size, url }`: `width` and `height` are the settings (`null` when auto), `output_*` the
size and format the file has or will have (never above the source), `generated` is true only when
the file exists with the variant's current settings, `size` is its bytes or `null`, and `url` is
`/static/uploadedImages/<stored name>?v=<name>`. A `cause` is `too_large`, `unsupported_format` or
`failed`. A file generated here is the same file the public url serves, and the other way round.
Uploads generate the variants flagged `generate_on_upload` (enabled ones, raster sources only)
before answering; a failure there is logged and never fails the upload.
Errors are plain text: 400 for a malformed id, 404 "Image not found" or "Variant not found" (also for a
disabled variant on `POST`), 413 "The source image is too large to generate this variant.", 415 "The
source image can't be read, so this variant can't be generated.", 422 "Variants aren't generated for
this format." and 500 "Internal server error".
## Errors
Every error is a plain-text body with one sentence; the status is in the HTTP status line. The
statuses are 400, 401, 403, 404, 409, 429 and 500. Messages are listed on the Integration page.