feat(09-10): add typed relation manager
This commit is contained in:
138
cabana/http.go
138
cabana/http.go
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -127,6 +128,8 @@ func (s *service) mount(r pact.Router) {
|
||||
constrainController(g)
|
||||
g.Get("/{vendor}/{plugin}/{controller}/schema/form", s.formSchema)
|
||||
constrainController(g)
|
||||
g.Get("/{vendor}/{plugin}/{controller}/schema/relation/{name}", s.relationSchema)
|
||||
constrainRelation(g)
|
||||
g.Get("/{vendor}/{plugin}/{controller}", s.list)
|
||||
constrainController(g)
|
||||
g.Post("/{vendor}/{plugin}/{controller}", s.create)
|
||||
@@ -139,6 +142,14 @@ func (s *service) mount(r pact.Router) {
|
||||
constrainController(g)
|
||||
g.Delete("/{vendor}/{plugin}/{controller}/{id}", s.deleteRecord)
|
||||
constrainController(g)
|
||||
g.Get("/{vendor}/{plugin}/{controller}/{id}/relations/{name}", s.relationLinked)
|
||||
constrainRelation(g)
|
||||
g.Get("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates", s.relationCandidates)
|
||||
constrainRelation(g)
|
||||
g.Post("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/link", s.relationLink)
|
||||
constrainRelation(g)
|
||||
g.Post("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink", s.relationUnlink)
|
||||
constrainRelation(g)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -148,6 +159,129 @@ func constrainController(g pact.Router) {
|
||||
g.Where("controller", "[A-Za-z0-9_-]+")
|
||||
}
|
||||
|
||||
func constrainRelation(g pact.Router) {
|
||||
constrainController(g)
|
||||
g.Where("name", "[A-Za-z_][A-Za-z0-9_]*")
|
||||
}
|
||||
|
||||
func (s *service) relationSchema(w http.ResponseWriter, r *http.Request) {
|
||||
s.protect(w, r, func(cc *CompiledController) {
|
||||
cr, err := relationOf(cc, r.PathValue("name"))
|
||||
if err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
tr := s.translator()
|
||||
view := cr.Schema.Localize(r.Context(), tr)
|
||||
meta := map[string]any{}
|
||||
if locale := schemaLocale(r.Context(), tr); locale != "" {
|
||||
meta["locale"] = locale
|
||||
}
|
||||
WriteData(w, http.StatusOK, view, meta)
|
||||
})
|
||||
}
|
||||
|
||||
func relationQueryFromRequest(r *http.Request) RelationQuery {
|
||||
q := r.URL.Query()
|
||||
return RelationQuery{Search: q.Get("search"), Sort: q.Get("sort"), Dir: q.Get("dir"), Page: q.Get("page"), PerPage: q.Get("per_page")}
|
||||
}
|
||||
|
||||
func (s *service) relationLinked(w http.ResponseWriter, r *http.Request) {
|
||||
s.relationList(w, r, false)
|
||||
}
|
||||
|
||||
func (s *service) relationCandidates(w http.ResponseWriter, r *http.Request) {
|
||||
s.relationList(w, r, true)
|
||||
}
|
||||
|
||||
func (s *service) relationList(w http.ResponseWriter, r *http.Request, candidates bool) {
|
||||
s.protect(w, r, func(cc *CompiledController) {
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
svc, err := s.relations()
|
||||
if err != nil {
|
||||
WriteError(w, http.StatusInternalServerError, "error", msgServerError)
|
||||
return
|
||||
}
|
||||
var result *RelationResult
|
||||
if candidates {
|
||||
result, err = svc.Candidates(r.Context(), cc, r.PathValue("name"), id, relationQueryFromRequest(r))
|
||||
} else {
|
||||
result, err = svc.Linked(r.Context(), cc, r.PathValue("name"), id, relationQueryFromRequest(r))
|
||||
}
|
||||
if err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
WriteData(w, http.StatusOK, result.Data, map[string]any{"page": result.Meta.Page, "per_page": result.Meta.PerPage, "total": result.Meta.Total, "last_page": result.Meta.LastPage})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) relationLink(w http.ResponseWriter, r *http.Request) {
|
||||
s.relationMutation(w, r, true)
|
||||
}
|
||||
|
||||
func (s *service) relationUnlink(w http.ResponseWriter, r *http.Request) {
|
||||
s.relationMutation(w, r, false)
|
||||
}
|
||||
|
||||
func (s *service) relationMutation(w http.ResponseWriter, r *http.Request, link bool) {
|
||||
s.protect(w, r, func(cc *CompiledController) {
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
in, err := decodeRelationMutation(r)
|
||||
if err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
svc, err := s.relations()
|
||||
if err != nil {
|
||||
WriteError(w, http.StatusInternalServerError, "error", msgServerError)
|
||||
return
|
||||
}
|
||||
var result RelationMutationResult
|
||||
if link {
|
||||
result, err = svc.Link(r.Context(), cc, r.PathValue("name"), id, in)
|
||||
} else {
|
||||
result, err = svc.Unlink(r.Context(), cc, r.PathValue("name"), id, in)
|
||||
}
|
||||
if err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
WriteData(w, http.StatusOK, result, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func decodeRelationMutation(r *http.Request) (RelationMutationInput, error) {
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.UseNumber()
|
||||
dec.DisallowUnknownFields()
|
||||
var in RelationMutationInput
|
||||
if err := dec.Decode(&in); err != nil {
|
||||
return RelationMutationInput{}, relationInvalid("body", "The request body is invalid.")
|
||||
}
|
||||
var trailing any
|
||||
if err := dec.Decode(&trailing); err != io.EOF {
|
||||
return RelationMutationInput{}, relationInvalid("body", "The request body is invalid.")
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (s *service) relations() (RelationService, error) {
|
||||
db, err := s.db()
|
||||
if err != nil {
|
||||
return RelationService{}, err
|
||||
}
|
||||
return RelationService{DB: db}, nil
|
||||
}
|
||||
|
||||
func (s *service) formSchema(w http.ResponseWriter, r *http.Request) {
|
||||
s.protect(w, r, func(cc *CompiledController) {
|
||||
if cc.Form == nil {
|
||||
@@ -373,7 +507,7 @@ func (s *service) protect(w http.ResponseWriter, r *http.Request, fn func(*Compi
|
||||
fn(cc)
|
||||
}
|
||||
|
||||
func projectRow(row any, cols []ListColumn) map[string]any {
|
||||
func projectRow(row any, controller pact.AdminController, cols []ListColumn) map[string]any {
|
||||
v := reflect.ValueOf(row)
|
||||
for v.Kind() == reflect.Pointer {
|
||||
if v.IsNil() {
|
||||
@@ -387,7 +521,7 @@ func projectRow(row any, cols []ListColumn) map[string]any {
|
||||
}
|
||||
for _, col := range cols {
|
||||
if col.Relation != "" {
|
||||
if value, ok := relatedSelect(v, col); ok {
|
||||
if value, ok := relatedSelect(v, controller, col); ok {
|
||||
out[col.Key] = value
|
||||
}
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user