feat(12.2-03): defer relation work on unsaved records and add child file routes
- record id 0 with X-Session-Key manages deferrable relations: create, link, unlink, delete and pivot edits are held in deferred_bindings
- the record's create save applies relation bindings with the file bindings; an ineligible link is a 422 on the relation-manager field
- child forms upload files through .../records/{child}/files/{field} keyed by X-Child-Session-Key; the child save commits them
- boot refuses a deferrable relation with create whose related model no plugin lists in Models()
This commit is contained in:
@@ -52,6 +52,7 @@ All paths are relative to `<prefix>/api/v1`. A controller ID `vendor.plugin.cont
|
||||
| GET and PUT `.../{id}/relations/{name}/records/{child}` | Show a child (needs `update` or a view form) and save it through the manage form (needs `update`). |
|
||||
| POST `.../{id}/relations/{name}/delete` | `{ids}`: delete children through their model (needs `delete`). A belongsToMany record loses this record's pivot row first. All or nothing. |
|
||||
| GET and PUT `.../{id}/relations/{name}/pivot/{child}` | Read and save the pivot form values of one link (needs a pivot form and `link` or `update`). |
|
||||
| GET and POST `.../{id}/relations/{name}/records/{child}/files/{field}`, PUT and DELETE `.../files/{field}/{file}`, POST `.../files/{field}/reorder`, GET `.../files/{field}/{file}/download` and `/thumb` | The file routes of a `fileupload` field in the relation's manage form, keyed by `X-Child-Session-Key`; `{child}` 0 is the child being created. |
|
||||
| GET `.../{id}/files/{field}` | The files of a `type: fileupload` field: attached files minus the session's pending removals, plus its pending uploads, in `sort_order`. `{id}` 0 is the record being created and needs `X-Session-Key`. |
|
||||
| POST `.../{id}/files/{field}` | Upload one multipart `file_data` part; it is bound to the `X-Session-Key` session (required) and attached by the record's next save. Answers 201 with the pending `cabana.FileItem`. |
|
||||
| PUT `.../{id}/files/{field}/{file}` | Save a file's `title` and `description` at once; the field must declare `useCaption` (403 otherwise). |
|
||||
@@ -59,6 +60,8 @@ All paths are relative to `<prefix>/api/v1`. A controller ID `vendor.plugin.cont
|
||||
| POST `.../{id}/files/{field}/reorder` | `{ids}` in the new order, exactly the field's visible files; they take the existing `sort_order` values at once. attachMany only (403 otherwise). |
|
||||
| GET `.../{id}/files/{field}/{file}/download`, GET `.../{id}/files/{field}/{file}/thumb` | Stream a protected file or its preview thumbnail; see the protected files note below. |
|
||||
|
||||
Every relation route accepts `{id}` 0 for the record being created when the relation is deferrable (a belongsToMany, or a hasMany with a nullable foreign key), the request carries `X-Session-Key` and the controller declares create: children created, linked, unlinked and deleted there are held against the key and applied by the record's first save, in its transaction, and an ineligible link answers 422 on the `relation-manager` field. A deferrable relation that declares `create` needs its related model in some plugin's `pact.HasModels` list, so `deferred:purge` can delete abandoned children; boot fails otherwise. Existing belongsToMany managers without `context: update` therefore appear on create screens.
|
||||
|
||||
Every relation child and pivot route loads the record through `pact.FormExtendQuery` and finds the child with one query that carries the record: a hasMany child by its foreign key, a belongsToMany record by a pivot row. A child of another record is `not_found`, never `forbidden`.
|
||||
|
||||
Every file route resolves its file with one query scoped to the record (loaded through `pact.FormExtendQuery`) or to the administrator's own pending uploads: a file of another record is `not_found`, never `forbidden`. The save applies the session's file work in its transaction: a pending upload on an attachOne field replaces the file attached before, a deferred removal deletes the attached file, and the blobs of deleted files are removed after commit. After that the save checks `maxFiles` and a `required` fileupload field (at least one file) and answers 422 on the field when either fails; the pending work stays for the next attempt.
|
||||
@@ -165,7 +168,7 @@ func (p *Plugin) AdminFS() fs.FS { return adminFS }
|
||||
| `cabana.Registry` | Immutable map of compiled controllers and settings, with permission-filtered metadata. |
|
||||
| `cabana.CRUDService` | Schema-projected show, create, update, delete, bulk delete and relation options. |
|
||||
| `cabana.ExecuteList` | Runs an allowlisted, paginated list query for a controller. |
|
||||
| `cabana.RelationService` | Linked, candidate, link and unlink operations of relation managers. |
|
||||
| `cabana.RelationService` | Linked, candidate, link and unlink operations of relation managers, child create, show, update and delete (`CreateChild`, `ShowChild`, `UpdateChild`, `DeleteChildren`) and pivot values (`ShowPivot`, `UpdatePivot`); its `SessionKey` makes record id 0 the record being created in that session. |
|
||||
| `cabana.SettingsService` | Reads and transactionally updates singleton settings rows. |
|
||||
| `cabana.FieldRelationProvider` / `cabana.FieldRelationContract` | Controller-supplied bindings for `type: relation` form fields. |
|
||||
| `cabana.AdminRelationContractProvider` / `cabana.RelationContract` | Controller-supplied bindings for relation managers. |
|
||||
@@ -182,13 +185,15 @@ func (p *Plugin) AdminFS() fs.FS { return adminFS }
|
||||
| `cabana.AdminActionResult` | Answer of an action route: the localized `message` and the filtered `fill` object. |
|
||||
| `cabana.ControllerAssets` | The `assets` object of list and form schemas: `scripts` and `styles` URL lists, always arrays. |
|
||||
| `cabana.ToolbarAction` | One registered toolbar button in a list schema's `toolbarActions`: action name and localized label. |
|
||||
| `cabana.SessionKeyHeader` | `X-Session-Key`, the header that carries the form session key of deferred file work. |
|
||||
| `cabana.SessionKeyHeader` | `X-Session-Key`, the header that carries the form session key of deferred file and relation work. |
|
||||
| `cabana.ChildSessionKeyHeader` | `X-Child-Session-Key`, the header that carries a relation child form's own session key for its file uploads. |
|
||||
| `cabana.RecordInput` | A create or update body (`Body`) and the form session key (`SessionKey`) whose file bindings the save applies. |
|
||||
| `cabana.FileItem` | One file of a fileupload field: id, name, size, content type, title, description, `sort_order`, `pending`, and `url`/`thumb_url` for public relations. |
|
||||
| `cabana.ThumbOptions` | A fileupload field's `thumbOptions` (the preview thumbnail `mode`). |
|
||||
| `cabana.FileMutationResult` | Answer of the file removal route: `removed`. |
|
||||
| `cabana.AdminFileCaptionRequest` | Body of the file caption route: optional `title` and `description`. Unknown keys are refused. |
|
||||
| `cabana.AdminFileList` / `cabana.AdminFileUpload` / `cabana.AdminFileUpdate` / `cabana.AdminFileRemove` / `cabana.AdminFileReorder` / `cabana.AdminFileDownload` / `cabana.AdminFileThumb` | Swag annotations of the file routes. |
|
||||
| `cabana.AdminRelationChildFileList` / `cabana.AdminRelationChildFileUpload` / `cabana.AdminRelationChildFileUpdate` / `cabana.AdminRelationChildFileRemove` / `cabana.AdminRelationChildFileReorder` / `cabana.AdminRelationChildFileDownload` / `cabana.AdminRelationChildFileThumb` | Swag annotations of the relation child file routes. |
|
||||
| `cabana.PartialView` / `cabana.PartialNode` | A rendered partial: a list of nodes, each an allowlisted element (`tag`, `attrs`, `children`) or a text node (`text`). |
|
||||
|
||||
## Configuration
|
||||
|
||||
Reference in New Issue
Block a user