diff options
-rw-r--r-- | system/api/update.go | 28 | ||||
-rw-r--r-- | system/db/content.go | 92 | ||||
-rw-r--r-- | system/item/item.go | 13 |
3 files changed, 119 insertions, 14 deletions
diff --git a/system/api/update.go b/system/api/update.go index a669a9a..00933fb 100644 --- a/system/api/update.go +++ b/system/api/update.go @@ -3,6 +3,7 @@ package api import ( "context" "encoding/json" + "errors" "fmt" "log" "net/http" @@ -10,15 +11,17 @@ import ( "time" "github.com/ponzu-cms/ponzu/system/admin/upload" - "github.com/ponzu-cms/ponzu/system/admin/user" "github.com/ponzu-cms/ponzu/system/db" "github.com/ponzu-cms/ponzu/system/item" ) +var ErrNoAuth = errors.New("Auth failed for update request.") + // Updateable accepts or rejects update POST requests to endpoints such as: // /api/content/update?type=Review&id=1 type Updateable interface { // AcceptUpdate allows external content update submissions of a specific type + // user.IsValid(req) may be checked in AcceptUpdate to validate the request AcceptUpdate(http.ResponseWriter, *http.Request) error } @@ -55,12 +58,6 @@ func updateContentHandler(res http.ResponseWriter, req *http.Request) { return } - if user.IsValid(req) == false { - log.Println("[Update] invalid user.") - res.WriteHeader(http.StatusBadRequest) - return - } - post := p() ext, ok := post.(Updateable) @@ -139,15 +136,18 @@ func updateContentHandler(res http.ResponseWriter, req *http.Request) { return } - err = hook.BeforeAccept(res, req) + err = hook.BeforeAcceptUpdate(res, req) if err != nil { - log.Println("[Update] error calling BeforeAccept:", err) + log.Println("[Update] error calling BeforeAcceptUpdate:", err) return } err = ext.AcceptUpdate(res, req) if err != nil { - log.Println("[Update] error calling Accept:", err) + log.Println("[Update] error calling AcceptUpdate:", err) + if err == ErrNoAuth { + res.WriteHeader(http.StatusUnauthorized) + } return } @@ -160,9 +160,9 @@ func updateContentHandler(res http.ResponseWriter, req *http.Request) { // set specifier for db bucket in case content is/isn't Trustable var spec string - _, err = db.SetContent(t+spec+":"+id, req.PostForm) + _, err = db.UpdateContent(t+spec+":"+id, req.PostForm) if err != nil { - log.Println("[Update] error calling SetContent:", err) + log.Println("[Update] error calling UpdateContent:", err) res.WriteHeader(http.StatusInternalServerError) return } @@ -177,9 +177,9 @@ func updateContentHandler(res http.ResponseWriter, req *http.Request) { return } - err = hook.AfterAccept(res, req) + err = hook.AfterAcceptUpdate(res, req) if err != nil { - log.Println("[Update] error calling AfterAccept:", err) + log.Println("[Update] error calling AfterAcceptUpdate:", err) return } diff --git a/system/db/content.go b/system/db/content.go index d9096ae..81abd4f 100644 --- a/system/db/content.go +++ b/system/db/content.go @@ -36,6 +36,98 @@ func SetContent(target string, data url.Values) (int, error) { return update(ns, id, data) } +// UpdateContent inserts or updates values in the database. +// Updated content will be merged into existing values from the database. +// The `target` argument is a string made up of namespace:id (string:int) +func UpdateContent(target string, data url.Values) (int, error) { + t := strings.Split(target, ":") + ns, id := t[0], t[1] + + // check if content id == -1 (indicating new post). + // if so, run an insert which will assign the next auto incremented int. + // this is done because boltdb begins its bucket auto increment value at 0, + // which is the zero-value of an int in the Item struct field for ID. + // this is a problem when the original first post (with auto ID = 0) gets + // overwritten by any new post, originally having no ID, defauting to 0. + if id == "-1" { + return insert(ns, data) + } + + // retrieve existing content from the database + existingContent, err := Content(target) + if err != nil { + return 0, err + } + + // Unmarsal the existing values + s := item.Types[ns]() + + err = json.Unmarshal(existingContent, &s) + if err != nil { + log.Println("Error decoding json while sorting", ns, ":", err) + return 0, err + } + + var specifier string // i.e. __pending, __sorted, etc. + if strings.Contains(ns, "__") { + spec := strings.Split(ns, "__") + ns = spec[0] + specifier = "__" + spec[1] + } + + cid, err := strconv.Atoi(id) + if err != nil { + return 0, err + } + + // Don't allow the Item fields to be updated from form values + data.Del("id") + data.Del("uuid") + data.Del("slug") + + dec := schema.NewDecoder() + dec.SetAliasTag("json") // allows simpler struct tagging when creating a content type + dec.IgnoreUnknownKeys(true) // will skip over form values submitted, but not in struct + err = dec.Decode(s, data) + if err != nil { + return 0, err + } + + j, err := json.Marshal(s) + if err != nil { + return 0, err + } + + err = store.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte(ns + specifier)) + if err != nil { + return err + } + + err = b.Put([]byte(fmt.Sprintf("%d", cid)), j) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return 0, nil + } + + if specifier == "" { + go SortContent(ns) + } + + // update changes data, so invalidate client caching + err = InvalidateCache() + if err != nil { + return 0, err + } + + return cid, nil +} + func update(ns, id string, data url.Values) (int, error) { var specifier string // i.e. __pending, __sorted, etc. if strings.Contains(ns, "__") { diff --git a/system/item/item.go b/system/item/item.go index 5b41693..286842b 100644 --- a/system/item/item.go +++ b/system/item/item.go @@ -42,6 +42,9 @@ type Sortable interface { // to the different lifecycles/events a struct may encounter. Item implements // Hookable with no-ops so our user can override only whichever ones necessary. type Hookable interface { + BeforeAcceptUpdate(http.ResponseWriter, *http.Request) error + AfterAcceptUpdate(http.ResponseWriter, *http.Request) error + BeforeAccept(http.ResponseWriter, *http.Request) error AfterAccept(http.ResponseWriter, *http.Request) error @@ -132,6 +135,16 @@ func (i Item) String() string { return fmt.Sprintf("Item ID: %s", i.UniqueID()) } +// BeforeAcceptUpdate is a no-op to ensure structs which embed Item implement Hookable +func (i Item) BeforeAcceptUpdate(res http.ResponseWriter, req *http.Request) error { + return nil +} + +// AfterAcceptUpdate is a no-op to ensure structs which embed Item implement Hookable +func (i Item) AfterAcceptUpdate(res http.ResponseWriter, req *http.Request) error { + return nil +} + // BeforeAccept is a no-op to ensure structs which embed Item implement Hookable func (i Item) BeforeAccept(res http.ResponseWriter, req *http.Request) error { return nil |