summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--system/api/update.go28
-rw-r--r--system/db/content.go92
-rw-r--r--system/item/item.go13
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