summaryrefslogtreecommitdiff
path: root/system/api
diff options
context:
space:
mode:
authorSteve Manuel <nilslice@gmail.com>2017-03-15 11:01:28 -0700
committerSteve Manuel <nilslice@gmail.com>2017-03-15 11:01:28 -0700
commit07fe1b15899fa6439e587984d6183371f9a6877c (patch)
treef1191c86ce03fbc36ea03539e9fbd27d143ec72d /system/api
parent0eaddb8ae0b29937f7514cc83c63e0aa3c377850 (diff)
changing API for external client interaction. Externalable -> Createable, +Deleteable, changing Hookable interface methods to conform to pattern: BeforeAPI$ACTION, etc.
Diffstat (limited to 'system/api')
-rw-r--r--system/api/external.go230
-rw-r--r--system/api/handlers.go4
-rw-r--r--system/api/update.go29
3 files changed, 17 insertions, 246 deletions
diff --git a/system/api/external.go b/system/api/external.go
deleted file mode 100644
index 7f13917..0000000
--- a/system/api/external.go
+++ /dev/null
@@ -1,230 +0,0 @@
-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/db"
- "github.com/ponzu-cms/ponzu/system/item"
-)
-
-// Externalable accepts or rejects external POST requests to endpoints such as:
-// /api/content/external?type=Review
-type Externalable interface {
- // Accept allows external content submissions of a specific type
- Accept(http.ResponseWriter, *http.Request) error
-}
-
-// Trustable allows external content to be auto-approved, meaning content sent
-// as an Externalable will be stored in the public content bucket
-type Trustable interface {
- AutoApprove(http.ResponseWriter, *http.Request) error
-}
-
-func externalContentHandler(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("[External] 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("[External] attempt to submit unknown type:", t, "from:", req.RemoteAddr)
- res.WriteHeader(http.StatusNotFound)
- return
- }
-
- post := p()
-
- ext, ok := post.(Externalable)
- if !ok {
- log.Println("[External] rejected non-externalable 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("[External] 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("[External] error calling BeforeAccept:", err)
- return
- }
-
- err = ext.Accept(res, req)
- if err != nil {
- log.Println("[External] error calling Accept:", err)
- return
- }
-
- err = hook.BeforeSave(res, req)
- if err != nil {
- log.Println("[External] error calling BeforeSave:", err)
- return
- }
-
- // set specifier for db bucket in case content is/isn't Trustable
- var spec string
-
- // check if the content is Trustable should be auto-approved, if so the
- // content is immediately added to the public content API. If not, then it
- // is added to a "pending" list, only visible to Admins in the CMS and only
- // if the type implements editor.Mergable
- trusted, ok := post.(Trustable)
- if ok {
- err := trusted.AutoApprove(res, req)
- if err != nil {
- log.Println("[External] error calling AutoApprove:", err)
- return
- }
- } else {
- spec = "__pending"
- }
-
- id, err := db.SetContent(t+spec+":-1", req.PostForm)
- if err != nil {
- log.Println("[External] 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("[External] error calling AfterSave:", err)
- return
- }
-
- err = hook.AfterAccept(res, req)
- if err != nil {
- log.Println("[External] 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("[External] 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("[External] error writing response:", err)
- return
- }
-
-}
diff --git a/system/api/handlers.go b/system/api/handlers.go
index 4a9eaff..83bbe43 100644
--- a/system/api/handlers.go
+++ b/system/api/handlers.go
@@ -2,6 +2,7 @@ package api
import (
"encoding/json"
+ "errors"
"log"
"net/http"
"strconv"
@@ -11,6 +12,9 @@ import (
"github.com/ponzu-cms/ponzu/system/item"
)
+// ErrNoAuth should be used to report failed auth requests
+var ErrNoAuth = errors.New("Auth failed for request")
+
// deprecating from API, but going to provide code here in case someone wants it
func typesHandler(res http.ResponseWriter, req *http.Request) {
var types = []string{}
diff --git a/system/api/update.go b/system/api/update.go
index 3a92a84..f7f7346 100644
--- a/system/api/update.go
+++ b/system/api/update.go
@@ -3,7 +3,6 @@ package api
import (
"context"
"encoding/json"
- "errors"
"fmt"
"log"
"net/http"
@@ -15,13 +14,11 @@ import (
"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
- AcceptUpdate(http.ResponseWriter, *http.Request) error
+ // Update enabled external clients to update content of a specific type
+ Update(http.ResponseWriter, *http.Request) error
}
func updateContentHandler(res http.ResponseWriter, req *http.Request) {
@@ -45,14 +42,14 @@ func updateContentHandler(res http.ResponseWriter, req *http.Request) {
p, found := item.Types[t]
if !found {
- log.Println("[Update] attempt to submit unknown type:", t, "from:", req.RemoteAddr)
+ log.Println("[Update] attempt to update content unknown type:", t, "from:", req.RemoteAddr)
res.WriteHeader(http.StatusNotFound)
return
}
id := req.URL.Query().Get("id")
if !db.IsValidID(id) {
- log.Println("[Update] attempt to submit update with missing or invalid id from:", req.RemoteAddr)
+ log.Println("[Update] attempt to update content with missing or invalid id from:", req.RemoteAddr)
res.WriteHeader(http.StatusBadRequest)
return
}
@@ -135,21 +132,21 @@ func updateContentHandler(res http.ResponseWriter, req *http.Request) {
return
}
- err = hook.BeforeAcceptUpdate(res, req)
+ err = hook.BeforeAPIUpdate(res, req)
if err != nil {
- log.Println("[Update] error calling BeforeAcceptUpdate:", err)
+ log.Println("[Update] error calling BeforeAPIUpdate:", err)
if err == ErrNoAuth {
- // BeforeAcceptUpdate can check user.IsValid(req) for auth
+ // BeforeAPIUpdate can check user.IsValid(req) for auth
res.WriteHeader(http.StatusUnauthorized)
}
return
}
- err = ext.AcceptUpdate(res, req)
+ err = ext.Update(res, req)
if err != nil {
- log.Println("[Update] error calling AcceptUpdate:", err)
+ log.Println("[Update] error calling Update:", err)
if err == ErrNoAuth {
- // AcceptUpdate can check user.IsValid(req) for auth
+ // Update can check user.IsValid(req) or other forms of validation for auth
res.WriteHeader(http.StatusUnauthorized)
}
return
@@ -172,7 +169,7 @@ func updateContentHandler(res http.ResponseWriter, req *http.Request) {
}
// 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))
+ ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%s", t, id))
req = req.WithContext(ctx)
err = hook.AfterSave(res, req)
@@ -181,9 +178,9 @@ func updateContentHandler(res http.ResponseWriter, req *http.Request) {
return
}
- err = hook.AfterAcceptUpdate(res, req)
+ err = hook.AfterAPIUpdate(res, req)
if err != nil {
- log.Println("[Update] error calling AfterAcceptUpdate:", err)
+ log.Println("[Update] error calling AfterAPIUpdate:", err)
return
}