71 lines
7.3 KiB
Markdown
71 lines
7.3 KiB
Markdown
This release adds custom content types: owners and admins define structured types such as Events or Recipes, and any verified user creates, edits and deletes their entries from the app or through a generic API. The built-in articles, authors, sites, media and users are unchanged.
|
|
|
|
## New
|
|
|
|
**Custom content types**
|
|
|
|
- A content type is a name, a key, an optional icon and an ordered list of 1 to 50 fields. The key is a slug of 2 to 40 characters and cannot be changed after creation.
|
|
- A field has a key, a label, one of six types (text, rich text, number, boolean, date or select) and the options that fit its type: required, unique, a default value, a minimum and a maximum, and a list of choices for a select.
|
|
- Changing a type that already has entries is checked first. A new required field needs a default, a removed select choice cannot still be in use, a lowered maximum cannot be below a stored value, and a field cannot become unique while stored values collide. Changing a field's type is refused. Removing a field drops its values from the entries the next time they are read or saved.
|
|
- A content type with entries cannot be deleted.
|
|
|
|
**Schema screens**
|
|
|
|
- The new Schema item in the sidebar, after Users, opens a screen that lists every content type, custom and built-in, for owners and admins. Other users get a 403 page and no sidebar entry.
|
|
- The schema builder creates and edits a custom type: add, remove and reorder fields, pick an icon, set each field's options in an inspector, and see the effect of a change before applying it. When the type already has entries, a review step shows how many entries are affected and names what the save will refuse.
|
|
- A type is deleted from its builder, with a confirmation, and a type that still has entries explains why it cannot be deleted and links to them.
|
|
- The built-in types (articles, authors, sites, media and users) can be opened read-only to see their fields.
|
|
- The sidebar has a Content group with your custom types, showing the first few and a "Show all" link when there are many.
|
|
|
|
**Entries**
|
|
|
|
- The entry list at `/content/<key>` shows a table on desktop and cards on mobile. It is paged (5, 25, 50 or 100) and sortable by last updated, created or id, and the page, size and sort are kept in the address so a reload or a shared link opens the same view.
|
|
- A Columns menu chooses which fields to show, up to six, and remembers the choice per type on this browser. By default the first four fields are shown.
|
|
- The entry editor at `/content/<key>/new` and `/content/<key>/<id>` builds its form from the type. Each field type has its own control: a text box, a Markdown editor with a toolbar and preview, a number input, a switch, a date picker and a select.
|
|
- Defaults are pre-filled and tagged. Rules (required, minimum, maximum, choices) are checked when you save, every error is shown at once, and focus goes to the first one. A message from the server, such as a duplicate value in a unique field, is shown on its field.
|
|
- "Saved at" shows the time the server answered. A form with unsaved changes asks before you leave it. Deleting an entry asks for confirmation.
|
|
- When the type's fields changed since the form was opened, the form is locked and offers "Reload form".
|
|
- The entry list and editor show who created and last updated each entry, by display name.
|
|
|
|
## Improvements and fixes
|
|
|
|
- Every page-size menu now centres its items vertically.
|
|
- The Columns and Sort menus of the entry list align their items correctly.
|
|
- The builder's accessibility issues from the design review are fixed.
|
|
- Markdown in entries is rendered with raw HTML disabled, images disabled and only http, https and mailto links active.
|
|
|
|
## API
|
|
|
|
- `GET /v1/content-type` lists the custom content types and `GET /v1/content-type/{key}` reads one. Both are open to any verified user or API key. A missing type answers `404` `content_type_not_found`.
|
|
- `POST /v1/content-type`, `PUT /v1/content-type/{key}` and `DELETE /v1/content-type/{key}` are for owners and admins. A key already in use answers `409` `content_type_key_taken`, an invalid key `400` `invalid_content_type_key`, an invalid field list or icon `422` `invalid_field`, a change the stored entries cannot take `409` `schema_incompatible`, and deleting a type with entries `409` `content_type_has_entries`. A delete answers `204`.
|
|
- A content type response has `id`, `key`, `name`, `icon`, `fields`, `created_at` and `updated_at`. The icon is `null` or a lowercase name of up to 40 characters, and a `PUT` replaces the whole type, so an absent icon clears it.
|
|
- `GET /v1/content-type/{key}/entry` lists entries as `{ items, total, page, size }`, with `page`, `size` (5, 25, 50 or 100, default 25) and `sort` (`id`, `created_at` or `updated_at`, then `:asc` or `:desc`, default `updated_at:desc`). An invalid value answers `400`, and a page past the end answers an empty `items` with the real `total`.
|
|
- `POST /v1/content-type/{key}/entry` creates an entry from `{ "data": { ... } }`, applying defaults, and `PUT /v1/content-type/{key}/entry/{id}` replaces its data. `GET` and `DELETE` on `/v1/content-type/{key}/entry/{id}` read and delete it, and a delete answers `204`. An entry that breaks the schema answers `422` `invalid_entry: <field>: <rule>`, a duplicate in a unique field `409` with the same shape, and a missing entry `404` `entry_not_found`.
|
|
- An entry response has `id`, `content_type` (the type's key), `data`, `created_by`, `updated_by`, `created_at` and `updated_at`.
|
|
- `GET /v1/builtin-type` and `GET /v1/builtin-type/{key}` return the read-only description of the built-in types. An unknown key answers `404` `builtin_type_not_found`.
|
|
- `GET /v1/user/lookup?ids=1,2,3` resolves display names for the users who created or updated entries. It is open to any verified user or API key.
|
|
|
|
## Under the hood
|
|
|
|
- Content types and entries live in two generic tables, with the fields and the entry data stored as JSON, so no schema change runs at runtime and the SQL stays portable across SQLite, MySQL, MariaDB and PostgreSQL.
|
|
- Schema and entry validation lives in the domain and the use cases, and the new handlers only decode requests and map errors.
|
|
- The app has one registry of field types, used by the builder, the list cells and the entry form.
|
|
- The list, builder and editor share their delete, save and unsaved-changes machinery, and the dashboard shell no longer calls the logout API or preloads content types itself.
|
|
- New dependency: `markdown-it`, with its type definitions, for the Markdown preview.
|
|
- Feature specs and QA procedures for each slice are in `features/`.
|
|
|
|
## Upgrading
|
|
|
|
- Replace the binary, or pull the new image. Back up the database first as usual.
|
|
- The first start after the upgrade creates the two new tables for content types and entries. Nothing else changes, existing data is untouched, and a second start changes nothing.
|
|
- No new environment variable is needed.
|
|
|
|
## Known limitations
|
|
|
|
- Entries cannot be filtered or searched by their field values yet.
|
|
- There are no relations between types, no media fields and no permissions grid, so every verified user can read and write the entries of every type.
|
|
- A type cannot be exported or imported yet.
|
|
- The key `new` is not reserved, so a type with that key cannot be opened from its entry list.
|
|
- On mobile, the entry list has no link to the builder; open the Schema item from the menu instead.
|
|
- Moving from one entry to another by editing the address drops unsaved edits without asking.
|