summaryrefslogtreecommitdiff
path: root/system/api
diff options
context:
space:
mode:
authorKevin Keuning <kkeuning@gmail.com>2017-02-24 23:56:00 -0600
committerKevin Keuning <kkeuning@gmail.com>2017-03-01 23:54:07 -0600
commit1613413ecc3a88b2263c6ee31faa86ed615483d7 (patch)
tree2d31ae9a1a6d3fb3ac21386a7176baacdcfb1f24 /system/api
parent50613a4972b41d650857e88eae65b2a0e11176ce (diff)
adding update to api
Diffstat (limited to 'system/api')
-rw-r--r--system/api/server.go2
-rw-r--r--system/api/update.go222
2 files changed, 224 insertions, 0 deletions
diff --git a/system/api/server.go b/system/api/server.go
index c5c1a23..6a848dd 100644
--- a/system/api/server.go
+++ b/system/api/server.go
@@ -9,4 +9,6 @@ func Run() {
http.HandleFunc("/api/content", Record(CORS(Gzip(contentHandler))))
http.HandleFunc("/api/content/external", Record(CORS(externalContentHandler)))
+
+ http.HandleFunc("/api/content/update", Record(CORS(updateContentHandler)))
}
diff --git a/system/api/update.go b/system/api/update.go
new file mode 100644
index 0000000..93904dc
--- /dev/null
+++ b/system/api/update.go
@@ -0,0 +1,222 @@
+package api
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "strings"
+ "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"
+)
+
+// 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
+ AcceptUpdate(http.ResponseWriter, *http.Request) error
+}
+
+func updateContentHandler(res http.ResponseWriter, req *http.Request) {
+ if req.Method != http.MethodPost {
+ res.WriteHeader(http.StatusMethodNotAllowed)
+ return
+ }
+
+ err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
+ if err != nil {
+ log.Println("[Update] error:", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ t := req.URL.Query().Get("type")
+ if t == "" {
+ res.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ p, found := item.Types[t]
+ if !found {
+ log.Println("[Update] attempt to submit unknown type:", t, "from:", req.RemoteAddr)
+ res.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ id := req.URL.Query().Get("id")
+ if id == "" {
+ log.Println("[Update] attempt to submit update with missing id from:", req.RemoteAddr)
+ res.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ if user.IsValid(req) == false {
+ res.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ post := p()
+
+ ext, ok := post.(Updateable)
+ if !ok {
+ log.Println("[Update] rejected non-replaceable type:", t, "from:", req.RemoteAddr)
+ res.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ ts := fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UnixNano()/int64(time.Millisecond))
+ req.PostForm.Set("timestamp", ts)
+ req.PostForm.Set("updated", ts)
+
+ urlPaths, err := upload.StoreFiles(req)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ for name, urlPath := range urlPaths {
+ req.PostForm.Set(name, urlPath)
+ }
+
+ // check for any multi-value fields (ex. checkbox fields)
+ // and correctly format for db storage. Essentially, we need
+ // fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2}
+ fieldOrderValue := make(map[string]map[string][]string)
+ ordVal := make(map[string][]string)
+ for k, v := range req.PostForm {
+ if strings.Contains(k, ".") {
+ fo := strings.Split(k, ".")
+
+ // put the order and the field value into map
+ field := string(fo[0])
+ order := string(fo[1])
+ fieldOrderValue[field] = ordVal
+
+ // orderValue is 0:[?type=Thing&id=1]
+ orderValue := fieldOrderValue[field]
+ orderValue[order] = v
+ fieldOrderValue[field] = orderValue
+
+ // discard the post form value with name.N
+ req.PostForm.Del(k)
+ }
+
+ }
+
+ // add/set the key & value to the post form in order
+ for f, ov := range fieldOrderValue {
+ for i := 0; i < len(ov); i++ {
+ position := fmt.Sprintf("%d", i)
+ fieldValue := ov[position]
+
+ if req.PostForm.Get(f) == "" {
+ for i, fv := range fieldValue {
+ if i == 0 {
+ req.PostForm.Set(f, fv)
+ } else {
+ req.PostForm.Add(f, fv)
+ }
+ }
+ } else {
+ for _, fv := range fieldValue {
+ req.PostForm.Add(f, fv)
+ }
+ }
+ }
+ }
+
+ hook, ok := post.(item.Hookable)
+ if !ok {
+ log.Println("[Update] error: Type", t, "does not implement item.Hookable or embed item.Item.")
+ res.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ err = hook.BeforeAccept(res, req)
+ if err != nil {
+ log.Println("[Update] error calling BeforeAccept:", err)
+ return
+ }
+
+ err = ext.AcceptUpdate(res, req)
+ if err != nil {
+ log.Println("[Update] error calling Accept:", err)
+ return
+ }
+
+ err = hook.BeforeSave(res, req)
+ if err != nil {
+ log.Println("[Update] error calling BeforeSave:", err)
+ return
+ }
+
+ // set specifier for db bucket in case content is/isn't Trustable
+ var spec string
+
+ _, err = db.SetContent(t+spec+":"+id, req.PostForm)
+ if err != nil {
+ log.Println("[Update] error calling SetContent:", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ // set the target in the context so user can get saved value from db in hook
+ ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id))
+ req = req.WithContext(ctx)
+
+ err = hook.AfterSave(res, req)
+ if err != nil {
+ log.Println("[Update] error calling AfterSave:", err)
+ return
+ }
+
+ err = hook.AfterAccept(res, req)
+ if err != nil {
+ log.Println("[Update] error calling AfterAccept:", err)
+ return
+ }
+
+ // create JSON response to send data back to client
+ var data map[string]interface{}
+ if spec != "" {
+ spec = strings.TrimPrefix(spec, "__")
+ data = map[string]interface{}{
+ "status": spec,
+ "type": t,
+ }
+ } else {
+ spec = "public"
+ data = map[string]interface{}{
+ "id": id,
+ "status": spec,
+ "type": t,
+ }
+ }
+
+ resp := map[string]interface{}{
+ "data": []map[string]interface{}{
+ data,
+ },
+ }
+
+ j, err := json.Marshal(resp)
+ if err != nil {
+ log.Println("[Update] error marshalling response to JSON:", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ res.Header().Set("Content-Type", "application/json")
+ _, err = res.Write(j)
+ if err != nil {
+ log.Println("[Update] error writing response:", err)
+ return
+ }
+
+}