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:
@@ -2,10 +2,14 @@ package cabana
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/party"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type controllerRef struct {
|
||||
@@ -332,3 +336,83 @@ func (r *Registry) rolePermissions(role string) map[string]bool {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// checkDeferredModels stops boot when a deferrable relation that declares
|
||||
// create points at a related model no activated plugin lists in
|
||||
// pact.HasModels Models(): deferred:purge resolves the slave type of an
|
||||
// abandoned child through those models and could not delete it (RESEARCH
|
||||
// Pitfall 9). A listed model of the same Go type matches; with a database,
|
||||
// a listed model of the same morph type matches too.
|
||||
func checkDeferredModels(reg *Registry, plugins []party.Plugin, gdb *gorm.DB) error {
|
||||
if reg == nil {
|
||||
return nil
|
||||
}
|
||||
var models []any
|
||||
for _, p := range plugins {
|
||||
if hm, ok := p.(pact.HasModels); ok && hm != nil {
|
||||
for _, m := range hm.Models() {
|
||||
if m != nil {
|
||||
models = append(models, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ids := make([]string, 0, len(reg.byID))
|
||||
for id := range reg.byID {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Strings(ids)
|
||||
for _, id := range ids {
|
||||
cc := reg.byID[id]
|
||||
names := make([]string, 0, len(cc.Relations))
|
||||
for name := range cc.Relations {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
cr := cc.Relations[name]
|
||||
if !cr.deferrable || !cr.allows("create") {
|
||||
continue
|
||||
}
|
||||
related := cr.Contract.NewRelated()
|
||||
if !modelListed(models, related, gdb) {
|
||||
morph := tableName(related)
|
||||
if gdb != nil {
|
||||
if m, err := lagoon.MorphType(gdb, related); err == nil {
|
||||
morph = m
|
||||
}
|
||||
} else if owner, ok := related.(interface{ MorphName() string }); ok {
|
||||
morph = owner.MorphName()
|
||||
}
|
||||
return fmt.Errorf("cabana: admin controller %s/%s: relation %s creates %s records under deferral, but no activated plugin lists that model in Models(), so deferred:purge could not remove abandoned ones", cc.PluginID, id, name, morph)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func modelListed(models []any, related any, gdb *gorm.DB) bool {
|
||||
want := reflect.TypeOf(related)
|
||||
for want.Kind() == reflect.Pointer {
|
||||
want = want.Elem()
|
||||
}
|
||||
var morph string
|
||||
if gdb != nil {
|
||||
morph, _ = lagoon.MorphType(gdb, related)
|
||||
}
|
||||
for _, m := range models {
|
||||
t := reflect.TypeOf(m)
|
||||
for t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t == want {
|
||||
return true
|
||||
}
|
||||
if morph != "" {
|
||||
if got, err := lagoon.MorphType(gdb, m); err == nil && got == morph {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user