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:
Jakub Zych
2026-10-02 19:08:16 +02:00
parent afb05b6ee4
commit fe9e8baaf1
16 changed files with 3643 additions and 436 deletions

View File

@@ -90,6 +90,7 @@ func TestPhase10Coverage(t *testing.T) {
want := []string{
"DELETE /{vendor}/{plugin}/{controller}/{id}",
"DELETE /{vendor}/{plugin}/{controller}/{id}/files/{field}/{file}",
"DELETE /{vendor}/{plugin}/{controller}/{id}/relations/{name}/records/{child}/files/{field}/{file}",
"POST /auth/login",
"POST /auth/logout",
"POST /auth/refresh",
@@ -102,15 +103,18 @@ func TestPhase10Coverage(t *testing.T) {
"POST /{vendor}/{plugin}/{controller}/{id}/relations/{name}/delete",
"POST /{vendor}/{plugin}/{controller}/{id}/relations/{name}/link",
"POST /{vendor}/{plugin}/{controller}/{id}/relations/{name}/records",
"POST /{vendor}/{plugin}/{controller}/{id}/relations/{name}/records/{child}/files/{field}",
"POST /{vendor}/{plugin}/{controller}/{id}/relations/{name}/records/{child}/files/{field}/reorder",
"POST /{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink",
"PUT /settings/{code}",
"PUT /{vendor}/{plugin}/{controller}/{id}",
"PUT /{vendor}/{plugin}/{controller}/{id}/files/{field}/{file}",
"PUT /{vendor}/{plugin}/{controller}/{id}/relations/{name}/pivot/{child}",
"PUT /{vendor}/{plugin}/{controller}/{id}/relations/{name}/records/{child}",
"PUT /{vendor}/{plugin}/{controller}/{id}/relations/{name}/records/{child}/files/{field}/{file}",
}
if strings.Join(unsafe, "\n") != strings.Join(want, "\n") {
t.Fatalf("unsafe routes changed; extend TestPhase10CSRF (it expects 19 besides login):\n%s", strings.Join(unsafe, "\n"))
t.Fatalf("unsafe routes changed; extend TestPhase10CSRF (it expects 23 besides login):\n%s", strings.Join(unsafe, "\n"))
}
// The routes added in Phase 10 are safe reads: GET /lang and the shared
// nested pattern serving field options, filter options and relation lists.