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:
@@ -108,6 +108,13 @@ func Activate(app *backpack.App, plugins []party.Plugin) (*Routes, error) {
|
||||
if err := checkFileLimits(reg, uploadBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var gdb *gorm.DB
|
||||
if app != nil {
|
||||
gdb, _ = app.Lookup[*gorm.DB]()
|
||||
}
|
||||
if err := checkDeferredModels(reg, plugins, gdb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := compileContributions(reg, plugins); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -291,6 +298,23 @@ func (s *service) mount(r pact.Router) {
|
||||
constrainChild(g)
|
||||
g.Put("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/pivot/{child}", requireAjax(s.relationPivotUpdate))
|
||||
constrainChild(g)
|
||||
// File routes of a relation child form (D-17): {child} 0 is the
|
||||
// child being created in the X-Child-Session-Key session.
|
||||
const childFiles = "/{vendor}/{plugin}/{controller}/{id}/relations/{name}/records/{child}/files/{field}"
|
||||
g.Get(childFiles, s.relationChildFileList)
|
||||
constrainChildFile(g)
|
||||
g.Post(childFiles, requireAjax(s.relationChildFileUpload))
|
||||
constrainChildFile(g)
|
||||
g.Post(childFiles+"/reorder", requireAjax(s.relationChildFileReorder))
|
||||
constrainChildFile(g)
|
||||
g.Put(childFiles+"/{file}", requireAjax(s.relationChildFileUpdate))
|
||||
constrainChildFileID(g)
|
||||
g.Delete(childFiles+"/{file}", requireAjax(s.relationChildFileRemove))
|
||||
constrainChildFileID(g)
|
||||
g.Get(childFiles+"/{file}/download", s.relationChildFileDownload)
|
||||
constrainChildFileID(g)
|
||||
g.Get(childFiles+"/{file}/thumb", s.relationChildFileThumb)
|
||||
constrainChildFileID(g)
|
||||
// File routes of `type: fileupload` fields (D-09). {id} 0 is the
|
||||
// record being created in the X-Session-Key session.
|
||||
g.Post("/{vendor}/{plugin}/{controller}/{id}/files/{field}", requireAjax(s.fileUpload))
|
||||
@@ -335,6 +359,16 @@ func constrainChild(g pact.Router) {
|
||||
g.Where("child", "[0-9]+")
|
||||
}
|
||||
|
||||
func constrainChildFile(g pact.Router) {
|
||||
constrainChild(g)
|
||||
g.Where("field", "[A-Za-z_][A-Za-z0-9_]*")
|
||||
}
|
||||
|
||||
func constrainChildFileID(g pact.Router) {
|
||||
constrainChildFile(g)
|
||||
g.Where("file", "[0-9]+")
|
||||
}
|
||||
|
||||
func constrainFile(g pact.Router) {
|
||||
constrainController(g)
|
||||
g.Where("field", "[A-Za-z_][A-Za-z0-9_]*")
|
||||
@@ -506,9 +540,8 @@ func (s *service) relationList(w http.ResponseWriter, r *http.Request, candidate
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
svc, err := s.relations()
|
||||
if err != nil {
|
||||
WriteError(w, http.StatusInternalServerError, "error", msgServerError)
|
||||
svc, ok := s.relationsFor(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var result *RelationResult
|
||||
@@ -559,9 +592,8 @@ func (s *service) relationMutation(w http.ResponseWriter, r *http.Request, link
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
svc, err := s.relations()
|
||||
if err != nil {
|
||||
WriteError(w, http.StatusInternalServerError, "error", msgServerError)
|
||||
svc, ok := s.relationsFor(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var result RelationMutationResult
|
||||
@@ -601,6 +633,25 @@ func (s *service) relations() (RelationService, error) {
|
||||
return RelationService{DB: db, bucket: s.bucket(), tr: s.translator()}, nil
|
||||
}
|
||||
|
||||
// relationsFor is the relation service of one request, carrying its
|
||||
// X-Session-Key: with it, record id 0 is the record being created in that
|
||||
// session. A malformed key is a 422; it writes the response and returns
|
||||
// false on failure.
|
||||
func (s *service) relationsFor(w http.ResponseWriter, r *http.Request) (RelationService, bool) {
|
||||
key, _, err := sessionKeyFrom(r)
|
||||
if err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return RelationService{}, false
|
||||
}
|
||||
svc, err := s.relations()
|
||||
if err != nil {
|
||||
WriteError(w, http.StatusInternalServerError, "error", msgServerError)
|
||||
return RelationService{}, false
|
||||
}
|
||||
svc.SessionKey = key
|
||||
return svc, true
|
||||
}
|
||||
|
||||
func (s *service) formSchema(w http.ResponseWriter, r *http.Request) {
|
||||
s.protect(w, r, func(cc *CompiledController) {
|
||||
if cc.Form == nil {
|
||||
|
||||
Reference in New Issue
Block a user